feat(docs): add analytics and pattern showcase stories

This commit is contained in:
2026-03-25 19:59:42 +08:00
parent a5d75f42e9
commit f049736c8a
21 changed files with 5765 additions and 1204 deletions
@@ -0,0 +1,274 @@
import {
AppShell,
AppShellBody,
AppShellFooter,
AppShellHeader,
AppShellMain,
AppShellSidebar,
Badge,
Button,
PageFooter,
PageFooterActions,
PageFooterDescription,
PageFooterLeading,
PageFooterTitle,
PageHeader,
PageHeaderActions,
PageHeaderDescription,
PageHeaderEyebrow,
PageHeaderLeading,
PageHeaderTitle,
SidebarNav,
SidebarNavContent,
SidebarNavHeader,
SidebarNavItem,
SidebarNavItemIcon,
SidebarNavItemLabel,
SidebarNavItems,
SidebarNavSection,
SidebarNavSectionLabel,
StatCard,
StatCardDelta,
StatCardDescription,
StatCardHeader,
StatCardLabel,
StatCardMetric,
StatCardValue
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
function ShellMark() {
return (
<span className="flex size-10 items-center justify-center rounded-[1rem] bg-[linear-gradient(145deg,var(--color-foreground),color-mix(in_oklch,var(--color-foreground)_74%,var(--color-primary)_26%))] text-[var(--color-background)]">
<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 RailIcon({ kind }: { kind: "overview" | "pipeline" | "team" }) {
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 "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 "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 AppShellExample() {
return (
<div className="w-full max-w-[1320px]">
<AppShell layout="sidebar" surface="panel">
<AppShellSidebar>
<SidebarNav className="h-full">
<SidebarNavHeader>
<div className="flex items-center gap-3">
<ShellMark />
<div>
<p
className="text-[1.35rem] 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)]">Workspace shell</p>
</div>
</div>
</SidebarNavHeader>
<SidebarNavContent>
<SidebarNavSection>
<SidebarNavSectionLabel>Main</SidebarNavSectionLabel>
<SidebarNavItems>
<SidebarNavItem active>
<SidebarNavItemIcon>
<RailIcon kind="overview" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Overview</SidebarNavItemLabel>
</SidebarNavItem>
<SidebarNavItem>
<SidebarNavItemIcon>
<RailIcon kind="pipeline" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Pipeline</SidebarNavItemLabel>
</SidebarNavItem>
<SidebarNavItem>
<SidebarNavItemIcon>
<RailIcon kind="team" />
</SidebarNavItemIcon>
<SidebarNavItemLabel>Team</SidebarNavItemLabel>
</SidebarNavItem>
</SidebarNavItems>
</SidebarNavSection>
</SidebarNavContent>
</SidebarNav>
</AppShellSidebar>
<AppShellBody>
<AppShellHeader>
<PageHeader density="compact" variant="default">
<PageHeaderLeading>
<PageHeaderEyebrow>Workspace shell</PageHeaderEyebrow>
<PageHeaderTitle>One shared frame for navigation, header, and page body.</PageHeaderTitle>
<PageHeaderDescription>
AppShell owns the outer layout contract so product pages can swap content without
reinventing the sidebar and body structure.
</PageHeaderDescription>
</PageHeaderLeading>
<PageHeaderActions>
<Button>Primary action</Button>
<Button variant="secondary">Secondary action</Button>
</PageHeaderActions>
</PageHeader>
</AppShellHeader>
<AppShellMain>
<div className="grid gap-4 lg:grid-cols-2">
<StatCard>
<StatCardHeader>
<StatCardLabel>Live revenue pulse</StatCardLabel>
</StatCardHeader>
<StatCardMetric>
<StatCardValue>$101,820</StatCardValue>
<StatCardDelta tone="success">+84</StatCardDelta>
</StatCardMetric>
<StatCardDescription>
AI-assisted follow-up is still the strongest lift across the board window.
</StatCardDescription>
</StatCard>
<StatCard>
<StatCardHeader>
<StatCardLabel>Forecast confidence</StatCardLabel>
</StatCardHeader>
<StatCardMetric>
<StatCardValue>31%</StatCardValue>
<StatCardDelta tone="primary">+9.2%</StatCardDelta>
</StatCardMetric>
<StatCardDescription>
Strong enough for planning, but still worth watching through the anomaly lane.
</StatCardDescription>
</StatCard>
</div>
</AppShellMain>
<AppShellFooter>
<PageFooter tone="subtle">
<PageFooterLeading>
<PageFooterTitle>Shell state is synchronized across the workspace.</PageFooterTitle>
<PageFooterDescription>
The page footer closes the shell with status and low-emphasis follow-up actions.
</PageFooterDescription>
</PageFooterLeading>
<PageFooterActions>
<Badge tone="primary" variant="subtle">
Ready for review
</Badge>
</PageFooterActions>
</PageFooter>
</AppShellFooter>
</AppShellBody>
</AppShell>
</div>
);
}
const meta = {
title: "Patterns/AppShell",
component: AppShellExample,
parameters: {
docs: {
description: {
component:
"AppShell is the shared outer layout pattern for workspace-style screens. Use it when a product view needs a repeatable sidebar/body frame that can host `SidebarNav`, `PageHeader`, `PageFooter`, and arbitrary content panels without each scene redefining the same shell grid. The shell should feel quietly awake through soft surface motion and focus-within depth changes, while leaving stronger animation work to the content living inside it."
}
},
layout: "centered"
},
tags: ["autodocs"]
} satisfies Meta<typeof AppShellExample>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {};
export const Anatomy: Story = {
render: () => (
<div className="grid w-[1320px] 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)]">
App shell anatomy
</p>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
The stable shell regions are the sidebar, body, header, main content lane, and footer.
That keeps full-page scenes aligned around one layout contract instead of local CSS grids.
</p>
</div>
<AppShellExample />
</div>
)
};
export const Motion: Story = {
parameters: {
docs: {
description: {
story:
"The panel shell should feel a little more alive than a static CSS grid, but it should still read as infrastructure. Hover and focus inside the shell to check the subtle surface wake-up without competing with the contained components."
}
}
},
render: () => (
<div className="grid w-[1320px] gap-5 rounded-[var(--radius-xl)] border border-[color-mix(in_oklch,var(--color-border)_72%,transparent)] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_80%,white_20%),color-mix(in_oklch,var(--color-surface-container-low)_88%,white_12%))] p-6 shadow-[var(--shadow-sm)]">
<div className="grid gap-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-primary)]">
Motion review
</p>
<p className="max-w-3xl text-sm leading-6 text-[var(--color-muted-foreground)]">
AppShell now carries a restrained surface glow and depth response so the workspace frame
feels staged, not inert. The motion should stay in the background behind the content.
</p>
</div>
<AppShellExample />
</div>
)
};