diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e45ce684ba9323..8086c838a8638e 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -17,6 +17,10 @@ - `Button`: Hide focus outline on `:active` in High Contrast (forced colors) mode to provide visual click feedback ([#76833](https://github.com/WordPress/gutenberg/pull/76833)). +### Internal + +- `Modal`, `Menu`: Use `--wpds-motion-*` design tokens for animation duration and easing ([#76097](https://github.com/WordPress/gutenberg/pull/76097)). + ## 32.4.0 (2026-03-18) ### Bug Fixes diff --git a/packages/components/src/modal/index.tsx b/packages/components/src/modal/index.tsx index 9515bd96d7dcfb..997f57becdc97b 100644 --- a/packages/components/src/modal/index.tsx +++ b/packages/components/src/modal/index.tsx @@ -168,8 +168,7 @@ function UnforwardedModal( }; }, [ bodyOpenClassName ] ); - const { closeModal, frameRef, frameStyle, overlayClassname } = - useModalExitAnimation(); + const { closeModal, frameRef, overlayClassname } = useModalExitAnimation(); // Calls the isContentScrollable callback when the Modal children container resizes. useLayoutEffect( () => { @@ -259,10 +258,7 @@ function UnforwardedModal( sizeClass, className ) } - style={ { - ...frameStyle, - ...style, - } } + style={ style } ref={ useMergeRefs( [ frameRef, constrainedTabbingRef, diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index f84e55958ef3f1..049e6b7493738e 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -1,4 +1,3 @@ -@use "@wordpress/base-styles/animations" as *; @use "@wordpress/base-styles/colors" as *; @use "@wordpress/base-styles/mixins" as *; @use "@wordpress/base-styles/variables" as *; @@ -15,14 +14,16 @@ background-color: rgba($black, 0.35); z-index: z-index(".components-modal__screen-overlay"); display: flex; - // This animates the appearance of the backdrop. - @include animation__fade-in(); + @media not (prefers-reduced-motion) { + animation: components-modal__fade-in var(--wpds-motion-duration-sm) var(--wpds-motion-easing-gentle); + animation-fill-mode: forwards; + } &.is-animating-out { - // Note: it's important that the fade-out animation doesn't end after the - // modal frame's disappear animation, because the component will be removed - // from the DOM when that animation ends. - @include animation__fade-out($delay: 80ms); + @media not (prefers-reduced-motion) { + animation: components-modal__fade-out var(--wpds-motion-duration-sm) var(--wpds-motion-easing-gentle) var(--wpds-motion-duration-sm); + animation-fill-mode: forwards; + } } } @@ -42,7 +43,7 @@ // Animate the modal frame/contents appearing on the page. animation-name: components-modal__appear-animation; animation-fill-mode: forwards; - animation-timing-function: cubic-bezier(0.29, 0, 0, 1); + animation-timing-function: var(--wpds-motion-easing-decelerate-emphasized); // Ensure all headings use the proper editor text color, overriding wp-admin common.css h1, @@ -52,12 +53,12 @@ } @media not (prefers-reduced-motion) { - animation-duration: var(--modal-frame-animation-duration); + animation-duration: var(--wpds-motion-duration-md); } .components-modal__screen-overlay.is-animating-out & { animation-name: components-modal__disappear-animation; - animation-timing-function: cubic-bezier(1, 0, 0.2, 1); + animation-timing-function: var(--wpds-motion-easing-decelerate-emphasized); } // Show a centered modal on bigger screens. @@ -119,6 +120,24 @@ } } +@keyframes components-modal__fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes components-modal__fade-out { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + @keyframes components-modal__appear-animation { from { opacity: 0; diff --git a/packages/components/src/modal/use-modal-exit-animation.ts b/packages/components/src/modal/use-modal-exit-animation.ts index 3a62654aea4c56..7cb0fc9c38ccc5 100644 --- a/packages/components/src/modal/use-modal-exit-animation.ts +++ b/packages/components/src/modal/use-modal-exit-animation.ts @@ -10,9 +10,7 @@ import warning from '@wordpress/warning'; */ import { CONFIG } from '../utils'; -// Animation duration (ms) extracted to JS in order to be used on a setTimeout. -const FRAME_ANIMATION_DURATION = CONFIG.transitionDuration; -const FRAME_ANIMATION_DURATION_NUMBER = Number.parseInt( +const FRAME_ANIMATION_DURATION_MS = Number.parseInt( CONFIG.transitionDuration ); @@ -68,7 +66,7 @@ export function useModalExitAnimation() { // Allow an extra 20% of the animation duration for the // animationend event to fire, in case the animation frame is // slightly delayes by some other events in the event loop. - FRAME_ANIMATION_DURATION_NUMBER * 1.2 + FRAME_ANIMATION_DURATION_MS * 1.2 ); } ); @@ -91,9 +89,6 @@ export function useModalExitAnimation() { return { overlayClassname: isAnimatingOut ? 'is-animating-out' : undefined, frameRef, - frameStyle: { - '--modal-frame-animation-duration': `${ FRAME_ANIMATION_DURATION }`, - }, closeModal, }; } diff --git a/packages/components/src/utils/dropdown-motion.ts b/packages/components/src/utils/dropdown-motion.ts index bf6e4adef7b1c7..d6179c614fba89 100644 --- a/packages/components/src/utils/dropdown-motion.ts +++ b/packages/components/src/utils/dropdown-motion.ts @@ -1,5 +1,6 @@ // Motion configuration for dropdown-like popovers. // Keep constants in sync with: packages/ui/src/utils/css/dropdown-motion.module.css +// Values should stay in sync with --wpds-motion-* design tokens. export const DROPDOWN_MOTION = Object.freeze( { SLIDE_DISTANCE: 4, @@ -8,7 +9,7 @@ export const DROPDOWN_MOTION = Object.freeze( { function: 'cubic-bezier', args: [ 0, 0, 0, 1 ] as const, }, - FADE_DURATION: 80, + FADE_DURATION: 100, FADE_EASING: { function: 'linear', }, @@ -24,6 +25,9 @@ const convertEasingToString = ( easing: { return easing.function; }; +// Note: --wpds-* tokens can't be used here directly because the build-time +// fallback injection is not compatible with Emotion. This file is consumed +// by Menu's Emotion styles. export const DROPDOWN_MOTION_CSS = Object.freeze( { SLIDE_DISTANCE: `${ DROPDOWN_MOTION.SLIDE_DISTANCE }px`, SLIDE_DURATION: `${ DROPDOWN_MOTION.SLIDE_DURATION }ms`, diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 269dafb1125c47..b35e9de1c3c06b 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -22,6 +22,7 @@ - Added PostCSS, esbuild, and Vite build plugins that inject fallback values for design system tokens (`--wpds-*`). Available as package exports: `@wordpress/theme/postcss-plugins/postcss-ds-token-fallbacks`, `@wordpress/theme/esbuild-plugins/esbuild-ds-token-fallbacks`, `@wordpress/theme/vite-plugins/vite-ds-token-fallbacks` ([#75589](https://github.com/WordPress/gutenberg/pull/75589)). - Add `--wpds-cursor-control` design token for interactive non-link elements ([#75697](https://github.com/WordPress/gutenberg/pull/75697)). - Add `--wpds-dimension-surface-width-*` design tokens for component width constraints. +- Add `--wpds-motion-duration-*` and `--wpds-motion-easing-*` design tokens for standardizing animation timing across components. ## 0.7.0 (2026-02-18) diff --git a/packages/theme/README.md b/packages/theme/README.md index 384aa3a16a0174..46c14a8dfe820a 100644 --- a/packages/theme/README.md +++ b/packages/theme/README.md @@ -39,6 +39,7 @@ The design system follows the [Design Tokens Community Group (DTCG)](https://des | `typography.json` | Font family stacks, font sizes, and line heights | | `border.json` | Border radius and width values | | `elevation.json` | Shadow definitions for creating depth and layering | +| `motion.json` | Animation durations and easing curves | Each JSON file contains both primitive and semantic token definitions in a hierarchical structure. These files are the source of truth for the design system and are processed during the build step to generate CSS custom properties and other output formats in `/src/prebuilt`. @@ -59,20 +60,23 @@ Semantic tokens follow a consistent naming pattern: | `border` | Border properties like radius and width | | `elevation` | Shadow definitions for layering and depth | | `font` | Typography properties like family, size, and line-height | +| `motion` | Animation durations and easing curves | **Property** is the specific design property being defined. -| Value | Description | -| --------- | ---------------------------------- | -| `bg` | Background color | -| `fg` | Foreground color (text and icons) | -| `stroke` | Border and outline color | -| `padding` | Internal spacing within an element | -| `gap` | Spacing between elements | -| `radius` | Border radius for rounded corners | -| `width` | Border width | -| `size` | Font size | -| `family` | Font family | +| Value | Description | +| ---------- | ---------------------------------- | +| `bg` | Background color | +| `fg` | Foreground color (text and icons) | +| `stroke` | Border and outline color | +| `padding` | Internal spacing within an element | +| `gap` | Spacing between elements | +| `radius` | Border radius for rounded corners | +| `width` | Border width | +| `size` | Font size | +| `family` | Font family | +| `duration` | Animation duration | +| `easing` | Animation easing curve | **Target** is the component or element type the token applies to. diff --git a/packages/theme/docs/tokens.md b/packages/theme/docs/tokens.md index 565e039dfae72d..5789a17d94e69c 100644 --- a/packages/theme/docs/tokens.md +++ b/packages/theme/docs/tokens.md @@ -155,6 +155,20 @@ Do not edit directly. | `--wpds-elevation-md` | For components that offer additional actions. Example: Menus, Command Palette | | `--wpds-elevation-lg` | For components that confirm decisions or handle necessary interruptions. Example: Modals. | +### Motion + +| Variable name | Description | +| -------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `--wpds-motion-duration-xs` | Micro-delays and transition offsets | +| `--wpds-motion-duration-sm` | Micro-interactions like focus rings and state changes | +| `--wpds-motion-duration-md` | Standard transitions like menus and popovers | +| `--wpds-motion-duration-lg` | Deliberate animations like slides and reveals | +| `--wpds-motion-duration-xl` | Extended animations like complex or multi-step transitions | +| `--wpds-motion-easing-gentle` | Subtle easing for hover, color, and background transitions | +| `--wpds-motion-easing-standard` | Balanced easing for on-screen movement like resizing, morphing, and layout shifts | +| `--wpds-motion-easing-decelerate` | Decelerating easing for elements entering or exiting the screen, such as menus and popovers | +| `--wpds-motion-easing-decelerate-emphasized` | Expressive easing for prominent elements entering or exiting, like dialogs and drawers | + ### Typography | Variable name | Description | diff --git a/packages/theme/src/prebuilt/css/design-tokens.css b/packages/theme/src/prebuilt/css/design-tokens.css index f980215b8a98c6..91e85f290a6aab 100644 --- a/packages/theme/src/prebuilt/css/design-tokens.css +++ b/packages/theme/src/prebuilt/css/design-tokens.css @@ -149,6 +149,15 @@ --wpds-font-size-xs: 11px; /* Extra small font size */ --wpds-font-weight-medium: 499; /* Medium font weight for emphasis and headings */ --wpds-font-weight-regular: 400; /* Regular font weight for body text */ + --wpds-motion-duration-lg: 300ms; /* Deliberate animations like slides and reveals */ + --wpds-motion-duration-md: 200ms; /* Standard transitions like menus and popovers */ + --wpds-motion-duration-sm: 100ms; /* Micro-interactions like focus rings and state changes */ + --wpds-motion-duration-xl: 400ms; /* Extended animations like complex or multi-step transitions */ + --wpds-motion-duration-xs: 50ms; /* Micro-delays and transition offsets */ + --wpds-motion-easing-decelerate: cubic-bezier(0, 0, 0, 1); /* Decelerating easing for elements entering or exiting the screen, such as menus and popovers */ + --wpds-motion-easing-decelerate-emphasized: cubic-bezier(0.29, 0, 0, 1); /* Expressive easing for prominent elements entering or exiting, like dialogs and drawers */ + --wpds-motion-easing-gentle: cubic-bezier(0.25, 0.1, 0.25, 1); /* Subtle easing for hover, color, and background transitions */ + --wpds-motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1); /* Balanced easing for on-screen movement like resizing, morphing, and layout shifts */ } [data-wpds-theme-provider-id][data-wpds-density='compact'] { diff --git a/packages/theme/src/prebuilt/js/design-token-fallbacks.mjs b/packages/theme/src/prebuilt/js/design-token-fallbacks.mjs index f2f900756f07aa..c9123d14e0a5e0 100644 --- a/packages/theme/src/prebuilt/js/design-token-fallbacks.mjs +++ b/packages/theme/src/prebuilt/js/design-token-fallbacks.mjs @@ -160,4 +160,14 @@ export default { '--wpds-font-size-xs': '11px', '--wpds-font-weight-medium': '499', '--wpds-font-weight-regular': '400', + '--wpds-motion-duration-lg': '300ms', + '--wpds-motion-duration-md': '200ms', + '--wpds-motion-duration-sm': '100ms', + '--wpds-motion-duration-xl': '400ms', + '--wpds-motion-duration-xs': '50ms', + '--wpds-motion-easing-decelerate': 'cubic-bezier(0, 0, 0, 1)', + '--wpds-motion-easing-decelerate-emphasized': + 'cubic-bezier(0.29, 0, 0, 1)', + '--wpds-motion-easing-gentle': 'cubic-bezier(0.25, 0.1, 0.25, 1)', + '--wpds-motion-easing-standard': 'cubic-bezier(0.4, 0, 0.2, 1)', }; diff --git a/packages/theme/src/prebuilt/js/design-tokens.mjs b/packages/theme/src/prebuilt/js/design-tokens.mjs index 0f037a16be9da6..80bd9ee7be5d40 100644 --- a/packages/theme/src/prebuilt/js/design-tokens.mjs +++ b/packages/theme/src/prebuilt/js/design-tokens.mjs @@ -127,6 +127,15 @@ export default [ '--wpds-elevation-sm', '--wpds-elevation-md', '--wpds-elevation-lg', + '--wpds-motion-duration-xs', + '--wpds-motion-duration-sm', + '--wpds-motion-duration-md', + '--wpds-motion-duration-lg', + '--wpds-motion-duration-xl', + '--wpds-motion-easing-gentle', + '--wpds-motion-easing-standard', + '--wpds-motion-easing-decelerate', + '--wpds-motion-easing-decelerate-emphasized', '--wpds-font-family-heading', '--wpds-font-family-body', '--wpds-font-family-mono', diff --git a/packages/theme/src/prebuilt/ts/token-types.ts b/packages/theme/src/prebuilt/ts/token-types.ts index fdbee9ba0f1367..331c14f69d0551 100644 --- a/packages/theme/src/prebuilt/ts/token-types.ts +++ b/packages/theme/src/prebuilt/ts/token-types.ts @@ -18,6 +18,20 @@ export type GapSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'; */ export type SurfaceWidthSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; +/** + * Size scale for duration tokens. + */ +export type DurationSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +/** + * Easing curve variants. + */ +export type Easing = + | 'gentle' + | 'standard' + | 'decelerate' + | 'decelerate-emphasized'; + /** * Size scale for border radius tokens. */ diff --git a/packages/theme/terrazzo.config.ts b/packages/theme/terrazzo.config.ts index 1543d8233ad72a..cd800cf9e20ac2 100644 --- a/packages/theme/terrazzo.config.ts +++ b/packages/theme/terrazzo.config.ts @@ -22,6 +22,7 @@ export default defineConfig( { './tokens/cursor.json', './tokens/dimension.json', './tokens/elevation.json', + './tokens/motion.json', './tokens/typography.json', ], outDir: './src/prebuilt', @@ -121,6 +122,16 @@ export default defineConfig( { description: 'Size scale for surface width tokens.', patterns: [ /^wpds-dimension\.surface-width\.([^.]+)$/ ], }, + { + name: 'DurationSize', + description: 'Size scale for duration tokens.', + patterns: [ /^wpds-motion\.duration\.([^.]+)$/ ], + }, + { + name: 'Easing', + description: 'Easing curve variants.', + patterns: [ /^wpds-motion\.easing\.([^.]+)$/ ], + }, { name: 'BorderRadiusSize', description: 'Size scale for border radius tokens.', diff --git a/packages/theme/tokens/motion.json b/packages/theme/tokens/motion.json new file mode 100644 index 00000000000000..cfa4b52a7462a6 --- /dev/null +++ b/packages/theme/tokens/motion.json @@ -0,0 +1,46 @@ +{ + "wpds-motion": { + "duration": { + "$type": "duration", + "xs": { + "$value": "50ms", + "$description": "Micro-delays and transition offsets" + }, + "sm": { + "$value": "100ms", + "$description": "Micro-interactions like focus rings and state changes" + }, + "md": { + "$value": "200ms", + "$description": "Standard transitions like menus and popovers" + }, + "lg": { + "$value": "300ms", + "$description": "Deliberate animations like slides and reveals" + }, + "xl": { + "$value": "400ms", + "$description": "Extended animations like complex or multi-step transitions" + } + }, + "easing": { + "$type": "cubicBezier", + "gentle": { + "$value": [ 0.25, 0.1, 0.25, 1 ], + "$description": "Subtle easing for hover, color, and background transitions" + }, + "standard": { + "$value": [ 0.4, 0, 0.2, 1 ], + "$description": "Balanced easing for on-screen movement like resizing, morphing, and layout shifts" + }, + "decelerate": { + "$value": [ 0, 0, 0, 1 ], + "$description": "Decelerating easing for elements entering or exiting the screen, such as menus and popovers" + }, + "decelerate-emphasized": { + "$value": [ 0.29, 0, 0, 1 ], + "$description": "Expressive easing for prominent elements entering or exiting, like dialogs and drawers" + } + } + } +} diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index feb5a187a12d09..153c262fd50707 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -25,6 +25,7 @@ - Extract `useDeprioritizedInitialFocus` shared hook for reuse across overlay components ([#76910](https://github.com/WordPress/gutenberg/pull/76910)). - `Tabs`: Add development-mode validation for Tab/Panel count matching ([#75170](https://github.com/WordPress/gutenberg/pull/75170)). +- `Dialog`, dropdown motion utilities: Use `--wpds-motion-*` design tokens for animation duration and easing ([#76097](https://github.com/WordPress/gutenberg/pull/76097)). ### TypeScript diff --git a/packages/ui/src/dialog/style.module.css b/packages/ui/src/dialog/style.module.css index c2dff0b2fd8d11..8ddcd8584c134a 100644 --- a/packages/ui/src/dialog/style.module.css +++ b/packages/ui/src/dialog/style.module.css @@ -17,7 +17,7 @@ } @media not (prefers-reduced-motion) { - transition: opacity 0.2s ease-out; + transition: opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-gentle); } } @@ -50,13 +50,13 @@ @media not (prefers-reduced-motion) { transition: - opacity 0.2s cubic-bezier(1, 0, 0.2, 1), - transform 0.2s cubic-bezier(1, 0, 0.2, 1); + opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-decelerate-emphasized), + transform var(--wpds-motion-duration-md) var(--wpds-motion-easing-decelerate-emphasized); &[data-open] { transition: - opacity 0.2s cubic-bezier(0.29, 0, 0, 1), - transform 0.2s cubic-bezier(0.29, 0, 0, 1); + opacity var(--wpds-motion-duration-md) var(--wpds-motion-easing-decelerate-emphasized), + transform var(--wpds-motion-duration-md) var(--wpds-motion-easing-decelerate-emphasized); } } diff --git a/packages/ui/src/utils/css/dropdown-motion.module.css b/packages/ui/src/utils/css/dropdown-motion.module.css index b5f1bf423959d8..52e0c9c50ae601 100644 --- a/packages/ui/src/utils/css/dropdown-motion.module.css +++ b/packages/ui/src/utils/css/dropdown-motion.module.css @@ -8,9 +8,9 @@ @layer wp-ui-utilities { .dropdown-motion { --wp-ui-dropdown-slide-distance: 4px; - --wp-ui-dropdown-slide-duration: 200ms; - --wp-ui-dropdown-slide-easing: cubic-bezier(0, 0, 0, 1); - --wp-ui-dropdown-fade-duration: 80ms; + --wp-ui-dropdown-slide-duration: var(--wpds-motion-duration-md); + --wp-ui-dropdown-slide-easing: var(--wpds-motion-easing-decelerate); + --wp-ui-dropdown-fade-duration: var(--wpds-motion-duration-sm); --wp-ui-dropdown-fade-easing: linear; @media not ( prefers-reduced-motion ) { diff --git a/storybook/package-styles/config.js b/storybook/package-styles/config.js index 745d6b1da43585..0faa444864ccb0 100644 --- a/storybook/package-styles/config.js +++ b/storybook/package-styles/config.js @@ -80,6 +80,11 @@ const CONFIG = [ ltr: [ designTokens, componentsLtr, adminUiLtr ], rtl: [ designTokens, componentsRtl, adminUiRtl ], }, + { + componentIdMatcher: /^design-system-tokens-/, + ltr: [ designTokens, componentsLtr ], + rtl: [ designTokens, componentsRtl ], + }, { componentIdMatcher: /^design-system-/, ltr: [ designTokens ], diff --git a/storybook/stories/design-system/tokens/motion.story.module.css b/storybook/stories/design-system/tokens/motion.story.module.css new file mode 100644 index 00000000000000..ab197fc04deedb --- /dev/null +++ b/storybook/stories/design-system/tokens/motion.story.module.css @@ -0,0 +1,41 @@ +@keyframes slideRight { + from { + inset-inline-start: 4px; + } + to { + inset-inline-start: calc(100% - 36px); + } +} + +.label { + font-family: var(--wpds-font-family-mono); + font-size: var(--wpds-font-size-sm); + color: var(--wpds-color-fg-content-neutral); +} + +.description { + font-family: var(--wpds-font-family-body); + font-size: var(--wpds-font-size-xs); + color: var(--wpds-color-fg-content-neutral-weak); +} + +.track { + position: relative; + height: 40px; + border-radius: var(--wpds-border-radius-sm); + background-color: var(--wpds-color-bg-surface-neutral-weak); + overflow: hidden; + flex: 1; +} + +.dot { + position: absolute; + top: 4px; + inset-inline-start: 4px; + width: 32px; + height: 32px; + border-radius: var(--wpds-border-radius-sm); + background-color: var(--wpds-color-bg-interactive-brand-strong); + animation-name: slideRight; + animation-fill-mode: forwards; +} diff --git a/storybook/stories/design-system/tokens/motion.story.tsx b/storybook/stories/design-system/tokens/motion.story.tsx new file mode 100644 index 00000000000000..1fe0b9525e13c0 --- /dev/null +++ b/storybook/stories/design-system/tokens/motion.story.tsx @@ -0,0 +1,209 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Button, SelectControl, TextControl } from '@wordpress/components'; +import { useState, useCallback } from '@wordpress/element'; +import { Stack } from '@wordpress/ui'; + +import styles from './motion.story.module.css'; + +const EASING_TOKENS = [ + { + name: 'gentle', + variable: 'var(--wpds-motion-easing-gentle)', + description: 'Hover, color, and background transitions.', + }, + { + name: 'standard', + variable: 'var(--wpds-motion-easing-standard)', + description: + 'On-screen movement like resizing, morphing, and layout shifts.', + }, + { + name: 'decelerate', + variable: 'var(--wpds-motion-easing-decelerate)', + description: + 'Elements entering or exiting the screen, such as menus and popovers.', + }, + { + name: 'decelerate-emphasized', + variable: 'var(--wpds-motion-easing-decelerate-emphasized)', + description: + 'Prominent elements entering or exiting, like dialogs and drawers.', + }, +]; + +const DURATION_TOKENS = [ + { + name: 'xs', + variable: 'var(--wpds-motion-duration-xs)', + description: 'Micro-delays and transition offsets.', + }, + { + name: 'sm', + variable: 'var(--wpds-motion-duration-sm)', + description: 'Micro-interactions like focus rings and state changes.', + }, + { + name: 'md', + variable: 'var(--wpds-motion-duration-md)', + description: 'Standard transitions like menus and popovers.', + }, + { + name: 'lg', + variable: 'var(--wpds-motion-duration-lg)', + description: 'Deliberate animations like slides and reveals.', + }, + { + name: 'xl', + variable: 'var(--wpds-motion-duration-xl)', + description: + 'Extended animations like complex or multi-step transitions.', + }, +]; + +const DURATION_OPTIONS = [ + { label: 'xs', value: 'var(--wpds-motion-duration-xs)' }, + { label: 'sm', value: 'var(--wpds-motion-duration-sm)' }, + { label: 'md', value: 'var(--wpds-motion-duration-md)' }, + { label: 'lg', value: 'var(--wpds-motion-duration-lg)' }, + { label: 'xl', value: 'var(--wpds-motion-duration-xl)' }, + { label: 'Custom', value: 'custom' }, +]; + +function AnimationRow( { + label, + description, + duration, + easing, + animKey, +}: { + label: string; + description?: string; + duration: string; + easing: string; + animKey: number; +} ) { + return ( + + + { label } + { description && ( + + { description } + + ) } + +
+
+
+ + ); +} + +function MotionDemo() { + const [ animKey, setAnimKey ] = useState( 0 ); + const replay = useCallback( () => setAnimKey( ( k ) => k + 1 ), [] ); + const [ selectedDuration, setSelectedDuration ] = useState( + 'var(--wpds-motion-duration-xl)' + ); + const [ customDuration, setCustomDuration ] = useState( '600' ); + + const easingDuration = + selectedDuration === 'custom' + ? `${ customDuration }ms` + : selectedDuration; + + return ( + +
+ +
+ + +

Easing curves

+ + { + setSelectedDuration( value ); + setAnimKey( ( k ) => k + 1 ); + } } + style={ { minWidth: '180px' } } + /> + { selectedDuration === 'custom' && ( + { + setCustomDuration( value ); + setAnimKey( ( k ) => k + 1 ); + } } + style={ { width: '120px' } } + /> + ) } + + + { EASING_TOKENS.map( ( token ) => ( + + ) ) } + +
+ + +

Durations

+

All using easing-standard

+ + { DURATION_TOKENS.map( ( token ) => ( + + ) ) } + +
+
+ ); +} + +const meta: Meta< typeof MotionDemo > = { + title: 'Design System/Tokens/Motion', + component: MotionDemo, + parameters: { + controls: { hideNoControlsWarning: true }, + docs: { canvas: { sourceState: 'shown' } }, + }, +}; +export default meta; + +export const Default: StoryObj< typeof MotionDemo > = {}; diff --git a/storybook/tsconfig.json b/storybook/tsconfig.json index ff8022c0b75067..42233bea0c4622 100644 --- a/storybook/tsconfig.json +++ b/storybook/tsconfig.json @@ -4,7 +4,8 @@ "compilerOptions": { "jsx": "react-jsx", "rootDir": ".", - "noEmit": true + "noEmit": true, + "types": [ "css-modules" ] }, "include": [ "**/*.tsx", "**/*.ts" ] }