diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e488e28..af68dea 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -8,6 +8,8 @@ import { Project } from '@/pages/Project'; import { Session } from '@/pages/Session'; import { Toaster } from '@/components/ui/sonner'; import { useUserEvents } from '@/hooks/useUserEvents'; +import { SidebarDrawerProvider, useSidebarDrawer } from '@/hooks/useSidebarDrawer'; +import { useViewport } from '@/hooks/useViewport'; function SessionRightRail() { const { id } = useParams<{ id: string }>(); @@ -27,11 +29,25 @@ function RightRailForSession({ sessionId }: { sessionId: string }) { return ; } +function MobileBackdrop() { + const { open, setOpen } = useSidebarDrawer(); + const { isMobile } = useViewport(); + if (!isMobile || !open) return null; + return ( +
setOpen(false)} + aria-hidden="true" + /> + ); +} + function AppShell() { useUserEvents(); return (
+
} /> @@ -50,7 +66,9 @@ function AppShell() { export default function App() { return ( - + + + ); } diff --git a/apps/web/src/components/ProjectSidebar.tsx b/apps/web/src/components/ProjectSidebar.tsx index 8c9159f..9d26f2c 100644 --- a/apps/web/src/components/ProjectSidebar.tsx +++ b/apps/web/src/components/ProjectSidebar.tsx @@ -20,6 +20,8 @@ import { import { AddProjectModal } from './AddProjectModal'; import { api } from '@/api/client'; import { useSidebar } from '@/hooks/useSidebar'; +import { useSidebarDrawer } from '@/hooks/useSidebarDrawer'; +import { useViewport } from '@/hooks/useViewport'; import type { SidebarProject } from '@/api/types'; import { giteaUrlFor } from '@/lib/projectUrls'; import { cn } from '@/lib/utils'; @@ -195,8 +197,23 @@ export function ProjectSidebar() { const rowCls = (active: boolean) => active ? 'bg-sidebar-accent text-sidebar-accent-foreground' : 'hover:bg-sidebar-accent/60'; + const { open: drawerOpen } = useSidebarDrawer(); + const { isMobile } = useViewport(); + + // On mobile the sidebar is a slide-in drawer (fixed, z-40, off-screen by + // default). On desktop it sits inline as a normal flex column. The + // backdrop is rendered by AppShell; drawer-open state lives in + // SidebarDrawerProvider. + const asideCls = isMobile + ? cn( + 'fixed inset-y-0 left-0 z-40 w-60 border-r bg-sidebar text-sidebar-foreground flex flex-col', + 'transition-transform duration-200 ease-out', + drawerOpen ? 'translate-x-0' : '-translate-x-full', + ) + : 'w-60 shrink-0 border-r bg-sidebar text-sidebar-foreground flex flex-col h-screen'; + return ( -