You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
3.0 KiB
TypeScript

"use client";
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ChevronRightIcon } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@/components/ui/sidebar";
import { navItems, type NavItem } from "./nav-items";
type NavGroupItem = Extract<NavItem, { items: unknown }>;
export function AppSidebar({ footer }: { footer: React.ReactNode }) {
const pathname = usePathname();
return (
<Sidebar>
<SidebarHeader className="px-4 py-3 font-semibold">
Cleo Minimarket
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarMenu className="gap-0.5">
{navItems.map((item) =>
"items" in item ? (
<NavGroup key={item.title} item={item} pathname={pathname} />
) : (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton
render={<Link href={item.href} />}
isActive={pathname === item.href}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
),
)}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>{footer}</SidebarFooter>
</Sidebar>
);
}
// Controlled karena sidebar tetap ter-mount antar navigasi; defaultOpen yang berubah memicu warning Base UI.
function NavGroup({
item,
pathname,
}: {
item: NavGroupItem;
pathname: string;
}) {
const isActive = item.items.some((sub) => pathname.startsWith(sub.href));
const [open, setOpen] = useState(isActive);
const [prevActive, setPrevActive] = useState(isActive);
// Buka otomatis saat navigasi masuk ke grup ini, tanpa menutup grup yang dibuka manual.
if (isActive !== prevActive) {
setPrevActive(isActive);
if (isActive) setOpen(true);
}
return (
<Collapsible
open={open}
onOpenChange={setOpen}
render={<SidebarMenuItem />}
>
<CollapsibleTrigger render={<SidebarMenuButton />}>
<item.icon />
<span>{item.title}</span>
<ChevronRightIcon className="ml-auto transition-transform group-data-panel-open/menu-button:rotate-90" />
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items.map((sub) => (
<SidebarMenuSubItem key={sub.href}>
<SidebarMenuSubButton
render={<Link href={sub.href} />}
isActive={pathname.startsWith(sub.href)}
>
<span>{sub.title}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</Collapsible>
);
}