diff --git a/docs/02-design-tokens-reference.md b/docs/02-design-tokens-reference.md index f4c4e4a..12118bc 100644 --- a/docs/02-design-tokens-reference.md +++ b/docs/02-design-tokens-reference.md @@ -1,6 +1,6 @@ # Design Tokens Reference -**Last Updated:** March 5, 2026 +**Last Updated:** March 15, 2026 Complete reference for all design tokens in the Design System Starter Kit. @@ -14,6 +14,7 @@ Complete reference for all design tokens in the Design System Starter Kit. 4. [Colors](#colors) 5. [Borders](#borders) 6. [Focus States](#focus-states) +7. [Motion](#motion) --- @@ -308,6 +309,50 @@ Focus indicators use a dual-outline technique: a primary `outline` for the main --- +## Motion + +### Transition Duration + +Five semantic durations for UI transitions. All are set to `0ms` under `@media (prefers-reduced-motion: reduce)`. + +| Token | CSS Variable | Value | +| ------- | ----------------------------------- | ----- | +| instant | `--dsn-transition-duration-instant` | 0ms | +| fast | `--dsn-transition-duration-fast` | 100ms | +| normal | `--dsn-transition-duration-normal` | 200ms | +| slow | `--dsn-transition-duration-slow` | 350ms | +| slower | `--dsn-transition-duration-slower` | 500ms | + +### Transition Easing + +Five semantic easing curves for consistent motion feel. + +| Token | CSS Variable | Value | +| ------- | --------------------------------- | ---------------------------------- | +| default | `--dsn-transition-easing-default` | `cubic-bezier(0.25, 0.1, 0.25, 1)` | +| enter | `--dsn-transition-easing-enter` | `cubic-bezier(0, 0, 0.2, 1)` | +| exit | `--dsn-transition-easing-exit` | `cubic-bezier(0.4, 0, 1, 1)` | +| move | `--dsn-transition-easing-move` | `cubic-bezier(0.4, 0, 0.2, 1)` | +| linear | `--dsn-transition-easing-linear` | `linear` | + +**Usage in components:** + +```css +.dsn-button { + transition: + background-color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default), + color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default), + border-color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default); +} +``` + +**Reduced motion:** All duration tokens resolve to `0ms` via a central `@media (prefers-reduced-motion: reduce)` block appended to every full CSS configuration — no component-level media queries needed. + +--- + ## Token Statistics **Total Tokens (as of v4.9.0):** diff --git a/docs/05-storybook-configuration.md b/docs/05-storybook-configuration.md index 186206b..865a442 100644 --- a/docs/05-storybook-configuration.md +++ b/docs/05-storybook-configuration.md @@ -1,6 +1,6 @@ # Storybook Configuration -**Last Updated:** March 13, 2026 +**Last Updated:** March 15, 2026 Documentation for the Storybook setup, runtime theme switching, UI components, and documentation structure. @@ -398,6 +398,49 @@ const meta: Meta = { /> ``` +### TokenTable + +**Location:** `packages/storybook/src/components/TokenTable.tsx` + +**Purpose:** Renders a table of design tokens with optional live preview visuals and computed CSS values. + +**Props:** + +| Prop | Type | Default | Description | +| --------------- | ------------- | -------- | ----------------------------------- | +| `tokens` | `Token[]` | — | Array of `{ name, cssVar, value? }` | +| `previewType` | `PreviewType` | `'none'` | Visual preview type | +| `showLiveValue` | `boolean` | `true` | Show computed CSS value | + +**`previewType` values:** + +| Value | Preview | +| ----------------------- | ------------------------------------------------------------- | +| `'color'` | Gekleurd vlak 32×32px | +| `'spacing'` | Horizontale balk (breedte = tokenwaarde) | +| `'typography-size'` | "Aa" in de tokengrootte | +| `'border-radius'` | Gevuld vlak 40×40px met border-radius toegepast | +| `'shadow'` | Kaartje met `box-shadow` toegepast | +| `'transition-duration'` | Animerende sweep-balk (animationDuration = token) | +| `'transition-easing'` | Stip op track (animationTimingFunction = token, vaste 1200ms) | +| `'none'` | Geen preview kolom | + +Alle kleur-previews gebruiken `--dsn-color-accent-1-inverse-bg-default` als accentkleur. + +### TocLink + +**Location:** `packages/storybook/src/components/TocLink.tsx` + +**Purpose:** Anchor-navigatie binnen een Storybook docs-pagina zonder de Storybook URL te breken. + +Storybook gebruikt `?path=`-gebaseerde URL-routing. Een gewone `` vervangt de hele URL en laat de sidebar verdwijnen. `TocLink` onderschept de klik via `e.preventDefault()` en roept `document.getElementById(targetId)?.scrollIntoView({ behavior: 'smooth' })` aan. + +```tsx +Colors +// rendert als: Colors +// klik: scrollt naar element met id="colors", geen URL-verandering +``` + --- ## Documentation Structure diff --git a/docs/changelog.md b/docs/changelog.md index b267259..b10d5d9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,20 @@ All notable changes to this project are documented in this file. --- +## Version 5.8.0 (March 15, 2026) + +### Motion tokens: transition duration & easing (PR #85) + +- **10 nieuwe design tokens** — 5 duration (`instant/fast/normal/slow/slower`) en 5 easing (`default/enter/exit/move/linear`) in `base.json` van het Start-thema +- **Centrale `prefers-reduced-motion` media query** — toegevoegd via `build.js` post-processing aan alle 8 volledige CSS-configuraties; alle duration tokens worden `0ms`; geen component-level media queries nodig +- **5 component CSS-bestanden bijgewerkt** — hardcoded `0.2s ease` vervangen door `var(--dsn-transition-duration-normal)` en `var(--dsn-transition-easing-default)` in `button.css`, `link.css`, `text-input.css`, `text-area.css`, `details.css` +- **Storybook Design Tokens pagina uitgebreid** — Motion-sectie toegevoegd onderaan met `## Motion`, `### Transition Duration` en `### Transition Easing` tabellen +- **`TokenTable` nieuwe `previewType` waarden** — `transition-duration` (animated sweep bar) en `transition-easing` (dot op track) tonen live animatie-previews in de documentatie +- **Navigatie-index** toegevoegd aan de Design Tokens pagina — genummerde `OrderedList` met `TocLink` componenten (gebruikt `scrollIntoView` i.p.v. URL-navigatie zodat de Storybook sidebar intact blijft) +- **Preview kleuren** geüniformeerd — spacing, border-radius en motion previews gebruiken allemaal `--dsn-color-accent-1-inverse-bg-default` + +--- + ## Version 5.7.0 (March 13, 2026) ### Body component: document-level cascade basisstijlen (PR #84, issue #83) diff --git a/packages/components-html/src/button/button.css b/packages/components-html/src/button/button.css index 535b1a8..68ee0e0 100644 --- a/packages/components-html/src/button/button.css +++ b/packages/components-html/src/button/button.css @@ -23,9 +23,12 @@ cursor: pointer; user-select: none; transition: - background-color 0.2s ease, - color 0.2s ease, - border-color 0.2s ease; + background-color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default), + color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default), + border-color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default); /* Accessibility - WCAG pointer target */ min-block-size: var(--dsn-button-min-block-size); diff --git a/packages/components-html/src/details/details.css b/packages/components-html/src/details/details.css index 7384f21..6fb3c0d 100644 --- a/packages/components-html/src/details/details.css +++ b/packages/components-html/src/details/details.css @@ -103,7 +103,8 @@ width: var(--dsn-details-icon-size); height: var(--dsn-details-icon-size); color: var(--dsn-details-summary-color); - transition: transform 0.2s ease; + transition: transform var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default); } .dsn-details[open] .dsn-details__icon { diff --git a/packages/components-html/src/link/link.css b/packages/components-html/src/link/link.css index 30fdbea..434b0bb 100644 --- a/packages/components-html/src/link/link.css +++ b/packages/components-html/src/link/link.css @@ -20,8 +20,10 @@ /* Interaction */ cursor: pointer; transition: - color 0.2s ease, - text-decoration-color 0.2s ease; + color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default), + text-decoration-color var(--dsn-transition-duration-normal) + var(--dsn-transition-easing-default); } /* Icon sizing */ diff --git a/packages/components-html/src/text-area/text-area.css b/packages/components-html/src/text-area/text-area.css index 3829aeb..4ec8032 100644 --- a/packages/components-html/src/text-area/text-area.css +++ b/packages/components-html/src/text-area/text-area.css @@ -43,8 +43,8 @@ /* Transitions */ transition-property: border-color, box-shadow, background-color; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; + transition-duration: var(--dsn-transition-duration-normal); + transition-timing-function: var(--dsn-transition-easing-default); } /* Placeholder */ diff --git a/packages/components-html/src/text-input/text-input.css b/packages/components-html/src/text-input/text-input.css index ebf752e..edb5633 100644 --- a/packages/components-html/src/text-input/text-input.css +++ b/packages/components-html/src/text-input/text-input.css @@ -40,8 +40,8 @@ /* Transitions */ transition-property: border-color, box-shadow, background-color; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; + transition-duration: var(--dsn-transition-duration-normal); + transition-timing-function: var(--dsn-transition-easing-default); } /* Placeholder */ diff --git a/packages/design-tokens/src/config/build.js b/packages/design-tokens/src/config/build.js index 40aa363..16a2bea 100644 --- a/packages/design-tokens/src/config/build.js +++ b/packages/design-tokens/src/config/build.js @@ -58,6 +58,36 @@ fullConfigNames.forEach((name, index) => { console.log(`\n✅ Built ${fullConfigNames.length} full configurations\n`); +// ============================================================================= +// APPEND PREFERS-REDUCED-MOTION MEDIA QUERY +// ============================================================================= + +console.log( + '🎬 Appending prefers-reduced-motion media query to CSS files...\n' +); + +const reducedMotionBlock = ` +@media (prefers-reduced-motion: reduce) { + :root { + --dsn-transition-duration-instant: 0ms; + --dsn-transition-duration-fast: 0ms; + --dsn-transition-duration-normal: 0ms; + --dsn-transition-duration-slow: 0ms; + --dsn-transition-duration-slower: 0ms; + } +} +`; + +fullConfigNames.forEach((name) => { + const cssFilePath = path.join(__dirname, '..', '..', `dist/css/${name}.css`); + if (fs.existsSync(cssFilePath)) { + fs.appendFileSync(cssFilePath, reducedMotionBlock); + console.log(` ✅ dist/css/${name}.css`); + } +}); + +console.log('\n✅ prefers-reduced-motion media query appended\n'); + // ============================================================================= // BUILD SCOPED CONFIGURATIONS (for runtime switching) // ============================================================================= diff --git a/packages/design-tokens/src/tokens/themes/start/base.json b/packages/design-tokens/src/tokens/themes/start/base.json index d05a0cc..bf79180 100644 --- a/packages/design-tokens/src/tokens/themes/start/base.json +++ b/packages/design-tokens/src/tokens/themes/start/base.json @@ -530,6 +530,52 @@ "value": "0 4px 8px 0 {dsn.color.shadow.direct}, 0 16px 32px 0 {dsn.color.shadow.ambient}, 0 0 0 1px {dsn.color.shadow.highlight}", "comment": "Grote elevatie — modals, dialogen, side panels" } + }, + "transition": { + "duration": { + "instant": { + "value": "0ms", + "comment": "State-changes die onmiddellijk moeten aanvoelen" + }, + "fast": { + "value": "100ms", + "comment": "Subtiele hover-states, kleur-transitions" + }, + "normal": { + "value": "200ms", + "comment": "Standaard UI-transitions" + }, + "slow": { + "value": "350ms", + "comment": "Grotere bewegingen, accordions" + }, + "slower": { + "value": "500ms", + "comment": "Complexe animaties, page-level transitions" + } + }, + "easing": { + "default": { + "value": "cubic-bezier(0.25, 0.1, 0.25, 1)", + "comment": "Algemeen — equivalent van CSS ease" + }, + "enter": { + "value": "cubic-bezier(0, 0, 0.2, 1)", + "comment": "Elementen die het scherm binnenkomen (decelereren)" + }, + "exit": { + "value": "cubic-bezier(0.4, 0, 1, 1)", + "comment": "Elementen die het scherm verlaten (accelereren)" + }, + "move": { + "value": "cubic-bezier(0.4, 0, 0.2, 1)", + "comment": "Elementen die van positie wisselen" + }, + "linear": { + "value": "linear", + "comment": "Progress bars, loaders" + } + } } } } diff --git a/packages/storybook/src/DesignTokens.mdx b/packages/storybook/src/DesignTokens.mdx index c4df302..a585db6 100644 --- a/packages/storybook/src/DesignTokens.mdx +++ b/packages/storybook/src/DesignTokens.mdx @@ -1,6 +1,8 @@ import { Meta } from '@storybook/blocks'; import { TokenTable } from './components/TokenTable'; import { TokenControls } from './components/TokenControls'; +import { OrderedList } from '@dsn/components-react'; +import { TocLink } from './components/TocLink'; @@ -12,6 +14,21 @@ Design tokens are the single source of truth for colors, typography, spacing, bo --- + +
  • Colors
  • +
  • Inverse Colors
  • +
  • Typography
  • +
  • Spacing
  • +
  • Borders
  • +
  • Focus Indicators
  • +
  • Icon Sizes
  • +
  • Pointer Target Sizing
  • +
  • Box Shadows
  • +
  • Motion
  • +
    + +--- + ## Colors Each color scale provides a full set of tokens for backgrounds, borders, and text — including document, elevated, subtle, default, hover, and active states. The **elevated** token is intended for floating UI layers (modals, popovers, dropdowns) that appear above the document surface. @@ -675,3 +692,37 @@ The three primitives that power all elevation shadows. In light mode `highlight` { name: 'lg — Modals, dialogs', cssVar: '--dsn-box-shadow-lg' }, ]} /> + +--- + +## Motion + +Motion tokens beheren timing en easing op één centrale plek. Alle componenten gebruiken deze tokens — waardoor `prefers-reduced-motion` automatisch en consistent wordt gerespecteerd. + +Bij `prefers-reduced-motion: reduce` worden alle duration-tokens naar `0ms` gezet via een centrale media query in het theme CSS-bestand. Componenten hoeven zelf geen media query te implementeren. + +### Transition Duration + + + +### Transition Easing + + diff --git a/packages/storybook/src/components/TocLink.tsx b/packages/storybook/src/components/TocLink.tsx new file mode 100644 index 0000000..492a186 --- /dev/null +++ b/packages/storybook/src/components/TocLink.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Link } from '@dsn/components-react'; + +interface TocLinkProps { + targetId: string; + children: React.ReactNode; +} + +export function TocLink({ targetId, children }: TocLinkProps) { + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + document.getElementById(targetId)?.scrollIntoView({ behavior: 'smooth' }); + }; + + return ( + + {children} + + ); +} diff --git a/packages/storybook/src/components/TokenTable.tsx b/packages/storybook/src/components/TokenTable.tsx index b91abda..4245f3a 100644 --- a/packages/storybook/src/components/TokenTable.tsx +++ b/packages/storybook/src/components/TokenTable.tsx @@ -19,6 +19,8 @@ type PreviewType = | 'typography-size' | 'border-radius' | 'shadow' + | 'transition-duration' + | 'transition-easing' | 'none'; interface TokenTableProps { @@ -69,7 +71,7 @@ function SpacingPreview({ cssVar }: { cssVar: string }) { minWidth: 2, maxWidth: 200, borderRadius: 2, - background: 'var(--dsn-color-action-1-bg-default, #3366cc)', + background: 'var(--dsn-color-accent-1-inverse-bg-default, #3366cc)', }} /> ); @@ -96,8 +98,7 @@ function BorderRadiusPreview({ cssVar }: { cssVar: string }) { width: 40, height: 40, borderRadius: `var(${cssVar})`, - border: '2px solid var(--dsn-color-action-1-border-default, #3366cc)', - background: 'var(--dsn-color-action-1-bg-default, #e8eef5)', + background: 'var(--dsn-color-accent-1-inverse-bg-default, #3366cc)', }} /> ); @@ -127,6 +128,91 @@ function ShadowPreview({ cssVar }: { cssVar: string }) { ); } +function TransitionDurationPreview({ cssVar }: { cssVar: string }) { + return ( + <> + +
    +
    +
    + + ); +} + +function TransitionEasingPreview({ cssVar }: { cssVar: string }) { + return ( + <> + +
    +
    +
    +
    + + ); +} + function Preview({ type, cssVar }: { type: PreviewType; cssVar: string }) { switch (type) { case 'color': @@ -139,6 +225,10 @@ function Preview({ type, cssVar }: { type: PreviewType; cssVar: string }) { return ; case 'shadow': return ; + case 'transition-duration': + return ; + case 'transition-easing': + return ; case 'none': return null; }