Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/docs/components/web-demos/index.generated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,14 @@ export const WEB_DEMOS: Record<string, ComponentType> = {
'model-picker/prompt': dynamic(() => import('./model-picker/prompt')),
'model-picker/rail': dynamic(() => import('./model-picker/rail')),
'model-picker/thumb': dynamic(() => import('./model-picker/thumb')),
'nav-rail/collapsed': dynamic(() => import('./nav-rail/collapsed')),
'nav-rail/controlled': dynamic(() => import('./nav-rail/controlled')),
'nav-rail/groups': dynamic(() => import('./nav-rail/groups')),
'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/thumb': dynamic(() => import('./nav-rail/thumb')),
'nav-rail/variants': dynamic(() => import('./nav-rail/variants')),
'not-found/dissolve': dynamic(() => import('./not-found/dissolve')),
'not-found/outline': dynamic(() => import('./not-found/outline')),
'not-found/page': dynamic(() => import('./not-found/page')),
Expand Down
87 changes: 87 additions & 0 deletions apps/docs/components/web-demos/nav-rail/collapsed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client';

import {
BellIcon,
CreditCardIcon,
FolderKanbanIcon,
InboxIcon,
LayoutDashboardIcon,
LayersIcon,
LifeBuoyIcon,
SettingsIcon,
UsersIcon,
} from 'lucide-react';
import { NavRail } from 'panelui-web/nav-rail';

const stop = (event: { preventDefault(): void }) => event.preventDefault();

const ACTIVITY = [
{ label: 'Inbox', detail: '12 unread', icon: BellIcon },
{ label: 'Projects', detail: '4 in review', icon: FolderKanbanIcon },
{ label: 'People', detail: '8 collaborators', icon: UsersIcon },
];

export default function NavRailCollapsed() {
return (
<div className="flex h-[450px] w-full max-w-2xl items-stretch gap-2 rounded-2xl border border-border/70 bg-background p-2">
<NavRail aria-label="Meridian" defaultCollapsed>
<NavRail.Header>
<NavRail.Brand icon={<LayersIcon />}>Meridian</NavRail.Brand>
<NavRail.Trigger />
</NavRail.Header>

<NavRail.Group label="Workspace">
<NavRail.Item href="#" icon={<LayoutDashboardIcon />} label="Overview" onClick={stop} />
<NavRail.Item href="#" active icon={<InboxIcon />} label="Inbox" onClick={stop}>
Inbox <NavRail.Badge>12</NavRail.Badge>
</NavRail.Item>
<NavRail.Item href="#" icon={<FolderKanbanIcon />} label="Projects" onClick={stop}>
Projects <NavRail.Badge>4</NavRail.Badge>
</NavRail.Item>
<NavRail.Item href="#" icon={<UsersIcon />} label="People" onClick={stop} />
</NavRail.Group>

<NavRail.Group label="Account">
<NavRail.Item href="#" icon={<CreditCardIcon />} label="Billing" onClick={stop} />
<NavRail.Item href="#" icon={<SettingsIcon />} label="Settings" onClick={stop} />
</NavRail.Group>

<NavRail.Footer>
<NavRail.Item href="#" icon={<LifeBuoyIcon />} label="Help and support" onClick={stop} />
</NavRail.Footer>
</NavRail>

<div className="flex min-w-0 flex-1 flex-col gap-4 rounded-xl bg-muted/30 p-5">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-[11px] font-semibold tracking-[0.09em] text-muted-foreground uppercase">
Folded rail
</p>
<h3 className="mt-1 text-xl font-semibold tracking-[-0.02em]">Everything still one press away</h3>
</div>
<span className="rounded-full bg-background px-2.5 py-1 text-[11px] font-medium text-muted-foreground ring-1 ring-border">
56 px
</span>
</div>

<p className="max-w-md text-sm leading-6 text-muted-foreground">
The labels are folded away, so each row keeps its accessible name and shows it in a flyout on hover or focus.
Group headings become dividers, and the two counts become dots on the corner of their icons.
</p>

<div className="flex flex-col gap-2">
{ACTIVITY.map(({ label, detail, icon: Icon }) => (
<div
key={label}
className="flex items-center gap-3 rounded-xl bg-background px-3 py-2.5 ring-1 ring-border/70"
>
<Icon className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
<span className="text-sm font-medium">{label}</span>
<span className="ms-auto text-xs text-muted-foreground">{detail}</span>
</div>
))}
</div>
</div>
</div>
);
}
117 changes: 117 additions & 0 deletions apps/docs/components/web-demos/nav-rail/controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use client';

import { useState } from 'react';
import {
FolderKanbanIcon,
InboxIcon,
LayoutDashboardIcon,
LayersIcon,
LifeBuoyIcon,
SettingsIcon,
UsersIcon,
} from 'lucide-react';
import { NavRail } from 'panelui-web/nav-rail';

const PAGES = [
{ id: 'overview', label: 'Overview', icon: LayoutDashboardIcon, count: 0 },
{ id: 'inbox', label: 'Inbox', icon: InboxIcon, count: 8 },
{ id: 'projects', label: 'Projects', icon: FolderKanbanIcon, count: 0 },
{ id: 'people', label: 'People', icon: UsersIcon, count: 0 },
];

export default function NavRailControlled() {
const [collapsed, setCollapsed] = useState(false);
const [page, setPage] = useState('inbox');

return (
<div className="flex h-[400px] w-full max-w-2xl items-stretch gap-2 rounded-2xl border border-border/70 bg-background p-2">
<NavRail aria-label="Meridian" collapsed={collapsed} onCollapsedChange={setCollapsed}>
<NavRail.Header>
<NavRail.Brand icon={<LayersIcon />}>Meridian</NavRail.Brand>
<NavRail.Trigger />
</NavRail.Header>

<NavRail.Group label="Workspace">
{PAGES.map((item) => {
const Icon = item.icon;
return (
<NavRail.Item
key={item.id}
href="#"
active={page === item.id}
icon={<Icon />}
onClick={(event) => {
event.preventDefault();
setPage(item.id);
}}
>
{item.label}
{item.count ? <NavRail.Badge>{item.count}</NavRail.Badge> : null}
</NavRail.Item>
);
})}
</NavRail.Group>

<NavRail.Footer>
<NavRail.Item href="#" icon={<SettingsIcon />} label="Settings" onClick={(event) => event.preventDefault()} />
<NavRail.Item
href="#"
icon={<LifeBuoyIcon />}
label="Help and support"
onClick={(event) => event.preventDefault()}
/>
</NavRail.Footer>
</NavRail>

<div className="flex min-w-0 flex-1 flex-col gap-4 rounded-xl bg-muted/30 p-5">
<div>
<p className="text-[11px] font-semibold tracking-[0.09em] text-muted-foreground uppercase">Controlled</p>
<h3 className="mt-1 text-xl font-semibold tracking-[-0.02em]">The app holds the state</h3>
</div>

<p className="max-w-md text-sm leading-6 text-muted-foreground">
Pass <code className="rounded bg-background px-1 py-0.5 text-[12px] ring-1 ring-border">collapsed</code> and{' '}
<code className="rounded bg-background px-1 py-0.5 text-[12px] ring-1 ring-border">onCollapsedChange</code>{' '}
to decide the fold yourself, from a media query or a stored preference. The trigger inside the rail reports
through the same pair.
</p>

<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => setCollapsed((value) => !value)}
className="inline-flex h-9 items-center justify-center rounded-lg bg-primary px-3.5 text-sm font-medium text-primary-foreground outline-none transition-[background-color,scale] hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:scale-[0.98]"
>
{collapsed ? 'Open the rail' : 'Fold the rail'}
</button>
<span className="rounded-lg bg-background px-2.5 py-1.5 font-mono text-[11px] text-muted-foreground ring-1 ring-border">
collapsed: {String(collapsed)}
</span>
</div>

<div className="mt-auto rounded-xl bg-background p-4 ring-1 ring-border/70">
<p className="text-[11px] font-medium text-muted-foreground">Also controlled</p>
<div className="mt-2 flex flex-wrap gap-1.5">
{PAGES.map((item) => (
<button
key={item.id}
type="button"
onClick={() => setPage(item.id)}
className={`inline-flex h-8 items-center rounded-md px-2.5 text-xs font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring ${
page === item.id
? 'bg-foreground text-background'
: 'bg-muted text-muted-foreground hover:text-foreground'
}`}
>
{item.label}
</button>
))}
</div>
<p className="mt-3 text-xs text-muted-foreground">
The active row and the buttons here read the same <code className="font-mono text-[11px]">page</code> state.
</p>
</div>
</div>
</div>
);
}
94 changes: 94 additions & 0 deletions apps/docs/components/web-demos/nav-rail/groups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client';

import {
ChartNoAxesColumnIcon,
CreditCardIcon,
FolderKanbanIcon,
InboxIcon,
LayoutDashboardIcon,
LayersIcon,
SettingsIcon,
} from 'lucide-react';
import { NavRail } from 'panelui-web/nav-rail';

const stop = (event: { preventDefault(): void }) => event.preventDefault();

const REPORTS = [
{ name: 'Weekly studio pulse', owner: 'Ines', change: '+12%' },
{ name: 'Review turnaround', owner: 'Ravi', change: '−3%' },
{ name: 'Client margin', owner: 'Marta', change: '+6%' },
];

export default function NavRailGroups() {
return (
<div className="flex h-[420px] w-full max-w-2xl items-stretch gap-2 rounded-2xl border border-border/70 bg-background p-2">
<NavRail aria-label="Meridian">
<NavRail.Header>
<NavRail.Brand icon={<LayersIcon />}>Meridian</NavRail.Brand>
<NavRail.Trigger />
</NavRail.Header>

<NavRail.Group label="Workspace">
<NavRail.Item href="#" icon={<LayoutDashboardIcon />} label="Overview" onClick={stop} />
<NavRail.Item href="#" icon={<InboxIcon />} label="Inbox" onClick={stop}>
Inbox <NavRail.Badge>3</NavRail.Badge>
</NavRail.Item>
<NavRail.Item href="#" icon={<FolderKanbanIcon />} label="Projects" onClick={stop} />
</NavRail.Group>

<NavRail.Group label="Insights">
<NavRail.Item href="#" active icon={<ChartNoAxesColumnIcon />} label="Reports" onClick={stop} />
</NavRail.Group>

<NavRail.Separator className="my-1" />

<NavRail.Footer>
<NavRail.Item href="#" icon={<SettingsIcon />} label="Settings" onClick={stop} />
<NavRail.Item href="#" icon={<CreditCardIcon />} label="Billing" onClick={stop} />
</NavRail.Footer>
</NavRail>

<div className="flex min-w-0 flex-1 flex-col gap-4 rounded-xl bg-muted/30 p-5">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-[11px] font-semibold tracking-[0.09em] text-muted-foreground uppercase">Insights</p>
<h3 className="mt-1 text-xl font-semibold tracking-[-0.02em]">Reports</h3>
</div>
<span className="rounded-full bg-background px-2.5 py-1 text-[11px] font-medium text-muted-foreground ring-1 ring-border">
Last 7 days
</span>
</div>

<div className="rounded-xl bg-background p-4 ring-1 ring-border/70">
<div className="flex items-baseline justify-between gap-4">
<p className="text-sm font-medium">Studio pulse</p>
<p className="text-xs font-medium text-[var(--success)]">+18% week on week</p>
</div>
<div className="mt-4 flex h-16 items-end gap-1.5">
{[34, 48, 40, 56, 50, 68, 62, 74, 60, 84, 78, 92].map((height, index) => (
<span
key={index}
className="w-full rounded-t-[3px] bg-primary/25 last:bg-primary"
style={{ height: `${height}%` }}
/>
))}
</div>
</div>

<div className="flex flex-col gap-1.5">
{REPORTS.map((report) => (
<div
key={report.name}
className="flex items-center gap-3 rounded-lg px-2 py-2 transition-colors hover:bg-background"
>
<span className="size-1.5 rounded-full bg-primary/60" />
<span className="min-w-0 flex-1 truncate text-sm">{report.name}</span>
<span className="text-xs text-muted-foreground">{report.owner}</span>
<span className="w-12 text-end text-xs font-medium tabular-nums">{report.change}</span>
</div>
))}
</div>
</div>
</div>
);
}
Loading
Loading