From 13c3433058c18a4d4ebc6eec97891e65280b905e Mon Sep 17 00:00:00 2001 From: Une Sofie Kinn Ekroll Date: Tue, 8 Oct 2024 15:10:02 +0200 Subject: [PATCH] test(storybook): support running visual tests in Chromatic To better make use of visual tests: - "Avatar/In Dropdown" story renders with the dropdown open - Modals, comboboxes, dropdowns, and popovers are now opened automatically (when not in docs) through interaction tests - SkipLink: added a story showing the SkipLink when it has become visible - Switch: added examples of enabled and hovered switches - Added a default 1rem padding to stories, to account for normally overflowing content like focus styles, floating badges etc - Several stories have custom styling to ensure elements that have been removed from the normal layout flow (e.g. with absolute positioning) or are rendered outside the Story root element (e.g. with a React portal) are actually visible in the snapshots. To easier facilitate this, a global customStylesDecorator has been added, which adds styles configured through `parameters.customStyles`. This commit also replaces story-specific decorators which only added styling with `parameter.customStyles`, simplifying the stories a bit. Visual tests on 320px wide screen revealed a bug where Combobox was wider than the screen. This has been fixed. With the changes that open modals, add pseudo states etc, some new accessibility violations surfaced. - Fixed axe violation `svg-img-alt` in Dropdown stories - Disabled axe rule `color-contrast` when the pseudo-class :active is emulated through `storybook-addon-pseudo-states`, since we concluded that 4.5:1 text contrast during press actions is unnecessary Chromatic is run automatically for pull requests through Github Actions -- see `.github/workflows/test.yml`. If you want to trigger visual tests from your own machine, add (or edit) the file `apps/storybook/.env`. This file is in .gitignore and will not be committed. Add the following to `.env`: ``` CHROMATIC_PROJECT_TOKEN= ``` Replace `` with the token found [here](https://www.chromatic.com/manage?appId=66fe736b9d639fe6801bf130&setup=true), under "Setup Chromatic with this project token". Then run these commands: ``` yarn build:storybook yarn chromatic ``` You can also replace the last command with e.g. ``` yarn chromatic --no-only-changed --only-story-names "Komponenter/Modal/*" ``` ...to only run tests for the Modal components --- .changeset/slimy-buttons-train.md | 5 + .github/workflows/test.yml | 14 ++ ...book-addon-a11y-npm-8.3.4-1c07bc384c.patch | 20 +++ apps/storybook/.env.example | 11 ++ apps/storybook/.storybook/main.ts | 1 + apps/storybook/.storybook/preview.tsx | 23 ++- apps/storybook/.storybook/test-runner.ts | 4 +- apps/storybook/chromatic.config.json | 8 + apps/storybook/package.json | 5 +- .../story-utils/customStylesDecorator.tsx | 54 +++++++ apps/storybook/story-utils/modes.ts | 6 + .../story-utils/type-extensions.d.ts | 147 ++++++++++++++++++ package.json | 5 +- packages/css/combobox.css | 1 + .../src/components/Avatar/Avatar.stories.tsx | 30 ++-- .../react/src/components/Button/Button.mdx | 4 +- .../src/components/Button/Button.stories.tsx | 121 +++++++------- .../src/components/Card/Card.stories.tsx | 34 ++-- .../src/components/Chip/Chip.stories.tsx | 12 +- .../components/Dropdown/Dropdown.stories.tsx | 33 +++- .../ErrorSummary/ErrorSummary.stories.tsx | 23 ++- .../components/HelpText/HelpText.stories.tsx | 29 ++-- .../src/components/Link/Link.stories.tsx | 11 +- .../src/components/List/List.stories.tsx | 13 +- .../src/components/Modal/Modal.stories.tsx | 51 ++++-- .../components/Popover/Popover.stories.tsx | 119 +++++++++----- .../components/SkipLink/SkipLink.stories.tsx | 27 +++- .../src/components/Table/Table.stories.tsx | 10 +- .../react/src/components/Tag/Tag.stories.tsx | 12 +- .../components/Tooltip/Tooltip.stories.tsx | 15 +- .../form/Combobox/Combobox.stories.tsx | 77 +++++++-- .../components/form/Switch/Switch.stories.tsx | 26 +++- .../form/Textarea/Textarea.stories.tsx | 12 +- .../loaders/Spinner/Spinner.stories.tsx | 24 +-- .../useDebounceCallback.stories.tsx | 1 + .../useMediaQuery/useMediaQuery.stories.tsx | 1 + .../useSynchronizedAnimation.stories.tsx | 29 ++-- packages/react/stories/showcase.stories.tsx | 9 ++ packages/react/stories/testing.stories.tsx | 18 +-- packages/react/tsconfig.json | 7 +- yarn.lock | 48 +++++- 41 files changed, 799 insertions(+), 301 deletions(-) create mode 100644 .changeset/slimy-buttons-train.md create mode 100644 .yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch create mode 100644 apps/storybook/.env.example create mode 100644 apps/storybook/chromatic.config.json create mode 100644 apps/storybook/story-utils/customStylesDecorator.tsx create mode 100644 apps/storybook/story-utils/modes.ts create mode 100644 apps/storybook/story-utils/type-extensions.d.ts diff --git a/.changeset/slimy-buttons-train.md b/.changeset/slimy-buttons-train.md new file mode 100644 index 0000000000..06e0a25d73 --- /dev/null +++ b/.changeset/slimy-buttons-train.md @@ -0,0 +1,5 @@ +--- +'@digdir/designsystemet-css': patch +--- + +Combobox: fix overflow on screens narrower than ~340px diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72c0ad6324..aee56beeca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # Necessary for Chromatic + fetch-depth: 0 - uses: ./.github/actions/gh-setup - name: Build run: yarn build - name: Types run: yarn types:react + - name: Test run: yarn test - name: 'Report Coverage' @@ -36,8 +40,10 @@ jobs: check_name: Unit Test Report check_annotations: true check_title_template: '{{FILE_NAME}} / {{TEST_NAME}}' + - name: Test CLI (create tokens, then build the theme) run: yarn test:cli + - name: Install Playwright run: npx playwright install --with-deps - name: Build storybook @@ -61,3 +67,11 @@ jobs: check_name: Storybook Test Report check_annotations: true check_title_template: '{{FILE_NAME}} / {{TEST_NAME}}' + + - name: Run Chromatic (visual and interaction tests) + uses: chromaui/action@latest + with: + workingDir: apps/storybook + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + exitZeroOnChanges: true + autoAcceptChanges: '{main,next}' diff --git a/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch b/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch new file mode 100644 index 0000000000..98cf5abcc4 --- /dev/null +++ b/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch @@ -0,0 +1,20 @@ +diff --git a/dist/preview.js b/dist/preview.js +index 2ebc8126dbb113e1d43f39f70f0ef9016b96f53f..9392d52eff52f083ced74c93b68680dc718f741a 100644 +--- a/dist/preview.js ++++ b/dist/preview.js +@@ -3,4 +3,4 @@ + var previewApi = require('storybook/internal/preview-api'); + var global = require('@storybook/global'); + +-var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global.global,channel=previewApi.addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelector(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); ++var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global.global,channel=previewApi.addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelectorAll(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); +diff --git a/dist/preview.mjs b/dist/preview.mjs +index 6e52b1b0b1c137af769e84a59ca32a0e8c91bbb7..dee1c98bfde648dd5f63037f16e954ef69913bb8 100644 +--- a/dist/preview.mjs ++++ b/dist/preview.mjs +@@ -1,4 +1,4 @@ + import { addons } from 'storybook/internal/preview-api'; + import { global } from '@storybook/global'; + +-var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global,channel=addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelector(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); ++var ADDON_ID="storybook/a11y";var RESULT=`${ADDON_ID}/result`,REQUEST=`${ADDON_ID}/request`,RUNNING=`${ADDON_ID}/running`,ERROR=`${ADDON_ID}/error`,MANUAL=`${ADDON_ID}/manual`,EVENTS={RESULT,REQUEST,RUNNING,ERROR,MANUAL};var{document}=global,channel=addons.getChannel(),active=!1,activeStoryId,defaultParameters={config:{},options:{}},handleRequest=async(storyId,input)=>{input?.manual||await run(storyId,input??defaultParameters);},run=async(storyId,input=defaultParameters)=>{activeStoryId=storyId;try{if(!active){active=!0,channel.emit(EVENTS.RUNNING);let{default:axe}=await import('axe-core'),{element="#storybook-root",config,options={}}=input,htmlElement=document.querySelectorAll(element);if(!htmlElement)return;axe.reset(),config&&axe.configure(config);let result=await axe.run(htmlElement,options),resultJson=JSON.parse(JSON.stringify(result));activeStoryId===storyId?channel.emit(EVENTS.RESULT,resultJson):(active=!1,run(activeStoryId));}}catch(error){channel.emit(EVENTS.ERROR,error);}finally{active=!1;}};channel.on(EVENTS.REQUEST,handleRequest);channel.on(EVENTS.MANUAL,run); diff --git a/apps/storybook/.env.example b/apps/storybook/.env.example new file mode 100644 index 0000000000..498893f50e --- /dev/null +++ b/apps/storybook/.env.example @@ -0,0 +1,11 @@ + +## +## This file contains examples of useful environment variables for the Storybook project. +## To actually use them, create a .env file in this directory, and set the desired +## environment variables. That file is in .gitignore and will never be commited. +## + +# To run Chromatic locally, set this var and replace with the token from +# https://www.chromatic.com/manage?appId=66fe736b9d639fe6801bf130&view=configure +# ...under "Setup Chromatic with this project token". +CHROMATIC_PROJECT_TOKEN= diff --git a/apps/storybook/.storybook/main.ts b/apps/storybook/.storybook/main.ts index c642868661..f3686651f6 100644 --- a/apps/storybook/.storybook/main.ts +++ b/apps/storybook/.storybook/main.ts @@ -38,6 +38,7 @@ const config: StorybookConfig = { getAbsolutePath('@chromatic-com/storybook'), getAbsolutePath('@storybook/addon-storysource'), '@storybook/addon-themes', + 'storybook-addon-pseudo-states', ], staticDirs: ['../assets'], framework: { diff --git a/apps/storybook/.storybook/preview.tsx b/apps/storybook/.storybook/preview.tsx index 9a127c6920..4fce15c045 100644 --- a/apps/storybook/.storybook/preview.tsx +++ b/apps/storybook/.storybook/preview.tsx @@ -8,10 +8,11 @@ import type { Preview } from '@storybook/react'; import type { LinkProps } from '@digdir/designsystemet-react'; import { Link, List, Paragraph, Table } from '@digdir/designsystemet-react'; +import { customStylesDecorator } from '../story-utils/customStylesDecorator'; +import { allModes, viewportWidths } from '../story-utils/modes'; import customTheme from './customTheme'; const viewports: Record = {}; -const viewportWidths = [320, 375, 576, 768, 992, 1200, 1440]; for (const width of viewportWidths) { viewports[`${width}px`] = { @@ -143,10 +144,30 @@ const preview: Preview = { viewport: { viewports, }, + chromatic: { + modes: { + mobile: allModes[320], + desktop: allModes[1200], + }, + }, backgrounds: { disable: true, }, + a11y: { + element: ['#storybook-root', '[data-floating-ui-portal]'], + config: { + rules: [ + { + // Ignore the color-contrast rule for the ":active" pseudo-state + id: 'color-contrast', + selector: + '#storybook-root:not(.pseudo-active-all) *:not(.pseudo-active)', + }, + ], + }, + }, }, + decorators: [customStylesDecorator], }; /* Add this back when https://github.com/storybookjs/storybook/issues/29189 is fixed */ diff --git a/apps/storybook/.storybook/test-runner.ts b/apps/storybook/.storybook/test-runner.ts index c76f512342..ad4ccffd61 100644 --- a/apps/storybook/.storybook/test-runner.ts +++ b/apps/storybook/.storybook/test-runner.ts @@ -23,7 +23,7 @@ const config: TestRunnerConfig = { */ await configureAxe(page, { - // Apply story-level a11y rules + // Apply a11y rules set through parameters (global, component or story level) rules: storyContext.parameters?.a11y?.config?.rules, }); @@ -31,7 +31,7 @@ const config: TestRunnerConfig = { if (!isA11yDisabled) { await checkA11y( page, - '#storybook-root', + storyContext.parameters?.a11y?.element ?? ['#storybook-root'], { detailedReport: true, detailedReportOptions: { diff --git a/apps/storybook/chromatic.config.json b/apps/storybook/chromatic.config.json new file mode 100644 index 0000000000..63ed44cf91 --- /dev/null +++ b/apps/storybook/chromatic.config.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://www.chromatic.com/config-file.schema.json", + "onlyChanged": true, + "projectId": "Project:66fe736b9d639fe6801bf130", + "storybookBaseDir": "apps/storybook", + "storybookBuildDir": "apps/storybook/dist", + "zip": true +} diff --git a/apps/storybook/package.json b/apps/storybook/package.json index 8d44d205c8..1fbeb38b86 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -7,7 +7,8 @@ "build": "storybook build -o ./dist", "static-storybook": "npx http-server dist --port 6006 --silent", "test-storybook": "rimraf test-report.xml && wait-on tcp:6006 && test-storybook --junit", - "run-and-test-storybook": "concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'yarn static-storybook' 'yarn test-storybook'" + "run-and-test-storybook": "concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'yarn static-storybook' 'yarn test-storybook'", + "chromatic": "npx chromatic" }, "author": "", "license": "ISC", @@ -22,7 +23,7 @@ "@digdir/designsystemet-css": "workspace:^", "@digdir/designsystemet-react": "workspace:^", "@digdir/designsystemet-theme": "workspace:^", - "@storybook/addon-a11y": "^8.3.4", + "@storybook/addon-a11y": "patch:@storybook/addon-a11y@npm%3A8.3.4#~/.yarn/patches/@storybook-addon-a11y-npm-8.3.4-1c07bc384c.patch", "@storybook/addon-essentials": "^8.3.4", "@storybook/addon-interactions": "^8.3.4", "@storybook/addon-links": "^8.3.4", diff --git a/apps/storybook/story-utils/customStylesDecorator.tsx b/apps/storybook/story-utils/customStylesDecorator.tsx new file mode 100644 index 0000000000..fb9f9fdda3 --- /dev/null +++ b/apps/storybook/story-utils/customStylesDecorator.tsx @@ -0,0 +1,54 @@ +import type { Decorator } from '@storybook/react'; + +/** + * This decorator is used to customize the style of the root story element. + * It is useful for customizing the layout, or when you need to account + * for elements that would otherwise not be visible in Chromatic's visual snapshots. + * + * The decorator is added globally, and can be configured through `parameters.customStyles` + * at the meta or story level. E.g. + * ```ts + * parameters: { + * customStyles: { + * // These apply both in docs mode and story mode + * display: 'flex', + * gap: '8px', + * docs: { + * // These apply only when the story renders in a docs page + * height: '200px' + * }, + * story: { + * // These apply only when the story is viewed individually + * height: '100vh' + * } + * } + * } + * ``` + * + * By default, the decorator sets `overflow: hidden` so you can see in Storybook exactly + * what Chromatic's snapshot will be, and `padding: 1rem` to account for most overflowing + * elements like focus styles, badges etc. + * + * From Chromatic's documentation: + * > Snapshots can sometimes exclude outline and other focus styles because Chromatic + * > trims each snapshot to the dimensions of the root node of the story. To capture + * > those styles, wrap the story in a decorator that adds slight padding. + */ +export const customStylesDecorator: Decorator = (Story, ctx) => { + const { docs, story, ...style } = ctx.parameters.customStyles ?? {}; + + return ( +
+ +
+ ); +}; diff --git a/apps/storybook/story-utils/modes.ts b/apps/storybook/story-utils/modes.ts new file mode 100644 index 0000000000..7f50b65c74 --- /dev/null +++ b/apps/storybook/story-utils/modes.ts @@ -0,0 +1,6 @@ +import { fromPairs } from 'ramda'; +export const viewportWidths = [320, 375, 576, 768, 992, 1200, 1440] as const; + +export const allModes = fromPairs( + viewportWidths.map((width) => [width, { viewport: { width } }]), +); diff --git a/apps/storybook/story-utils/type-extensions.d.ts b/apps/storybook/story-utils/type-extensions.d.ts new file mode 100644 index 0000000000..07e3899186 --- /dev/null +++ b/apps/storybook/story-utils/type-extensions.d.ts @@ -0,0 +1,147 @@ +import type {} from '@storybook/types'; +import type { ElementContext, Spec } from 'axe-core'; +import type { configureAxe } from 'axe-playwright'; +import type { CSSProperties } from 'react'; + +type AxeConfig = Parameters[1]; + +type ChromaticViewport = { + width?: number | `${string}px`; + height?: number | `${string}px`; +}; + +declare module '@storybook/types' { + type PseudoState = + | 'hover' + | 'active' + | 'focusVisible' + | 'focusWithin' + | 'focus' + | 'visited' + | 'link' + | 'target'; + + type PseudoValue = boolean | string | string[]; + + interface Parameters { + /** + * Set custom styling for the story's root element. The default styling is: + * ```css + * { overflow: hidden; padding: 1rem; } + * ``` + * + * This is a custom parameter, implemented by `customStylesDecorator.ts`. + * */ + customStyles?: CSSProperties & { + /** Styles that only apply when viewing a docs page */ + docs?: CSSProperties; + /** Styles that only apply when viewing an individual story */ + story?: CSSProperties; + }; + + /** + * Set the story layout. + * + * This is a standard Storybook parameter, + * [see the docs](https://storybook.js.org/docs/configure/story-layout) + */ + layout?: 'centered' | 'fullscreen' | 'padded'; + + /** + * Configure `@storybook/addon-a11y`. See [the documentation](https://storybook.js.org/addons/@storybook/addon-a11y) + */ + a11y?: { + disable?: boolean; + element?: ElementContext; + config?: Spec; + manual?: boolean; + }; + + /** + * Configure Chromatic. See [the documentation](https://www.chromatic.com/docs/config-with-story-params/). + */ + chromatic?: { + /** Disable visual snapshots at the component or story level */ + disableSnapshot?: true; + /** + * By default, CSS animations are paused at the end of their animation cycle + * when tests are run in Chromatic. Setting this to false will pause animations + * at the first frame instead. + */ + pauseAnimationAtEnd?: false; + /** Delay in ms before running tests in Chromatic */ + delay?: number; + /** + * Allows you to fine-tune the threshold for visual change between snapshots before + * Chromatic flags them. Must be a number from 0 to 1. 0 is the most accurate, while + * 1 is the least accurate. + * + * @default 0.063 + */ + diffThreshold?: number; + /** + * Modes allow separate snapshots and baselines for a collection + * of parameters like viewport size, theme etc. + */ + modes?: Record< + string, + { + /** + * Disable a mode that has been enabled at a higher level. + * E.g. disable a global mode for a specific story. + **/ + disable?: true; + /** + * The viewport to use. + * + * This parameter can either be an object with height and/or width (in px), or + * the name of one of the viewports configured in `parameters.viewports` in `.storybook/preview.tsx` + */ + viewport?: ChromaticViewport | string; + // ...any other globals from Storybook, addons or decorators which we want + // to use in modes can also be added here + } + >; + }; + + /** + * Toggle pseudo states. Supported states are listed in {@link PseudoState}. + * Read [Storybook Pseudo States documentation](https://github.com/chromaui/storybook-addon-pseudo-states) + * for more info. + * + * Each state can be toggled on/off: + * ```ts + * export const Hover = () => + * Hover.parameters = { pseudo: { hover: true } } + * ``` + * + * You can also use CSS selectors to target the elements you want to enable the state for: + * ```ts + * export const Buttons = () => ( + * <> + * + * + * + * + * ) + * Buttons.parameters = { + * pseudo: { + * hover: ["#one", "#two", "#three"], + * focus: ["#two", "#three"], + * active: "#three", + * }, + * } + * ``` + */ + pseudo?: { + /** + * If you need to render elements outside Storybook's root element, you can set + * rootSelector to override it. This is convenient for portals, dialogs, tooltips, etc. + * @default "#storybook-root" + */ + rootSelector?: string; + } & { + [K in PseudoState]?: PseudoValue; + }; + } +} diff --git a/package.json b/package.json index f842b55ba9..11700ba30e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "types:react": "yarn workspace @digdir/designsystemet-react types", "types:storefront": "yarn workspace storefront types", "version-packages": "changeset version", - "publish": "yarn build && changeset publish" + "publish": "yarn build && changeset publish", + "chromatic": "yarn workspace @designsystemet/storybook chromatic" }, "devDependencies": { "@biomejs/biome": "1.8.3", @@ -46,8 +47,10 @@ "@vitejs/plugin-react-swc": "^3.7.0", "@vitest/coverage-v8": "^2.0.5", "@vitest/expect": "^2.0.5", + "chromatic": "^11.11.0", "copyfiles": "^2.4.1", "prettier": "^3.3.3", + "storybook-addon-pseudo-states": "^4.0.2", "stylelint": "^16.8.1", "stylelint-config-standard": "^36.0.1", "typescript": "^5.5.4", diff --git a/packages/css/combobox.css b/packages/css/combobox.css index bbc7420ca2..704013e5bb 100644 --- a/packages/css/combobox.css +++ b/packages/css/combobox.css @@ -40,6 +40,7 @@ .ds-combobox__input__wrapper .ds-combobox__input { height: 100%; min-width: 50px; + width: 100%; flex-grow: 1; appearance: none; border: none; diff --git a/packages/react/src/components/Avatar/Avatar.stories.tsx b/packages/react/src/components/Avatar/Avatar.stories.tsx index bdceb4b65d..3c1f80c25a 100644 --- a/packages/react/src/components/Avatar/Avatar.stories.tsx +++ b/packages/react/src/components/Avatar/Avatar.stories.tsx @@ -7,26 +7,18 @@ import { Badge, Dropdown } from '../'; type Story = StoryFn; -const meta: Meta = { +const meta: Meta = { title: 'Komponenter/Avatar', component: Avatar, parameters: { layout: 'padded', + customStyles: { + display: 'flex', + gap: 'var(--ds-spacing-2)', + justifyContent: 'center', + alignItems: 'center', + }, }, - decorators: [ - (Story) => ( -
- -
- ), - ], }; export default meta; @@ -101,7 +93,7 @@ export const InDropdown: Story = () => ( Velg Profil - + Alle kontoer @@ -122,6 +114,12 @@ export const InDropdown: Story = () => ( ); +InDropdown.parameters = { + layout: 'fullscreen', + customStyles: { + height: '320px', + }, +}; export const AsLink: Story = () => ( diff --git a/packages/react/src/components/Button/Button.mdx b/packages/react/src/components/Button/Button.mdx index f547a35a39..117f245aa9 100644 --- a/packages/react/src/components/Button/Button.mdx +++ b/packages/react/src/components/Button/Button.mdx @@ -54,11 +54,11 @@ Knappene kommer i to hovedfarger `accent` og `neutral`. Hvis du har overstyrt fa - `accent`-fargen stikker seg mer ut enn `neutral` og brukes gjerne når handlingen skal ha oppmerksomhet. - + - `neutral`-fargen er mer nøytral og kan brukes når det er flere knapper på en side som ikke skal ta så mye oppmerksomhet. - + ### Danger diff --git a/packages/react/src/components/Button/Button.stories.tsx b/packages/react/src/components/Button/Button.stories.tsx index 3ee84be702..28417350bc 100644 --- a/packages/react/src/components/Button/Button.stories.tsx +++ b/packages/react/src/components/Button/Button.stories.tsx @@ -1,4 +1,3 @@ -import { Stack } from '@doc-components'; import { ArrowForwardIcon, ArrowRightIcon, @@ -8,15 +7,12 @@ import { ExternalLinkIcon, PencilWritingIcon, PlusCircleIcon, - PlusIcon, PrinterSmallIcon, TrashIcon, } from '@navikt/aksel-icons'; -import type { Meta, ReactRenderer, StoryFn, StoryObj } from '@storybook/react'; -import type { PartialStoryFn } from '@storybook/types'; - -import { Spinner, Tooltip } from '../'; +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; +import { Tooltip } from '../'; import { Button } from './'; type Story = StoryObj; @@ -24,15 +20,20 @@ type Story = StoryObj; const meta: Meta = { title: 'Komponenter/Button', component: Button, + parameters: { + customStyles: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + flexWrap: 'wrap', + gap: 'var(--ds-spacing-4)', + }, + }, }; export default meta; -const stack = (Story: PartialStoryFn) => ( - - - -); export const Preview: Story = { render: ({ ...args }) => { return @@ -104,14 +109,24 @@ export const Second: StoryFn = () => ( Rediger - ); -Second.decorators = [stack]; +export const NeutralHover = Neutral.bind({}); +NeutralHover.parameters = { + pseudo: { hover: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; + +export const NeutralPressed = Neutral.bind({}); +NeutralPressed.parameters = { + pseudo: { active: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; export const Danger: StoryFn = () => ( <> @@ -119,21 +134,28 @@ export const Danger: StoryFn = () => ( Slett + + ); -Danger.decorators = [ - (Story) => ( - - - - ), -]; +export const DangerHover = Danger.bind({}); +DangerHover.parameters = { + pseudo: { hover: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; + +export const DangerPressed = Danger.bind({}); +DangerPressed.parameters = { + pseudo: { active: true }, + chromatic: { modes: { mobile: { disable: true } } }, +}; export const CombinedColors: StoryFn = () => ( <> @@ -149,8 +171,6 @@ export const CombinedColors: StoryFn = () => ( ); -CombinedColors.decorators = [stack]; - export const AsLink: StoryFn = () => ( ); - -IconsOnlyPrimary.decorators = [stack]; diff --git a/packages/react/src/components/Card/Card.stories.tsx b/packages/react/src/components/Card/Card.stories.tsx index 696a9d177b..0f0f3e32d9 100644 --- a/packages/react/src/components/Card/Card.stories.tsx +++ b/packages/react/src/components/Card/Card.stories.tsx @@ -18,27 +18,21 @@ type Story = StoryFn; export default { title: 'Komponenter/Card', component: Card, - decorators: [ - (Story) => ( -
- -
- ), - ], -} as Meta; + parameters: { + layout: 'fullscreen', + customStyles: { + width: '100%', + maxWidth: 800, + alignItems: 'center', + display: 'grid', + gap: 'var(--ds-spacing-4)', + gridTemplateColumns: 'repeat(auto-fit, minmax(280px , 1fr))', + }, + }, +} satisfies Meta; export const Preview: Story = (args) => ( - + Card Neutral @@ -170,7 +164,7 @@ export const Media: Story = () => ( ); export const Video: Story = () => ( - +