Add style matrix validation surface

This commit is contained in:
2026-03-20 11:31:13 +08:00
parent 40a05df4b3
commit 84c185a8d7
3 changed files with 251 additions and 4 deletions
+231
View File
@@ -0,0 +1,231 @@
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Input,
Skeleton,
Switch,
skinDetails,
skinNames,
type SkinName
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
const motionModes = [
{
label: "System motion",
value: "system"
},
{
label: "Reduced motion",
value: "reduced"
}
] as const;
type MotionMode = (typeof motionModes)[number]["value"];
function RuntimePill({ children }: { children: React.ReactNode }) {
return (
<span className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
{children}
</span>
);
}
function PanelPreview() {
return (
<div
className="grid gap-3 border p-4"
style={{
background: "var(--ui-panel-bg)",
borderColor: "var(--ui-panel-border)",
borderRadius: "var(--ui-panel-radius)",
borderWidth: "var(--ui-panel-border-width)",
boxShadow: "var(--ui-panel-shadow)",
backdropFilter: "blur(var(--ui-panel-backdrop-blur))"
}}
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-[var(--color-foreground)]">
Dialog panel contract
</p>
<p className="mt-1 text-sm leading-6 text-[var(--color-muted-foreground)]">
Panel vars preview the dialog surface without opening an overlay in every
matrix cell.
</p>
</div>
<Button aria-hidden="true" size="icon" tabIndex={-1} variant="ghost">
×
</Button>
</div>
<div className="grid gap-2">
<Skeleton shape="line" />
<Skeleton shape="block" />
</div>
</div>
);
}
function ComparisonCell({
motion,
skin
}: {
motion: MotionMode;
skin: SkinName;
}) {
return (
<section
className="grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-4 shadow-[var(--shadow-sm)]"
data-motion={motion === "reduced" ? "reduced" : undefined}
data-skin={skin}
>
<div className="flex flex-wrap gap-2">
<RuntimePill>{skinDetails[skin].label}</RuntimePill>
<RuntimePill>{motion}</RuntimePill>
</div>
<Card interactive tone="default">
<CardHeader>
<CardTitle>Release routing</CardTitle>
<CardDescription>
The same component tree should now pick up distinct skin treatments.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-3">
<Input defaultValue="Launch notes approved" readOnly />
<div className="flex items-center justify-between gap-3">
<span className="text-sm text-[var(--color-muted-foreground)]">
Quiet notifications
</span>
<Switch checked />
</div>
<div className="flex flex-wrap gap-3">
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="subtle">Subtle</Button>
</div>
</CardContent>
</Card>
<PanelPreview />
</section>
);
}
function MatrixDialogSandbox() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">Open live dialog preview</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog validation sandbox</DialogTitle>
<DialogDescription>
Use the Storybook toolbar to validate the real overlay under the active theme,
skin, and motion settings.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost">Back</Button>
<Button>Approve</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function StyleMatrixShowcase() {
return (
<div className="min-h-screen bg-[var(--color-background)] px-6 py-10 text-[var(--color-foreground)] sm:px-10">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-8">
<header className="max-w-4xl space-y-4">
<p className="text-sm uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
AI UI / Phase 3
</p>
<h1
className="font-semibold tracking-[var(--tracking-tight)]"
style={{
fontFamily: "var(--font-display)",
fontSize: "var(--text-4xl)",
lineHeight: "var(--leading-tight)"
}}
>
Style matrix compares the same product surface across skin and motion scopes.
</h1>
<p className="max-w-3xl text-[var(--text-lg)] leading-[var(--leading-loose)] text-[var(--color-muted-foreground)]">
This page is the screenshot-friendly regression target for the pilot skin work.
The grid uses nested `data-skin` and `data-motion` scopes so the same building
blocks can be reviewed side by side.
</p>
</header>
<section className="grid gap-4">
{motionModes.map((motionMode) => (
<div key={motionMode.value} className="grid gap-3">
<div className="flex items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold">{motionMode.label}</h2>
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
`{motionMode.value === "reduced" ? 'data-motion="reduced"' : "default"}`
{" "}
on the wrapper scope.
</p>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-3">
{skinNames.map((skin) => (
<ComparisonCell key={`${motionMode.value}-${skin}`} motion={motionMode.value} skin={skin} />
))}
</div>
</div>
))}
</section>
<section className="grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)] lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center">
<div>
<h2 className="text-2xl font-semibold">Live overlay validation</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-[var(--color-muted-foreground)]">
Dialog still portals to the document root, so compare its real overlay and
panel treatment with the Storybook toolbar. The matrix above covers scoped
inline regression. The control below covers the live overlay behavior.
</p>
</div>
<div className="flex justify-start lg:justify-end">
<MatrixDialogSandbox />
</div>
</section>
</div>
</div>
);
}
const meta = {
title: "Foundation/Style Matrix",
component: StyleMatrixShowcase,
parameters: {
docs: {
description: {
component:
"Phase 3 adds the regression-oriented comparison surface. Use this page for screenshots and visual review, then use the live dialog sandbox below to validate portal-driven overlays under the active toolbar settings."
}
}
}
} satisfies Meta<typeof StyleMatrixShowcase>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Overview: Story = {};
+5 -3
View File
@@ -511,7 +511,7 @@ Deliverables:
Status: Status:
- not started - completed
### Phase 4: Expand Coverage ### Phase 4: Expand Coverage
@@ -605,7 +605,9 @@ As of 2026-03-20, the project is at this point:
- docs switching surface added in `Foundation/Style Contract` - docs switching surface added in `Foundation/Style Contract`
- pilot recipe extraction completed for `Button`, `Card`, `Input`, `Dialog`, `Switch`, - pilot recipe extraction completed for `Button`, `Card`, `Input`, `Dialog`, `Switch`,
and `Skeleton` and `Skeleton`
- screenshot-friendly validation surface added in `Foundation/Style Matrix`
- scoped `data-motion="reduced"` now works for nested docs wrappers
- broader component-library rollout still pending - broader component-library rollout still pending
The next implementation task should be Phase 3: add a dedicated comparison-oriented docs The next implementation task should be Phase 4: expand the skin-aware pattern across the
validation surface for skins and motion modes. broader component library.
+15 -1
View File
@@ -19,7 +19,8 @@
--scale-pop: 1.02; --scale-pop: 1.02;
} }
:root[data-motion="reduced"] { :root[data-motion="reduced"],
[data-motion="reduced"] {
--dur-instant: 1ms; --dur-instant: 1ms;
--dur-fast: 1ms; --dur-fast: 1ms;
--dur-base: 1ms; --dur-base: 1ms;
@@ -32,6 +33,19 @@
--scale-press: 1; --scale-press: 1;
--scale-hover: 1; --scale-hover: 1;
--scale-pop: 1; --scale-pop: 1;
scroll-behavior: auto;
}
:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after,
[data-motion="reduced"] *,
[data-motion="reduced"] *::before,
[data-motion="reduced"] *::after {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 1ms !important;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {