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}
)
}