2026-01-08 19:10:40 +02:00
|
|
|
"use client";
|
|
|
|
|
|
2026-03-08 21:16:52 +05:30
|
|
|
import { CheckCircle2, CircleAlert } from "lucide-react";
|
2026-03-07 02:34:23 +05:30
|
|
|
import { Spinner } from "@/components/ui/spinner";
|
2026-01-08 19:10:40 +02:00
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import type { NavItem } from "../../types/layout.types";
|
2026-03-21 12:06:48 +05:30
|
|
|
import { SidebarButton } from "./SidebarButton";
|
2026-01-08 19:10:40 +02:00
|
|
|
|
|
|
|
|
interface NavSectionProps {
|
|
|
|
|
items: NavItem[];
|
|
|
|
|
onItemClick?: (item: NavItem) => void;
|
|
|
|
|
isCollapsed?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 21:16:52 +05:30
|
|
|
function StatusBadge({ status }: { status: NavItem["statusIndicator"] }) {
|
|
|
|
|
if (status === "processing") {
|
|
|
|
|
return (
|
|
|
|
|
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center h-[14px] w-[14px] rounded-full bg-primary/15">
|
|
|
|
|
<Spinner size="xs" className="text-primary" />
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (status === "success") {
|
|
|
|
|
return (
|
|
|
|
|
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center h-[14px] w-[14px] rounded-full bg-emerald-500/15 animate-in fade-in duration-300">
|
|
|
|
|
<CheckCircle2 className="h-[10px] w-[10px] text-emerald-500" />
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (status === "error") {
|
|
|
|
|
return (
|
|
|
|
|
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center h-[14px] w-[14px] rounded-full bg-destructive/15 animate-in fade-in duration-300">
|
|
|
|
|
<CircleAlert className="h-[10px] w-[10px] text-destructive" />
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 21:17:45 +05:30
|
|
|
function StatusIcon({
|
|
|
|
|
status,
|
|
|
|
|
FallbackIcon,
|
|
|
|
|
className,
|
|
|
|
|
}: {
|
2026-03-08 21:16:52 +05:30
|
|
|
status: NavItem["statusIndicator"];
|
|
|
|
|
FallbackIcon: NavItem["icon"];
|
|
|
|
|
className?: string;
|
|
|
|
|
}) {
|
|
|
|
|
if (status === "processing") {
|
|
|
|
|
return <Spinner size="sm" className={cn("shrink-0 text-primary", className)} />;
|
|
|
|
|
}
|
|
|
|
|
if (status === "success") {
|
2026-03-08 21:17:45 +05:30
|
|
|
return (
|
|
|
|
|
<CheckCircle2
|
|
|
|
|
className={cn("shrink-0 text-emerald-500 animate-in fade-in duration-300", className)}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2026-03-08 21:16:52 +05:30
|
|
|
}
|
|
|
|
|
if (status === "error") {
|
2026-03-08 21:17:45 +05:30
|
|
|
return (
|
|
|
|
|
<CircleAlert
|
|
|
|
|
className={cn("shrink-0 text-destructive animate-in fade-in duration-300", className)}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2026-03-08 21:16:52 +05:30
|
|
|
}
|
|
|
|
|
return <FallbackIcon className={cn("shrink-0", className)} />;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 12:06:48 +05:30
|
|
|
function CollapsedOverlay({ item }: { item: NavItem }) {
|
|
|
|
|
const indicator = item.statusIndicator;
|
|
|
|
|
if (indicator && indicator !== "idle") {
|
|
|
|
|
return <StatusBadge status={indicator} />;
|
|
|
|
|
}
|
|
|
|
|
if (item.badge) {
|
|
|
|
|
return (
|
|
|
|
|
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center min-w-[14px] h-[14px] px-0.5 rounded-full bg-red-500 text-white text-[9px] font-medium">
|
|
|
|
|
{item.badge}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-08 19:10:40 +02:00
|
|
|
export function NavSection({ items, onItemClick, isCollapsed = false }: NavSectionProps) {
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn("flex flex-col gap-0.5 py-2", isCollapsed && "items-center")}>
|
|
|
|
|
{items.map((item) => {
|
2026-03-17 04:40:46 +05:30
|
|
|
const joyrideAttr =
|
|
|
|
|
item.title === "Inbox" || item.title.toLowerCase().includes("inbox")
|
2026-03-21 12:06:48 +05:30
|
|
|
? { "data-joyride": "inbox-sidebar" as const }
|
2026-03-17 04:40:46 +05:30
|
|
|
: {};
|
2026-01-08 19:10:40 +02:00
|
|
|
|
|
|
|
|
return (
|
2026-03-21 12:06:48 +05:30
|
|
|
<SidebarButton
|
2026-01-08 19:10:40 +02:00
|
|
|
key={item.url}
|
2026-03-21 12:06:48 +05:30
|
|
|
icon={item.icon}
|
|
|
|
|
label={item.title}
|
2026-01-08 19:10:40 +02:00
|
|
|
onClick={() => onItemClick?.(item)}
|
2026-03-21 12:06:48 +05:30
|
|
|
isCollapsed={isCollapsed}
|
2026-03-22 00:01:50 +05:30
|
|
|
isActive={item.isActive}
|
2026-03-21 12:06:48 +05:30
|
|
|
badge={item.badge}
|
|
|
|
|
collapsedOverlay={<CollapsedOverlay item={item} />}
|
|
|
|
|
expandedIconNode={
|
|
|
|
|
<StatusIcon
|
|
|
|
|
status={item.statusIndicator}
|
|
|
|
|
FallbackIcon={item.icon}
|
|
|
|
|
className="h-4 w-4"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
buttonProps={joyrideAttr}
|
|
|
|
|
/>
|
2026-01-08 19:10:40 +02:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|