From 91893363ade072e3bc0a0eb69ebd0cc841648e09 Mon Sep 17 00:00:00 2001 From: Daniel Esteves Date: Sun, 8 Nov 2026 15:48:41 -0400 Subject: [PATCH 1/7] feat(web/nav-rail): hold a whole screen, not only a panel The rail can now sit beside a page as its navigation. NavRail.Provider owns the fold for the shell, so a trigger in the page header and the one in the rail agree, the saved preference is read once, and the command or control shortcut works outside a field. Below the breakpoint the same rail is a drawer and the page keeps the full width. NavRail.Content gives the destinations a scrolling middle while the header and the footer stay against their edges. NavRail.Rail is the grab handle on the outer edge, and NavRail.Inset is the page beside the rail. Folding can leave icons, leave the layout, or be disabled, and the rail can sit on either edge. Folded labels and flyouts are placed against the viewport, so the new scroll container no longer clips them. Co-Authored-By: Claude Opus 5.5 --- .../components/web-demos/index.generated.tsx | 1 + .../components/web-demos/nav-rail/shell.tsx | 116 ++++ .../content/docs/web/components/nav-rail.mdx | 463 +++++++++++---- apps/docs/public/r/web/nav-rail.json | 6 +- apps/docs/public/r/web/registry.json | 4 +- .../skills/panelui-web/components/nav-rail.md | 340 ++++++----- apps/docs/scripts/web-usage/nav-rail.json | 34 +- .../web/src/components/nav-rail/index.tsx | 542 ++++++++++++++++-- packages/web/src/index.ts | 12 + skills/panelui-web/components/nav-rail.md | 340 ++++++----- 10 files changed, 1426 insertions(+), 432 deletions(-) create mode 100644 apps/docs/components/web-demos/nav-rail/shell.tsx diff --git a/apps/docs/components/web-demos/index.generated.tsx b/apps/docs/components/web-demos/index.generated.tsx index d7724155..fabad053 100644 --- a/apps/docs/components/web-demos/index.generated.tsx +++ b/apps/docs/components/web-demos/index.generated.tsx @@ -271,6 +271,7 @@ export const WEB_DEMOS: Record = { 'nav-rail/nested': dynamic(() => import('./nav-rail/nested')), 'nav-rail/nested-folded': dynamic(() => import('./nav-rail/nested-folded')), 'nav-rail/overview': dynamic(() => import('./nav-rail/overview')), + 'nav-rail/shell': dynamic(() => import('./nav-rail/shell')), 'nav-rail/thumb': dynamic(() => import('./nav-rail/thumb')), 'nav-rail/variants': dynamic(() => import('./nav-rail/variants')), 'not-found/dissolve': dynamic(() => import('./not-found/dissolve')), diff --git a/apps/docs/components/web-demos/nav-rail/shell.tsx b/apps/docs/components/web-demos/nav-rail/shell.tsx new file mode 100644 index 00000000..ea848884 --- /dev/null +++ b/apps/docs/components/web-demos/nav-rail/shell.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { useState } from 'react'; +import { + BellIcon, + CreditCardIcon, + FolderKanbanIcon, + InboxIcon, + LayoutDashboardIcon, + LayersIcon, + LifeBuoyIcon, + SettingsIcon, + UsersIcon, +} from 'lucide-react'; +import { NavRail } from 'panelui-web/nav-rail'; + +const ITEMS = [ + { id: 'overview', label: 'Overview', icon: LayoutDashboardIcon, note: 'This week across the studio.' }, + { id: 'inbox', label: 'Inbox', icon: InboxIcon, count: 12, note: 'Twelve conversations are waiting on you.' }, + { id: 'projects', label: 'Projects', icon: FolderKanbanIcon, count: 4, note: 'Four projects are in review this morning.' }, + { id: 'people', label: 'People', icon: UsersIcon, note: 'Eight collaborators, two invitations out.' }, + { id: 'billing', label: 'Billing', icon: CreditCardIcon, note: 'The studio plan renews on 1 December.' }, +]; + +export default function NavRailShell() { + const [active, setActive] = useState('overview'); + const current = ITEMS.find((item) => item.id === active) ?? ITEMS[0]!; + + return ( + +
+ + + }>Meridian + + + + + + {ITEMS.map((item) => { + const Icon = item.icon; + return ( + } + onClick={(event) => { + event.preventDefault(); + setActive(item.id); + }} + > + {item.label} + {item.count ? {item.count} : null} + + ); + })} + + + + } onClick={(event) => event.preventDefault()}> + Settings + + + + + + + } label="Help and support"> + Help and support + + + + + + + +
+ +

Meridian

+
+ +
+
+
+

+ Thursday, 6 November +

+

{current.label}

+
+ +
+ +

{current.note}

+ +
+
+

Open reviews

+

07

+
+
+

Time saved

+

+ 6.4h +

+
+
+
+
+
+
+ ); +} diff --git a/apps/docs/content/docs/web/components/nav-rail.mdx b/apps/docs/content/docs/web/components/nav-rail.mdx index ef0c8cbd..f736b0f8 100644 --- a/apps/docs/content/docs/web/components/nav-rail.mdx +++ b/apps/docs/content/docs/web/components/nav-rail.mdx @@ -2,8 +2,289 @@ title: Nav Rail description: A collapsible sidebar of destinations that keeps one active pill sliding between rows, and folds to a column of icons with tooltips. status: new +full: true --- + + +```tsx lineNumbers +'use client'; + +import { useState } from 'react'; +import { + BellIcon, + CreditCardIcon, + FolderKanbanIcon, + InboxIcon, + LayoutDashboardIcon, + LayersIcon, + LifeBuoyIcon, + SettingsIcon, + UsersIcon, +} from 'lucide-react'; +import { NavRail } from '@/components/panelui/nav-rail'; + +const ITEMS = [ + { id: 'overview', label: 'Overview', icon: LayoutDashboardIcon, note: 'This week across the studio.' }, + { id: 'inbox', label: 'Inbox', icon: InboxIcon, count: 12, note: 'Twelve conversations are waiting on you.' }, + { id: 'projects', label: 'Projects', icon: FolderKanbanIcon, count: 4, note: 'Four projects are in review this morning.' }, + { id: 'people', label: 'People', icon: UsersIcon, note: 'Eight collaborators, two invitations out.' }, + { id: 'billing', label: 'Billing', icon: CreditCardIcon, note: 'The studio plan renews on 1 December.' }, +]; + +export default function NavRailShell() { + const [active, setActive] = useState('overview'); + const current = ITEMS.find((item) => item.id === active) ?? ITEMS[0]!; + + return ( + +
+ + + }>Meridian + + + + + + {ITEMS.map((item) => { + const Icon = item.icon; + return ( + } + onClick={(event) => { + event.preventDefault(); + setActive(item.id); + }} + > + {item.label} + {item.count ? {item.count} : null} + + ); + })} + + + + } onClick={(event) => event.preventDefault()}> + Settings + + + + + + + } label="Help and support"> + Help and support + + + + + + + +
+ +

Meridian

+
+ +
+
+
+

+ Thursday, 6 November +

+

{current.label}

+
+ +
+ +

{current.note}

+ +
+
+

Open reviews

+

07

+
+
+

Time saved

+

+ 6.4h +

+
+
+
+
+
+
+ ); +} +``` + +
+ +Use it for the destinations of a product that should stay visible while the work beside them changes: the sections of a studio, the parts of an editor, the pages of an account. It is a `