SPB Git
3.9 KB · 55 lines tsx
1"use client";2import { useEffect, useState, createContext, useContext, type ReactNode } from "react";3import { useRouter, usePathname } from "next/navigation";4import { Menu, PanelLeft } from "lucide-react";5import { Sidebar } from "./sidebar";6import { CommandPalette } from "./command-palette";7import { usePrefs, setPrefs, applyTheme } from "@/lib/client/prefs";8import { resetDraft } from "@/lib/client/use-chat";9import { cn } from "./ui";10import { Logo } from "./logo";1112const ShellCtx = createContext<{ openPalette: () => void; openDrawer: () => void }>({ openPalette: () => {}, openDrawer: () => {} });13export const useShell = () => useContext(ShellCtx);1415export function AppShell({ children }: { children: ReactNode }) {16  const prefs = usePrefs(); const router = useRouter(); const pathname = usePathname();17  const [drawer, setDrawer] = useState(false); const [palette, setPalette] = useState(false);18  useEffect(() => { applyTheme(prefs.theme); }, [prefs.theme]);19  useEffect(() => { document.documentElement.dataset.fontsize = prefs.fontSize; }, [prefs.fontSize]);20  useEffect(() => { setDrawer(false); }, [pathname]);21  useEffect(() => { if ("serviceWorker" in navigator && process.env.NODE_ENV === "production") navigator.serviceWorker.register("/sw.js").catch(() => {}); }, []);22  useEffect(() => {23    const onKey = (e: KeyboardEvent) => {24      const mod = e.metaKey || e.ctrlKey;25      if (mod && e.key.toLowerCase() === "k") { e.preventDefault(); setPalette(p => !p); }26      else if (mod && e.shiftKey && e.key.toLowerCase() === "o") { e.preventDefault(); resetDraft(); router.push("/"); }27      else if (mod && e.key === "\\") { e.preventDefault(); setPrefs({ sidebarCollapsed: !prefs.sidebarCollapsed }); }28    };29    window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey);30  }, [prefs.sidebarCollapsed, router]);31  const collapsed = prefs.sidebarCollapsed;32  return (33    <ShellCtx.Provider value={{ openPalette: () => setPalette(true), openDrawer: () => setDrawer(true) }}>34      <div className="flex h-dvh w-full overflow-hidden bg-bg">35        {/* desktop sidebar */}36        <aside className={cn("hidden md:flex h-full shrink-0 flex-col border-r border-border bg-bg-elev transition-[width] duration-200", collapsed ? "w-0 overflow-hidden border-r-0" : "w-[var(--sidebar-w)]")}>37          <Sidebar onOpenPalette={() => setPalette(true)} />38        </aside>39        {/* mobile drawer */}40        {drawer && <div className="fixed inset-0 z-40 md:hidden" onClick={() => setDrawer(false)}><div className="absolute inset-0 bg-black/60" /><aside onClick={e => e.stopPropagation()} className="absolute inset-y-0 left-0 w-[86vw] max-w-[320px] anim-fade-up bg-bg-elev shadow-soft safe-t"><Sidebar onOpenPalette={() => { setDrawer(false); setPalette(true); }} onNavigate={() => setDrawer(false)} /></aside></div>}41        <div className="relative flex min-w-0 flex-1 flex-col">42          {/* top bar (mobile + collapsed) */}43          <header className={cn("flex items-center gap-2 px-2 py-2 md:px-3 safe-t", !collapsed && "md:hidden")}>44            <button className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-lg hover:bg-surface-2" aria-label="Menu" onClick={() => setDrawer(true)}><Menu className="h-5 w-5" /></button>45            <button className="hidden md:inline-flex h-9 w-9 items-center justify-center rounded-lg text-fg-2 hover:bg-surface-2" aria-label="Afficher le panneau" onClick={() => setPrefs({ sidebarCollapsed: false })}><PanelLeft className="h-4.5 w-4.5" /></button>46            <div className="flex items-center gap-2 md:hidden"><Logo className="h-6 w-6" /><span className="text-sm font-semibold tracking-tight">Chat SPBoucher AI</span></div>47          </header>48          <main className="flex min-h-0 flex-1 flex-col">{children}</main>49        </div>50      </div>51      <CommandPalette open={palette} onClose={() => setPalette(false)} />52    </ShellCtx.Provider>53  );54}55
Files · laptop-build-20261101
Menu

Theme