From 1b545cfffd58f404b7cd7b9332d32def1bee4444 Mon Sep 17 00:00:00 2001 From: francy51 Date: Thu, 12 Mar 2026 15:39:44 -0400 Subject: [PATCH] feat(shell): add collapsible sidebar rail --- components/shell/app-shell.tsx | 122 +++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 22 deletions(-) diff --git a/components/shell/app-shell.tsx b/components/shell/app-shell.tsx index 9041d9c..2f6bce3 100644 --- a/components/shell/app-shell.tsx +++ b/components/shell/app-shell.tsx @@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query'; import type { LucideIcon } from 'lucide-react'; -import { Activity, BarChart3, BookOpenText, ChartCandlestick, Eye, Landmark, LineChart, LogOut, Menu, NotebookTabs, Search } from 'lucide-react'; +import { Activity, BarChart3, BookOpenText, ChartCandlestick, ChevronLeft, ChevronRight, Eye, Landmark, LineChart, LogOut, Menu, NotebookTabs, Search } from 'lucide-react'; import Link from 'next/link'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; @@ -134,6 +134,8 @@ const GROUP_LABELS: Record = { portfolio: 'Portfolio' }; +const SIDEBAR_PREFERENCE_KEY = 'fiscal-shell-sidebar-collapsed'; + function normalizeTicker(value: string | null | undefined) { const normalized = value?.trim().toUpperCase() ?? ''; return normalized.length > 0 ? normalized : null; @@ -249,6 +251,8 @@ export function AppShell({ title, subtitle, actions, activeTicker, breadcrumbs, const [isSigningOut, setIsSigningOut] = useState(false); const [isMoreOpen, setIsMoreOpen] = useState(false); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); + const [hasLoadedSidebarPreference, setHasLoadedSidebarPreference] = useState(false); const notifications = useTaskNotificationsCenter(); const { data: session } = authClient.useSession(); const sessionUser = (session?.user ?? null) as { name?: string | null; email?: string | null; role?: unknown } | null; @@ -391,6 +395,22 @@ export function AppShell({ title, subtitle, actions, activeTicker, breadcrumbs, } }; + useEffect(() => { + const storedPreference = window.localStorage.getItem(SIDEBAR_PREFERENCE_KEY); + if (storedPreference === 'true') { + setIsSidebarCollapsed(true); + } + setHasLoadedSidebarPreference(true); + }, []); + + useEffect(() => { + if (!hasLoadedSidebarPreference) { + return; + } + + window.localStorage.setItem(SIDEBAR_PREFERENCE_KEY, String(isSidebarCollapsed)); + }, [hasLoadedSidebarPreference, isSidebarCollapsed]); + useEffect(() => { const browserWindow = window as Window & { requestIdleCallback?: (callback: IdleRequestCallback) => number; @@ -448,19 +468,54 @@ export function AppShell({ title, subtitle, actions, activeTicker, breadcrumbs,