237 lines
9.1 KiB
TypeScript
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>
|
|
)
|
|
};
|