import type { HTMLAttributes, ReactNode } from 'react'import './navigation.css'export interface NavigationProps extends HTMLAttributes<HTMLElement> { /** Icons only, 72px wide. Open, it's 160px with labels. */ readonly collapsed?: boolean /** The mark at the top, 40px. */ readonly logo?: ReactNode /** One action at the bottom: an IconButton when collapsed, a Button when open. */ readonly action?: ReactNode /** The items: ListItems, icon only when collapsed. */ readonly children?: ReactNode /** Say what it is: "Main". */ readonly 'aria-label': string}const b = 'rost-navigation'/** * A side navigation on frosted glass: the logo, the items, one action. * Built from contracts/navigation.contract.yaml. */export function Navigation({ collapsed = false, logo, action, children, className, ...rest }: NavigationProps) { return ( <nav className={[b, collapsed && `${b}--collapsed`, className].filter(Boolean).join(' ')} {...rest}> <span className={`${b}__background`} /> {logo && <span className={`${b}__logo`}>{logo}</span>} <div className={`${b}__main`}>{children}</div> {action && <div className={`${b}__action`}>{action}</div>} </nav> )}
When to use
Used as the main navigation of an app, down the side: a few top-level places, and one action that matters everywhere.
When not to use
More than a handful of items. It's for the top level only.
A website's header nav. That runs across the top.
Behaviour
collapsed is icons only, 72px wide; open is 160px with labels. The items are ListItems: the current one is selected. The action is an IconButton when collapsed and a Button when open.
Accessibility
Element: a <nav> with an aria-label. The current item has aria-current="page".
Name: the aria-label on the <nav>, like "Main". Each item is named by its label.
Collapsed: icon-only items need an aria-label, since there's no visible text.
Keyboard: Tab goes through the items in order, then the action. Nothing custom, since they're links and a button.
Focus: each ListItem's own focus-ring.
Contrast: the plate is see-through, so check the labels against what's behind it, not just the page colour.
Content
One or two words per item, nouns. The action says what it does.
Props
Prop
Type
Values
Default
collapsed
boolean
false
logo
slot
main
slot
action
slot
Values
Straight from the contract. Each value sits next to its token, because six tokens here all come out as #3a3a3a and the colour alone can't tell you which one is right.