import { type MouseEvent, useCallback, useEffect, useMemo, useState, } from "react"; import { ChevronDown, ChevronRight, Train } from "lucide-react"; import { Stack, Group, Text, Box, UnstyledButton, NavLink } from "@mantine/core"; import type { SidebarItem, SidebarSection } from "./types"; export interface FreightSidebarProps { sections: SidebarSection[]; activeHref?: string; onNavigate?: (href: string) => void; } const sidebarItemKey = (item: SidebarItem, parentKey: string) => item.href ?? `${parentKey}::${item.label}`; const collectSidebarHrefs = (items: SidebarItem[]): string[] => items.flatMap((item) => { const hrefs: string[] = []; if (item.href) hrefs.push(item.href.toLowerCase()); if (item.children?.length) hrefs.push(...collectSidebarHrefs(item.children)); return hrefs; }); const flattenSectionItems = (sections: SidebarSection[]) => sections.flatMap((section) => section.items); const FreightSidebar = ({ sections, activeHref, onNavigate, }: FreightSidebarProps) => { const items = useMemo(() => flattenSectionItems(sections), [sections]); const activePath = activeHref?.toLowerCase() ?? ""; const isHrefActive = useCallback( (href: string) => { const normalized = href.toLowerCase(); return ( activePath === normalized || activePath.startsWith(`${normalized}/`) ); }, [activePath], ); const branchContainsActive = useCallback( (branch: SidebarItem[]) => collectSidebarHrefs(branch).some((href) => isHrefActive(href)), [isHrefActive], ); const defaultExpanded = useMemo(() => { const acc: Record = {}; const walk = (entries: SidebarItem[], parentKey: string) => { for (const entry of entries) { if (!entry.children?.length) continue; const key = sidebarItemKey(entry, parentKey); acc[key] = branchContainsActive(entry.children) || (entry.href ? isHrefActive(entry.href) : false); walk(entry.children, key); } }; for (const item of items) { if (!item.children?.length) continue; const key = item.href ?? item.label; acc[key] = activePath === key.toLowerCase() || activePath.startsWith(`${key.toLowerCase()}/`) || branchContainsActive(item.children); walk(item.children, key); } return acc; }, [activePath, branchContainsActive, isHrefActive, items]); const [expanded, setExpanded] = useState>(defaultExpanded); useEffect(() => { setExpanded((current) => ({ ...defaultExpanded, ...current })); }, [defaultExpanded]); const navigateTo = (event: MouseEvent, href: string) => { if (onNavigate) { event.preventDefault(); onNavigate(href); } }; const toggleExpanded = (key: string) => { setExpanded((current) => ({ ...current, [key]: !current[key] })); }; const renderNavBranch = ( children: SidebarItem[], depth: number, parentKey: string, ) => children.map((child) => { const key = sidebarItemKey(child, parentKey); const isGroup = Boolean(child.children?.length) && !child.href; if (isGroup) { const isOpen = expanded[key] ?? false; const groupActive = branchContainsActive(child.children!); return ( toggleExpanded(key)} style={{ background: groupActive ? "var(--mantine-color-green-1)" : "transparent", padding: "8px 12px", borderRadius: "8px", width: "100%", cursor: "pointer", }} > {child.label} {isOpen && ( {renderNavBranch(child.children!, depth + 1, key)} )} ); } if (!child.href) return null; const childHref = child.href.toLowerCase(); const childActiveHref = isHrefActive(childHref); return ( navigateTo(e as any, child.href!)} label={child.label} active={childActiveHref} color="green" style={{ borderRadius: "8px", cursor: "pointer", fontSize: "14px", }} rightSection={} /> ); }); const renderIconWell = (icon: React.ReactNode, active: boolean) => { if (!icon) return null; return ( {icon} ); }; const renderTopLevelItem = (item: SidebarItem) => { if (!item.href) return null; const hasChildren = Boolean(item.children?.length); const itemHref = item.href.toLowerCase(); const childActive = hasChildren ? branchContainsActive(item.children!) : false; const isCurrentItem = hasChildren ? activePath === itemHref : isHrefActive(itemHref); const isSectionActive = childActive && !isCurrentItem; const isActive = isCurrentItem || isSectionActive; const isOpen = expanded[item.href] ?? false; const leafActive = isCurrentItem && !hasChildren; return ( navigateTo(e as any, item.href!)} label={item.label} leftSection={renderIconWell(item.icon, isActive)} active={leafActive} color="green" variant="light" style={{ borderRadius: "10px", cursor: "pointer", fontSize: "14px", fontWeight: 500, padding: "8px 10px", }} rightSection={ hasChildren ? ( { e.preventDefault(); toggleExpanded(item.href!); }} /> ) : ( ) } /> {hasChildren && isOpen && ( {renderNavBranch(item.children!, 0, item.href)} )} ); }; return ( EDR Freight Backoffice {sections.map((section) => ( {section.title} {section.items.map((item) => renderTopLevelItem(item))} ))} ); }; export default FreightSidebar;