From d941c7a6cd58dafdba9340264fb03332be23f956 Mon Sep 17 00:00:00 2001 From: Moheeb Zara Date: Sat, 16 May 2026 20:14:56 -0700 Subject: [PATCH] feat(nav): collapsible sidebar categories incl. Finance and Learn Sidebar is now grouped into collapsible sections (Workspace, Governance, People, Finance, Learn, Account, Admin). Finance splits payments/financials into their own category; Learn groups classes/equipment/forms. Each section header toggles open/closed and the state persists per category in localStorage (defaults open so nothing is hidden on first visit). Admin section stays admin/board-gated. --- components/app-sidebar.tsx | 122 ++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 457e6fd..2513239 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -15,6 +15,7 @@ import { MessageSquare, Users, CreditCard, BookUser, Download, LogOut, Vote, ShieldAlert, ScrollText, LineChart, UserCircle, UserSearch, MessagesSquare, SlidersHorizontal, ClipboardList, FileText, Award, BadgeCheck, GraduationCap, Hammer, + ChevronDown, } from 'lucide-react' interface NavLinkProps { @@ -52,6 +53,54 @@ function NavLink({ href, label, icon: Icon, badge, active, onClick }: NavLinkPro ) } +// Collapsible category. Open/closed is remembered per category in +// localStorage so it survives navigation; defaults to open so nothing is +// hidden on first visit. +function NavSection({ + id, + title, + children, +}: { + id: string + title: string + children: React.ReactNode +}) { + const storageKey = `sidebar:section:${id}` + const [open, setOpen] = useState(true) + useEffect(() => { + try { + if (window.localStorage.getItem(storageKey) === 'closed') setOpen(false) + } catch { + /* localStorage unavailable; keep default */ + } + }, [storageKey]) + const toggle = () => { + setOpen(prev => { + const next = !prev + try { + window.localStorage.setItem(storageKey, next ? 'open' : 'closed') + } catch { + /* ignore */ + } + return next + }) + } + return ( +
+ + {open &&
{children}
} +
+ ) +} + interface AppSidebarProps { member: SpaceMember & { spaces: Space | null } roleName?: string @@ -109,44 +158,47 @@ export function AppSidebar({ member, roleName, taskBadge = 0, commsBadge = 0, pa {/* Nav */}