- useViewport: matchMedia-based hook (no resize polling). Breakpoints mobile <768 / tablet 768-1023 / desktop >=1024. SSR-safe. - useSidebarDrawer: Context provider with open/setOpen/toggle + auto-close on useLocation().pathname change. - App.tsx: wraps SidebarDrawerProvider around AppShell, renders a MobileBackdrop (z-30) when the drawer is open on mobile. - ProjectSidebar: aside is fixed/translate-x-full off-screen on mobile, slides in (z-40, 200ms transform) when drawerOpen. Inline column on desktop, unchanged. - Session.tsx + Project.tsx: hamburger (Menu icon, >=44x44 min) on mobile opens the drawer. Headers gain paddingTop: max(0.75rem, env(safe-area-inset-top)) for notch devices. Home.tsx left alone (sidebar content duplicates the home page). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { BrowserRouter, Routes, Route, useParams } from 'react-router-dom';
|
|
import { api } from '@/api/client';
|
|
import { ProjectSidebar } from '@/components/ProjectSidebar';
|
|
import { RightRail } from '@/components/RightRail';
|
|
import { Home } from '@/pages/Home';
|
|
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 }>();
|
|
if (!id) return null;
|
|
return <RightRailForSession sessionId={id} />;
|
|
}
|
|
|
|
function RightRailForSession({ sessionId }: { sessionId: string }) {
|
|
const [projectId, setProjectId] = useState<string | null>(null);
|
|
useEffect(() => {
|
|
api.sessions
|
|
.get(sessionId)
|
|
.then((s) => setProjectId(s.project_id))
|
|
.catch((err) => console.warn('RightRail: failed to fetch session', err));
|
|
}, [sessionId]);
|
|
if (!projectId) return null;
|
|
return <RightRail projectId={projectId} />;
|
|
}
|
|
|
|
function MobileBackdrop() {
|
|
const { open, setOpen } = useSidebarDrawer();
|
|
const { isMobile } = useViewport();
|
|
if (!isMobile || !open) return null;
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/40 md:hidden"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function AppShell() {
|
|
useUserEvents();
|
|
return (
|
|
<div className="dark h-screen flex bg-background text-foreground">
|
|
<ProjectSidebar />
|
|
<MobileBackdrop />
|
|
<main className="flex-1 flex flex-col min-w-0">
|
|
<Routes>
|
|
<Route path="/" element={<Home />} />
|
|
<Route path="/project/:id" element={<Project />} />
|
|
<Route path="/session/:id" element={<Session />} />
|
|
</Routes>
|
|
</main>
|
|
<Routes>
|
|
<Route path="/session/:id" element={<SessionRightRail />} />
|
|
</Routes>
|
|
<Toaster position="bottom-right" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<SidebarDrawerProvider>
|
|
<AppShell />
|
|
</SidebarDrawerProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|