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 */}
-
- {/* Command Palette */}
-
-
+ {/* Main */}
+
{children}
+
+ {/* Command Palette */}
+
+
+
+ {/* Collapse toggle */}
+
+
);
}