Files
cadence-ui/apps/docs/src/patterns/sidebar-nav.stories.tsx
T

237 lines
9.1 KiB
TypeScript

import {
Badge,
Button,
SidebarNav,
SidebarNavContent,
SidebarNavFooter,
SidebarNavHeader,
SidebarNavItem,
SidebarNavItemBadge,
SidebarNavItemIcon,
SidebarNavItemLabel,
SidebarNavItems,
SidebarNavSection,
SidebarNavSectionLabel
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
function PatternMark() {
return (
<span className="flex size-11 items-center justify-center rounded-[1.1rem] bg-[linear-gradient(145deg,var(--color-foreground),color-mix(in_oklch,var(--color-foreground)_72%,var(--color-primary)_28%))] text-[var(--color-background)] shadow-[0_18px_40px_color-mix(in_oklch,var(--color-foreground)_16%,transparent)]">
<svg aria-hidden="true" className="size-5" fill="none" viewBox="0 0 24 24">
<path
d="M5.25 7.2c0-1.08.87-1.95 1.95-1.95h9.6c1.08 0 1.95.87 1.95 1.95v9.6c0 1.08-.87 1.95-1.95 1.95H7.2a1.95 1.95 0 0 1-1.95-1.95V7.2Z"
fill="currentColor"
opacity="0.24"
/>
<path
d="m8.25 8.85 3.2 3.2-3.2 3.2M11.55 12.05h4.2"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.9"
/>
</svg>
</span>
);
}
function NavIcon({ kind }: { kind: "overview" | "pipeline" | "messages" | "team" | "billing" }) {
switch (kind) {
case "pipeline":
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 24 24">
<path d="M6 7.5h12M6 12h8m-8 4.5h12" stroke="currentColor" strokeLinecap="round" strokeWidth="1.8" />
</svg>
);
case "messages":
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 24 24">
<path
d="M6.5 7.25h11a2 2 0 0 1 2 2v5.25a2 2 0 0 1-2 2H12l-3.9 2.85c-.6.43-1.43 0-1.43-.74V16.5H6.5a2 2 0 0 1-2-2V9.25a2 2 0 0 1 2-2Z"
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.7"
/>
</svg>
);
case "team":
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 24 24">
<circle cx="9" cy="9" r="2.25" stroke="currentColor" strokeWidth="1.7" />
<circle cx="16" cy="10" r="1.9" stroke="currentColor" strokeWidth="1.7" opacity="0.7" />
<path
d="M5.5 18c.35-2.55 2.22-4 4.5-4s4.15 1.45 4.5 4M14.2 18c.16-1.36.93-2.28 2.25-2.9"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="1.7"
/>
</svg>
);
case "billing":
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 24 24">
<rect x="4.75" y="6.5" width="14.5" height="11" rx="2" stroke="currentColor" strokeWidth="1.7" />
<path d="M4.75 10.25h14.5" stroke="currentColor" strokeWidth="1.7" />
</svg>
);
case "overview":
default:
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 24 24">
<rect x="4" y="4" width="6" height="6" rx="1.5" fill="currentColor" />
<rect x="14" y="4" width="6" height="10" rx="1.5" fill="currentColor" opacity="0.75" />
<rect x="4" y="14" width="10" height="6" rx="1.5" fill="currentColor" opacity="0.65" />
</svg>
);
}
}
function SidebarNavExample() {
return (
<SidebarNav className="w-full max-w-[320px]">
<SidebarNavHeader>
<div className="space-y-5">
<div className="flex items-center gap-3">
<PatternMark />
<div>
<p
className="text-[1.55rem] font-semibold tracking-[-0.04em] text-[var(--color-foreground)]"
style={{ fontFamily: "var(--font-display)" }}
>
ScaleOps
</p>
<p className="text-sm text-[var(--color-muted-foreground)]">
Revenue command center
</p>
</div>
</div>
<div className="h-px bg-[linear-gradient(90deg,var(--color-border),transparent)]" />
</div>
</SidebarNavHeader>
<SidebarNavContent>
<SidebarNavSection>
<SidebarNavSectionLabel>Main</SidebarNavSectionLabel>
<SidebarNavItems>
<SidebarNavItem active>
<SidebarNavItemIcon>
<NavIcon kind="overview" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Dashboard</SidebarNavItemLabel>
</SidebarNavItem>
<SidebarNavItem>
<SidebarNavItemIcon>
<NavIcon kind="pipeline" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Pipeline</SidebarNavItemLabel>
</SidebarNavItem>
<SidebarNavItem>
<SidebarNavItemIcon>
<NavIcon kind="messages" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Messages</SidebarNavItemLabel>
<SidebarNavItemBadge>
<Badge size="sm" tone="success" variant="subtle">
2
</Badge>
</SidebarNavItemBadge>
</SidebarNavItem>
</SidebarNavItems>
</SidebarNavSection>
<SidebarNavSection>
<SidebarNavSectionLabel>Management</SidebarNavSectionLabel>
<SidebarNavItems>
<SidebarNavItem>
<SidebarNavItemIcon>
<NavIcon kind="team" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Team performance</SidebarNavItemLabel>
</SidebarNavItem>
<SidebarNavItem>
<SidebarNavItemIcon>
<NavIcon kind="billing" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Billing</SidebarNavItemLabel>
</SidebarNavItem>
</SidebarNavItems>
</SidebarNavSection>
</SidebarNavContent>
<SidebarNavFooter>
<div className="rounded-[1.5rem] border border-[color-mix(in_oklch,var(--color-primary)_14%,var(--color-border))] bg-[linear-gradient(160deg,color-mix(in_oklch,var(--color-primary-container)_78%,white_22%),color-mix(in_oklch,var(--color-tertiary-container)_74%,white_26%))] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Next pulse
</p>
<p className="mt-2 text-lg font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]">
Team forecast review at 09:30
</p>
<p className="mt-2 text-sm leading-6 text-[var(--color-muted-foreground)]">
Sales, finance, and ops owners are aligned on the board prep.
</p>
<Button className="mt-4 w-full" variant="secondary">
Open agenda
</Button>
</div>
</SidebarNavFooter>
</SidebarNav>
);
}
const meta = {
title: "Patterns/SidebarNav",
component: SidebarNavExample,
parameters: {
docs: {
description: {
component:
"SidebarNav is the reusable navigation rail pattern for dashboard and workspace shells. Use it when a product needs grouped navigation, a branded header, and a supporting footer region without improvising every rail from `Card` and `Button`."
}
},
layout: "centered"
},
tags: ["autodocs"]
} satisfies Meta<typeof SidebarNavExample>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {};
export const Anatomy: Story = {
render: () => (
<div className="grid w-[960px] gap-5 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<div className="grid gap-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Sidebar nav anatomy
</p>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
The stable hooks are the rail header, grouped sections, item rows, and footer region. That
lets one product family share navigation structure without forcing every app to share the
exact same icons or footer callout.
</p>
</div>
<SidebarNavExample />
<div className="grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>
<code className="text-[var(--color-foreground)]">data-slot="header"</code>,
<code className="ml-1 text-[var(--color-foreground)]">data-slot="content"</code>, and
<code className="ml-1 text-[var(--color-foreground)]">data-slot="footer"</code> define the
rail frame.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="section"</code>,
<code className="ml-1 text-[var(--color-foreground)]">data-slot="item"</code>, and the
nested <code className="text-[var(--color-foreground)]">icon</code> / <code className="text-[var(--color-foreground)]">label</code> / <code className="text-[var(--color-foreground)]">badge</code> slots keep
grouped navigation predictable for styling and tests.
</p>
</div>
</div>
)
};