import { createSignal, Switch, Match, For } from "@solidrt/core" import type { LayoutProps } from "@solidrt/core" import { createPress } from "./press" import { theme } from "./theme" import { policy } from "./policy" import { space } from "./spacing" import { typeStyle } from "./typography" export interface NavItem { value: unknown label: string // Optional icon content, rendered as-is above (tabs/rail) or beside // (sidebar) the label. icon?: any } export interface NavShellProps { items: NavItem[] // Controlled selected value. If omitted, the shell is uncontrolled. value?: unknown defaultValue?: unknown onChange?: (value: unknown) => void // The page content; keeps its node (and state) when the arrangement changes. children?: any layout?: LayoutProps } const RAIL_WIDTH = 72 const SIDEBAR_WIDTH = 220 /** * An app shell that arranges primary navigation around the content per the * navigation policy: bottom tabs under it, a narrow rail or a wide sidebar * beside it. The content is a single stable node; switching arrangement only * flips the shell's flex direction and remounts the (stateless) nav strip, so * page state survives a resize across a breakpoint. Safe areas are the * caller's concern: wrap the shell (or the window content) in SafeArea. */ export function NavShell(props: NavShellProps) { let [internal, setInternal] = createSignal(props.defaultValue) let value = () => (props.value !== undefined ? props.value : internal()) let select = (v: unknown) => { if (props.value === undefined) setInternal(() => v) props.onChange?.(v) } let labelColor = (item: NavItem) => (item.value === value() ? theme.color.primary : theme.color.textMuted) let itemBg = (item: NavItem, hovered: boolean) => item.value === value() ? theme.color.surfaceAlt : hovered && policy.interaction !== "touch" ? theme.color.overlayHover : "transparent" // Icon over a small label, centered; shared by the tab bar and the rail. let StackedItem = (p: { item: NavItem; padY: number; layout?: LayoutProps }) => { let press = createPress({ onPress: () => select(p.item.value) }) return ( {p.item.icon} {p.item.label} ) } let Hairline = (p: { vertical?: boolean }) => ( ) let Tabs = () => ( {(item: NavItem) => } ) let Rail = () => ( {(item: NavItem) => } ) let Sidebar = () => ( {(item: NavItem) => { let press = createPress({ onPress: () => select(item.value) }) return ( {item.icon} {item.label} ) }} ) // Children order is (content, nav): "column" puts the nav under the content, // "row-reverse" puts it to the left, and the content node never moves. return ( {props.children} ) }