Push Menu
nswds-ui/push-menuFreeComponent
Multi-level drill-down navigation menu with slide transitions, focus management and inert parked levels, for mobile drawers.
Install it
npx shadcn@latest add https://raw.githubusercontent.com/digitalnsw/nswds-ui/main/apps/registry/public/r/push-menu.jsonSource
1'use client'23import React from 'react'45import { cn } from '@/lib/utils'67import { Button } from '@/components/button'8import { Link } from '@/components/link'9import { IconChevronRight } from '@/icons/chevron-right'10import { IconClose } from '@/icons/close'11import { IconWest } from '@/icons/west'1213/**14 * Default slide duration for level transitions, in milliseconds — the default15 * of the `durationMs` prop.16 *17 * COUPLING: `durationMs` drives BOTH the `--push-menu-duration` custom18 * property (set inline on the root, read by the CSS transition) and the state19 * machine's `setTimeout`s (which settle the animation state, pop levels, and20 * schedule focus restoration). Do not override the custom property directly —21 * that changes only the visual duration while the state machine still settles22 * on `durationMs`, desyncing the two; pass `durationMs` instead. Under23 * `prefers-reduced-motion` the slide is skipped (the transition is applied24 * under `motion-safe:` only) but the state machine still waits the full25 * duration; the menu is simply "settled early" for those users, never broken.26 */27const PUSH_MENU_DURATION_MS = 3002829/** A node in the menu tree. Items with `links` drill in; items with `href` navigate. */30type PushMenuItem = {31 /** Unique across the whole tree — level ids and focus restoration track items by id. */32 id: string33 /** Visible label. */34 title: string35 /** Navigation target for leaf items. Ignored for drilling when `links` is non-empty. */36 href?: string37 /** Child items. A non-empty array makes this item a drill-in button, not a link. */38 links?: PushMenuItem[]39}4041/** One entry in the level stack — the root level plus one per drill-in. */42type PushMenuLevel = {43 id: string44 title: string45 /** 1 for the root level, incrementing per drill-in. */46 depth: number47 items: PushMenuItem[]48 /** The item whose activation opened this level. Absent on the root level. */49 parentItem?: PushMenuItem50}5152type PushMenuAnimationState = 'idle' | 'sliding-forward' | 'sliding-backward'5354function buildLevelId(path: string[]): string {55 return `level-${path.length > 0 ? path.join('--') : 'root'}`56}5758/**59 * Truncates to at most `maxLength` UTF-16 units, ellipsis included in the60 * budget.61 *62 * Iterates CODE POINTS (`for…of` over a string) rather than slicing by index:63 * a plain `slice` can cut between the two halves of a surrogate pair, leaving64 * a lone surrogate that renders as U+FFFD (an emoji in a level title was65// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from @nswds/ui
All 46 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aspect Ratioaspect-ratio | @nswds/ui | Components | Free | 2 deps · 2 files | |
| Badgebadge | @nswds/ui | Components | Free | 4 deps · 2 files | |
| Buttonbutton | @nswds/ui | Components | Free | 4 deps · 2 files | |
| Cardcard | @nswds/ui | Components | Free | 2 deps · 2 files | |
| Collapsiblecollapsible | @nswds/ui | Components | Free | 1 dep | |
| Drawerdrawer | @nswds/ui | Components | Free | 3 deps · 2 files | |
| Expandable Searchexpandable-search | @nswds/ui | Components | Free | 3 deps · 2 files | |
| Fieldfield | @nswds/ui | Components | Free | 4 deps · 2 files |
