Add style matrix validation surface
This commit is contained in:
@@ -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 = {};
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user