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>
24 lines
881 B
TypeScript
24 lines
881 B
TypeScript
import { NavLink } from 'react-router'
|
|
import { Icon } from '../ui/Icon.tsx'
|
|
import { navItems } from './navItems.ts'
|
|
import styles from './BottomBar.module.css'
|
|
|
|
// Mobile nav: a dark pill bar floating over the bottom of the screen. Inactive
|
|
// items show only their icon; the active item adds its label on a grape pill.
|
|
export function BottomBar() {
|
|
return (
|
|
<nav aria-label="Primary" className={styles.bar}>
|
|
{navItems.map((item) => (
|
|
<NavLink key={item.to} to={item.to} end={item.end} className={styles.item}>
|
|
{({ isActive }) => (
|
|
<span className={isActive ? `${styles.pill} ${styles.active}` : styles.pill}>
|
|
<Icon icon={item.icon} size="nav" />
|
|
<span className={isActive ? styles.label : styles.hiddenLabel}>{item.label}</span>
|
|
</span>
|
|
)}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
)
|
|
}
|