"use client"; import { usePathname } from "next/navigation"; import { useMobileNavigationItems, type MobileNavigationItem } from "../../contexts/MobileNavigationContext"; import { Link } from "../../shadcnui"; import { cn } from "../../utils/cn"; import { useIsMobile } from "../../utils/use-mobile"; /** * Active only for navigational slots. * * Exact-or-prefix, NOT a bare `startsWith`: the prefix arm keeps a detail route * (/accounts/123) highlighting its section (/accounts). * * "/" is special-cased to EXACT match. Without that, its prefix arm degenerates * to `pathname.startsWith("/")`, which is true for every route, and the Home * slot highlights everywhere. This is pinned by a unit test in Task C. */ function isActiveHref(pathname: string, href: string): boolean { if (href === "/") return pathname === "/"; return pathname === href || pathname.startsWith(`${href}/`); } const slotClass = cn( // min-h-12/min-w-12 = 48px touch target (Material/HIG guidance). "relative flex min-h-12 min-w-12 flex-1 items-center justify-center", "transition-colors", ); function SlotContent({ item }: { item: MobileNavigationItem }) { if (item.render) return <>{item.render}; const Icon = item.icon; return Icon ? : null; } /** * The mobile bottom navigation bar. * * Renders nothing on desktop, and nothing when the application supplied no * items — so page containers can render it unconditionally. * * Rendered as an in-flow flex sibling of the page content (NOT position:fixed), * so the content column shrinks to fit and can never be hidden behind the bar. */ export function MobileNavigationBar() { const items = useMobileNavigationItems(); const isMobile = useIsMobile(); const pathname = usePathname(); if (!isMobile || items.length === 0) return null; // A detached surface sitting BELOW the page card with a gap, so it carries a // rounded border rather than a `border-t`, which would read as if the bar were // welded to the card's bottom edge. `bg-sidebar` deliberately matches the // Header's own background so the two chrome edges of the viewport agree. // // Deliberately NO bottom safe-area padding here, and none on the page shell // either. iOS reports a 34px bottom inset but paints nothing in that strip on // current devices, so honouring it only produced dead space — 78px of bar for // a 42px icon row when held inside this border, and an empty band under the // bar when held by the shell instead. return ( ); }