feat(docs): add analytics and pattern showcase stories
This commit is contained in:
@@ -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>
|
||||
)
|
||||
};
|
||||
Reference in New Issue
Block a user