From b05eb8b2d45d56dcb210e4af1598718ef953de2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gjermund=20H=C3=B8s=C3=B8ien=20Wiggen?= Date: Sun, 7 Jun 2026 22:34:26 +0200 Subject: [PATCH] feat: add sidebar collapse/expand, theme-toggle, theme-aware colors --- web/src/components/app-shell.tsx | 257 +++++++++++++++++++------------ 1 file changed, 161 insertions(+), 96 deletions(-) diff --git a/web/src/components/app-shell.tsx b/web/src/components/app-shell.tsx index 6250f4b..fa0ed9a 100644 --- a/web/src/components/app-shell.tsx +++ b/web/src/components/app-shell.tsx @@ -1,7 +1,7 @@ "use client"; -import { useState, useEffect, Suspense } from "react"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useState, useEffect, Suspense, createContext, useContext } from "react"; +import { usePathname, useSearchParams } from "next/navigation"; import Link from "next/link"; import { LayoutGridIcon, @@ -9,12 +9,21 @@ import { InboxIcon, ClockIcon, SettingsIcon, + PanelLeftCloseIcon, + PanelLeftIcon, } from "lucide-react"; import { getTickets, getQueues } from "@/lib/api"; import type { Queue } from "@/lib/types"; import { CommandPalette } from "@/components/command-palette"; +import { ThemeToggle } from "@/components/theme-toggle"; import { cn } from "@/lib/utils"; +const SidebarCollapsedContext = createContext(false); + +function useSidebarCollapsed() { + return useContext(SidebarCollapsedContext); +} + interface ViewCounts { all: number; my: number; @@ -22,6 +31,46 @@ interface ViewCounts { recent: number; } +function SidebarNavItem({ + href, + icon: Icon, + label, + count, + active, +}: { + href: string; + icon: React.ComponentType<{ className?: string }>; + label: string; + count?: number; + active: boolean; +}) { + const collapsed = useSidebarCollapsed(); + + return ( + + + + {!collapsed && label} + + {!collapsed && count !== undefined && count > 0 && ( + + {count} + + )} + + ); +} + function SidebarNav() { const pathname = usePathname(); const searchParams = useSearchParams(); @@ -64,6 +113,8 @@ function SidebarNav() { }); }, []); + const collapsed = useSidebarCollapsed(); + const views = [ { label: "All tickets", @@ -101,64 +152,44 @@ function SidebarNav() { <>
{views.map((view) => { - const Icon = view.icon; const active = pathname === "/" && (view.param ? currentView === view.param : !currentView); return ( - - - - {view.label} - - {view.count > 0 && ( - - {view.count} - - )} - + icon={view.icon} + label={view.label} + count={view.count} + active={active} + /> ); })}
{queues.length > 0 && (
-
- Queues -
+ {!collapsed && ( +
+ Queues +
+ )} {queues.map((queue) => { const active = pathname === "/" && searchParams.get("queue") === queue.id; + const QueueIcon = () => ( + + ); return ( - - - - {queue.name} - - {queue.count > 0 && ( - - {queue.count} - - )} - + icon={QueueIcon} + label={queue.name} + count={queue.count} + active={active} + /> ); })}
@@ -169,26 +200,36 @@ function SidebarNav() { function SidebarBottom() { const pathname = usePathname(); + const collapsed = useSidebarCollapsed(); return ( -
- + +
- - Admin - -
-
- U +
+ + U +
- User + {!collapsed && ( + + User + + )} +
+
+
); @@ -196,6 +237,7 @@ function SidebarBottom() { export function AppShell({ children }: { children: React.ReactNode }) { const [commandOpen, setCommandOpen] = useState(false); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); useEffect(() => { const down = (e: KeyboardEvent) => { @@ -216,50 +258,73 @@ export function AppShell({ children }: { children: React.ReactNode }) { }, []); return ( -
- {/* Sidebar */} -
- {/* Bottom */} - - + {/* Nav */} + - {/* Main */} -
{children}
+ {/* Bottom */} + + - {/* Command Palette */} - -
+ {/* Main */} +
{children}
+ + {/* Command Palette */} + +
+ + {/* Collapse toggle */} + + ); }