49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { NavLink, Outlet } from "react-router";
|
|
import { SCREENS } from "../routes";
|
|
|
|
export interface LayoutProps {
|
|
/** Shown at the top right; live values arrive with the player state and clock steps. */
|
|
credits?: ReactNode;
|
|
clock?: ReactNode;
|
|
}
|
|
|
|
/** Header with credits and game clock, the current screen, and a bottom tab bar. */
|
|
export function Layout({ credits = "—", clock = "—" }: LayoutProps) {
|
|
return (
|
|
<div className="flex min-h-dvh flex-col bg-slate-950 text-slate-100">
|
|
<header className="sticky top-0 z-10 flex items-center justify-between gap-2 border-b border-slate-800 bg-slate-950/95 px-4 py-2">
|
|
<span className="truncate text-sm font-bold tracking-wide">DEADHEAD</span>
|
|
<div className="flex shrink-0 items-center gap-3 text-sm tabular-nums">
|
|
<span aria-label="Credits">{credits}</span>
|
|
<span aria-label="Game clock" className="text-slate-400">
|
|
{clock}
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="flex-1 overflow-x-hidden pb-20">
|
|
<Outlet />
|
|
</main>
|
|
|
|
<nav
|
|
aria-label="Main"
|
|
className="fixed inset-x-0 bottom-0 grid grid-cols-6 border-t border-slate-800 bg-slate-950 pb-[env(safe-area-inset-bottom)]"
|
|
>
|
|
{SCREENS.map((screen) => (
|
|
<NavLink
|
|
key={screen.path}
|
|
to={screen.path}
|
|
end={screen.path === "/"}
|
|
className={({ isActive }) =>
|
|
`truncate px-1 py-3 text-center text-xs ${isActive ? "font-semibold text-amber-400" : "text-slate-400"}`
|
|
}
|
|
>
|
|
{screen.label}
|
|
</NavLink>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
);
|
|
}
|