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
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>
|
|
);
|
|
}
|