Replace the catch-all placeholder with an AppShell that renders one primary nav per breakpoint (chosen with matchMedia via useBreakpoint): a dark pill bottom bar below 768 px, a 96 px rail with the "M" mark at 768-1199 px, and a 208 px sidebar with the MealQuest wordmark and the "Our kitchen / Shared household" card at 1200 px and up. Nav order is Home, Plan, Discover, Quick, Settings; NavLink marks the active item aria-current="page", with nested Plan and Settings paths keeping their parent current. The mobile scroll area reserves 100 px plus the safe area beneath its content. Register every handoff route plus /settings/household, /settings/regulars and /settings/calendar, each with a placeholder heading. Remove the default body margin so the shell fills the viewport. Tests force each breakpoint with a matchMedia mock and assert the routes, nav order, each layout's structure, aria-current and the mobile bottom padding from the stylesheet. Maestro task: 42a4695a-6bd0-49a1-925e-387858dbee48 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 lines
741 B
TypeScript
23 lines
741 B
TypeScript
import { Outlet } from 'react-router'
|
|
import { BottomBar } from './BottomBar.tsx'
|
|
import { Rail } from './Rail.tsx'
|
|
import { Sidebar } from './Sidebar.tsx'
|
|
import { useBreakpoint } from './useBreakpoint.ts'
|
|
import styles from './AppShell.module.css'
|
|
|
|
// The responsive frame around every page: one nav layout per breakpoint and a
|
|
// scrolling main area for the routed page.
|
|
export function AppShell() {
|
|
const breakpoint = useBreakpoint()
|
|
return (
|
|
<div className={`${styles.shell} ${styles[breakpoint]}`}>
|
|
{breakpoint === 'mobile' && <BottomBar />}
|
|
{breakpoint === 'tablet' && <Rail />}
|
|
{breakpoint === 'desktop' && <Sidebar />}
|
|
<main className={styles.main}>
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|