refactor(motion): simplify to default and reduced

This commit is contained in:
2026-03-20 16:44:24 +08:00
parent 9009ce4853
commit 142f4a399a
8 changed files with 92 additions and 322 deletions
+11 -29
View File
@@ -8,15 +8,11 @@ import {
skinNames
} from "@ai-ui/ui";
import {
defaultMotionAccessibility,
defaultMotionPack,
defaultMotionMode,
defaultTheme,
motionAccessibilityDetails,
motionAccessibilityNames,
motionPackDetails,
motionPackNames,
setMotionAccessibility,
setMotionPack,
motionModeDetails,
motionModeNames,
setMotionMode,
setTheme,
themeDetails,
themeNames
@@ -35,25 +31,14 @@ const preview: Preview = {
}))
}
},
motionPack: {
description: "Preview motion pack",
motionMode: {
description: "Preview motion mode",
toolbar: {
icon: "transfer",
dynamicTitle: true,
items: motionPackNames.map((packName) => ({
value: packName,
title: motionPackDetails[packName].label
}))
}
},
motionAccessibility: {
description: "Preview motion accessibility override",
toolbar: {
icon: "accessibility",
dynamicTitle: true,
items: motionAccessibilityNames.map((modeName) => ({
items: motionModeNames.map((modeName) => ({
value: modeName,
title: motionAccessibilityDetails[modeName].label
title: motionModeDetails[modeName].label
}))
}
},
@@ -70,8 +55,7 @@ const preview: Preview = {
}
},
initialGlobals: {
motionAccessibility: defaultMotionAccessibility,
motionPack: defaultMotionPack,
motionMode: defaultMotionMode,
skin: defaultSkin,
theme: defaultTheme
},
@@ -97,13 +81,11 @@ const preview: Preview = {
(Story, context) => {
if (typeof document !== "undefined") {
setTheme(context.globals.theme ?? defaultTheme);
setMotionPack(context.globals.motionPack ?? defaultMotionPack);
setMotionAccessibility(
context.globals.motionAccessibility ?? defaultMotionAccessibility
);
setMotionMode(context.globals.motionMode ?? defaultMotionMode);
setSkin(context.globals.skin ?? defaultSkin);
document.body.dataset.theme = context.globals.theme ?? defaultTheme;
document.body.dataset.motion = context.globals.motionMode ?? defaultMotionMode;
document.body.dataset.skin = context.globals.skin ?? defaultSkin;
}
+10 -20
View File
@@ -11,18 +11,15 @@ import {
type SkinName
} from "@ai-ui/ui";
import {
defaultMotionAccessibility,
defaultMotionPack,
defaultMotionMode,
defaultTheme,
type MotionAccessibilityName,
type MotionPackName,
type MotionModeName,
type ThemeName
} from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
type StyleContractShowcaseProps = {
motionAccessibility: MotionAccessibilityName;
motionPack: MotionPackName;
motionMode: MotionModeName;
skin: SkinName;
theme: ThemeName;
};
@@ -147,8 +144,7 @@ function SkinPanel({
}
function StyleContractShowcase({
motionAccessibility,
motionPack,
motionMode,
skin,
theme
}: StyleContractShowcaseProps) {
@@ -180,8 +176,7 @@ function StyleContractShowcase({
<section className="flex flex-wrap gap-3">
<RuntimeBadge label="theme" value={theme} />
<RuntimeBadge label="skin" value={skin} />
<RuntimeBadge label="motion pack" value={motionPack} />
<RuntimeBadge label="accessibility" value={motionAccessibility} />
<RuntimeBadge label="motion" value={motionMode} />
</section>
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
@@ -192,7 +187,7 @@ function StyleContractShowcase({
"A new runtime attribute: `data-skin`",
"Public helpers from `@ai-ui/ui` for skin names, defaults, and root updates",
"A dedicated `@ai-ui/ui/skins.css` entrypoint imported by the docs app",
"Storybook globals that apply theme, skin, motion pack, and accessibility override together"
"Storybook globals that apply theme, skin, and motion mode together"
].map((item) => (
<div
key={item}
@@ -242,8 +237,7 @@ const meta = {
title: "Foundation/Style Contract",
component: StyleContractShowcase,
args: {
motionAccessibility: defaultMotionAccessibility,
motionPack: defaultMotionPack,
motionMode: defaultMotionMode,
skin: defaultSkin,
theme: defaultTheme
},
@@ -251,18 +245,14 @@ const meta = {
docs: {
description: {
component:
"Phase 1 adds the runtime style contract. Use the Storybook toolbar to switch the active `theme`, `skin`, `motion pack`, and accessibility override globally, or inspect the side-by-side nested `data-skin` panels below."
"Phase 1 adds the runtime style contract. Use the Storybook toolbar to switch the active `theme`, `skin`, and `motion` mode globally, or inspect the side-by-side nested `data-skin` panels below."
}
}
},
render: (_args, context) => (
<StyleContractShowcase
motionAccessibility={
(context.globals.motionAccessibility as MotionAccessibilityName | undefined) ??
defaultMotionAccessibility
}
motionPack={
(context.globals.motionPack as MotionPackName | undefined) ?? defaultMotionPack
motionMode={
(context.globals.motionMode as MotionModeName | undefined) ?? defaultMotionMode
}
skin={(context.globals.skin as SkinName | undefined) ?? defaultSkin}
theme={(context.globals.theme as ThemeName | undefined) ?? defaultTheme}
+25 -64
View File
@@ -1,7 +1,7 @@
import {
motionPackDetails,
motionPackNames,
type MotionPackName
motionModeDetails,
motionModeNames,
type MotionModeName
} from "@ai-ui/tokens";
import {
Button,
@@ -26,19 +26,6 @@ import {
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
const motionAccessibilityModes = [
{
label: "System accessibility",
value: "system"
},
{
label: "Reduced accessibility",
value: "reduced"
}
] as const;
type MotionAccessibilityMode = (typeof motionAccessibilityModes)[number]["value"];
function ClosePreviewIcon() {
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 16 16">
@@ -97,25 +84,21 @@ function PanelPreview() {
}
function ComparisonCell({
motionAccessibility,
motionPack,
motionMode,
skin
}: {
motionAccessibility: MotionAccessibilityMode;
motionPack: MotionPackName;
motionMode: MotionModeName;
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={motionAccessibility === "reduced" ? "reduced" : undefined}
data-motion-pack={motionPack}
data-motion={motionMode}
data-skin={skin}
>
<div className="flex flex-wrap gap-2">
<RuntimePill>{motionPackDetails[motionPack].label}</RuntimePill>
<RuntimePill>{motionModeDetails[motionMode].label}</RuntimePill>
<RuntimePill>{skinDetails[skin].label}</RuntimePill>
<RuntimePill>{motionAccessibility}</RuntimePill>
</div>
<Card interactive tone="default">
@@ -127,7 +110,7 @@ function ComparisonCell({
</CardHeader>
<CardContent className="grid gap-3">
<Input
aria-label={`${motionPack} ${skin} release note status`}
aria-label={`${motionMode} ${skin} release note status`}
defaultValue="Launch notes approved"
readOnly
/>
@@ -136,7 +119,7 @@ function ComparisonCell({
Quiet notifications
</span>
<Switch
aria-label={`${motionPack} ${skin} quiet notifications`}
aria-label={`${motionMode} ${skin} quiet notifications`}
checked
/>
</div>
@@ -196,52 +179,30 @@ function StyleMatrixShowcase() {
</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`, `data-motion-pack`, and
`data-motion=&quot;reduced&quot;` scopes so the same building blocks can be
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">
{motionPackNames.map((motionPack) => (
<div key={motionPack} className="grid gap-4">
{motionModeNames.map((motionMode) => (
<div key={motionMode} className="grid gap-4">
<div>
<h2 className="text-2xl font-semibold">{motionPackDetails[motionPack].label}</h2>
<h2 className="text-2xl font-semibold">{motionModeDetails[motionMode].label}</h2>
<p className="mt-1 max-w-3xl text-sm leading-6 text-[var(--color-muted-foreground)]">
{motionPackDetails[motionPack].note}
{motionModeDetails[motionMode].note}
</p>
</div>
{motionAccessibilityModes.map((motionAccessibilityMode) => (
<div key={`${motionPack}-${motionAccessibilityMode.value}`} className="grid gap-3">
<div className="flex items-center justify-between gap-4">
<div>
<h3 className="text-xl font-semibold">
{motionAccessibilityMode.label}
</h3>
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
{motionAccessibilityMode.value === "reduced"
? '`data-motion="reduced"`'
: "System preference"}
{" "}with{" "}
<code className="text-[var(--color-foreground)]">
{`data-motion-pack="${motionPack}"`}
</code>
.
</p>
</div>
</div>
<div className="grid gap-4 xl:grid-cols-3">
{skinNames.map((skin) => (
<ComparisonCell
key={`${motionPack}-${motionAccessibilityMode.value}-${skin}`}
motionAccessibility={motionAccessibilityMode.value}
motionPack={motionPack}
skin={skin}
/>
))}
</div>
</div>
))}
<div className="grid gap-4 xl:grid-cols-3">
{skinNames.map((skin) => (
<ComparisonCell
key={`${motionMode}-${skin}`}
motionMode={motionMode}
skin={skin}
/>
))}
</div>
</div>
))}
</section>
@@ -252,7 +213,7 @@ function StyleMatrixShowcase() {
<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 across packs and reduced-motion overlay. The control below
inline regression across default and reduced motion modes. The control below
covers the live overlay behavior.
</p>
</div>
+12 -30
View File
@@ -1,25 +1,21 @@
import {
colorTokens,
defaultTheme,
defaultMotionAccessibility,
defaultMotionPack,
defaultMotionMode,
motionTokens,
motionAccessibilityDetails,
motionPackDetails,
motionModeDetails,
radiusTokens,
shadowTokens,
themeDetails,
themeNames,
typographyTokens,
type MotionAccessibilityName,
type MotionPackName,
type MotionModeName,
type ThemeName
} from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
type TokensOverviewProps = {
motionAccessibility: MotionAccessibilityName;
motionPack: MotionPackName;
motionMode: MotionModeName;
theme: ThemeName;
};
@@ -127,8 +123,7 @@ function ThemeCard({ themeName }: { themeName: ThemeName }) {
}
function TokensOverview({
motionAccessibility,
motionPack,
motionMode,
theme
}: TokensOverviewProps) {
return (
@@ -168,18 +163,10 @@ function TokensOverview({
</div>
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-card)] px-4 py-3 shadow-[var(--shadow-xs)]">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Motion Pack
Motion
</p>
<p className="mt-2 text-sm font-medium text-[var(--color-foreground)]">
{motionPackDetails[motionPack].label}
</p>
</div>
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-card)] px-4 py-3 shadow-[var(--shadow-xs)]">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Accessibility Override
</p>
<p className="mt-2 text-sm font-medium text-[var(--color-foreground)]">
{motionAccessibilityDetails[motionAccessibility].label}
{motionModeDetails[motionMode].label}
</p>
</div>
</div>
@@ -301,8 +288,8 @@ function TokensOverview({
<h2 className="text-2xl font-semibold">Motion tokens</h2>
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
Timing and motion scale now live in variables that components can consume
directly. The toolbar now separates the active motion pack from the
accessibility override.
directly. The toolbar now switches between the default interaction layer
and the reduced-motion fallback.
</p>
<div className="mt-6 grid gap-4 md:grid-cols-2">
<div className="space-y-3">
@@ -390,18 +377,13 @@ type Story = StoryObj<typeof meta>;
export const Overview: Story = {
args: {
motionAccessibility: defaultMotionAccessibility,
motionPack: defaultMotionPack,
motionMode: defaultMotionMode,
theme: defaultTheme
},
render: (_args, context) => (
<TokensOverview
motionAccessibility={
(context.globals.motionAccessibility as MotionAccessibilityName | undefined) ??
defaultMotionAccessibility
}
motionPack={
(context.globals.motionPack as MotionPackName | undefined) ?? defaultMotionPack
motionMode={
(context.globals.motionMode as MotionModeName | undefined) ?? defaultMotionMode
}
theme={context.globals.theme as ThemeName}
/>