diff --git a/web/app/(app)/layout.tsx b/web/app/(app)/layout.tsx index 5716a50..be7a8f8 100644 --- a/web/app/(app)/layout.tsx +++ b/web/app/(app)/layout.tsx @@ -1,6 +1,5 @@ import { AuthProvider } from "@/components/AuthProvider"; -import { LicenseBanner } from "@/components/LicenseBanner"; -import { Sidebar } from "@/components/Sidebar"; +import { AppShell } from "@/components/AppShell"; export default function AppLayout({ children, @@ -9,13 +8,7 @@ export default function AppLayout({ }) { return ( -
- -
- - {children} -
-
+ {children}
); } diff --git a/web/components/AppShell.tsx b/web/components/AppShell.tsx new file mode 100644 index 0000000..5eaac67 --- /dev/null +++ b/web/components/AppShell.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { usePathname } from "next/navigation"; +import { LicenseBanner } from "@/components/LicenseBanner"; +import { Logo } from "@/components/Logo"; +import { Sidebar, SidebarDrawer } from "@/components/Sidebar"; +import { useAuth } from "@/components/AuthProvider"; + +function MenuIcon() { + return ( + + + + ); +} + +/** + * Owns the responsive chrome so app/(app)/layout.tsx can stay a server + * component. Above lg this is the layout it always was; below lg the sidebar + * becomes an offcanvas behind the top bar's hamburger. + */ +export function AppShell({ children }: { children: React.ReactNode }) { + const [open, setOpen] = useState(false); + const pathname = usePathname(); + const buttonRef = useRef(null); + const { instance } = useAuth(); + + // A drawer that survives navigation would cover the page you just asked for. + useEffect(() => { + setOpen(false); + }, [pathname]); + + function close() { + setOpen(false); + buttonRef.current?.focus(); + } + + return ( +
+ + + +
+
+ + +
+ Vantage + {instance && ( + {instance.name} + )} +
+
+ +
+ + {children} +
+
+
+ ); +} diff --git a/web/components/Sidebar.tsx b/web/components/Sidebar.tsx index 6ab5408..215eefc 100644 --- a/web/components/Sidebar.tsx +++ b/web/components/Sidebar.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useEffect, useRef } from "react"; import { clsx } from "clsx"; import { useAuth } from "@/components/AuthProvider"; import { auth } from "@/lib/api"; @@ -132,7 +133,8 @@ const navItems: NavItem[] = [ { href: "/settings", label: "Settings", icon: , adminOnly: true }, ]; -export function Sidebar() { +/** Shared by the permanent aside and the offcanvas drawer — one copy of the nav. */ +export function SidebarContent({ onNavigate }: { onNavigate?: () => void }) { const pathname = usePathname(); const { user, instance, isAdmin } = useAuth(); @@ -152,8 +154,8 @@ export function Sidebar() { } return ( -