Files
boocode/apps/web/src/App.tsx
indifferentketchup 5932682193 feat(mobile): right-rail as drawer on mobile, header toggle button
Reverts v1.6.1's max-md:hidden wrapper around RightRail. On mobile,
RightRail now renders as a fixed right-side drawer (w-[85vw],
max-w-sm) toggled by a new FolderTree button in the Session header.

- New useRightRailDrawer hook mirrors useSidebarDrawer (Context +
  auto-close on route change).
- New MobileRightRailBackdrop component in App.tsx mirrors the
  existing MobileBackdrop for the left sidebar.
- RightRail computes an isOpen synthesis: on mobile, reads the
  drawer Context; on desktop, reads the persistent internal state.
  The existing tree-load effect and open_file_in_browser
  subscription share this plumbing via openRail / closeRail
  helpers.
- The desktop floating chevron handle is hidden on mobile (the
  Session header's FolderTree button replaces it).
- Session header gains a mobile-only FolderTree button after the
  ModelPicker, calling toggle() on the drawer Context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:37:13 +00:00

96 lines
3.0 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 { RightRailDrawerProvider, useRightRailDrawer } from '@/hooks/useRightRailDrawer';
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;
// v1.6.2: rendered on all viewports. On mobile, RightRail itself renders as
// a right-side drawer toggled by the header's FolderTree button (via
// useRightRailDrawer). On desktop, it renders inline as before with its
// own internal open/close state.
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 MobileRightRailBackdrop() {
const { open, setOpen } = useRightRailDrawer();
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>
<MobileRightRailBackdrop />
<Routes>
<Route path="/session/:id" element={<SessionRightRail />} />
</Routes>
<Toaster position="bottom-right" />
</div>
);
}
export default function App() {
return (
<BrowserRouter>
<SidebarDrawerProvider>
<RightRailDrawerProvider>
<AppShell />
</RightRailDrawerProvider>
</SidebarDrawerProvider>
</BrowserRouter>
);
}