diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml index 9b0127c8c..ef8ce6e19 100644 --- a/.github/workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -10,17 +10,27 @@ jobs: with: node-version: '18.16.1' + - name: Install dependencies ⚙️ + run: yarn + - name: Lint 👀 - run: | - yarn - yarn lint + run: yarn lint - name: Test 🚀 - run: | - yarn - yarn test + run: yarn test - name: Build Storybook 📖 + run: yarn build-storybook + + - name: Setup Playwright for Storybook tests + run: yarn playwright install + + - name: Storybook Tests (axe-core) ♿️ run: | - yarn - yarn build-storybook + yarn storybook & + echo "wait for storybook to start" + until curl --output /dev/null --silent --head --fail http://localhost:6006; do + printf '.' + sleep 5 + done + yarn test-storybook diff --git a/.storybook/main.js b/.storybook/main.js index 0f40f87e2..1d1c7c513 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -5,6 +5,7 @@ module.exports = { '../scss/**/*.stories.mdx', '../scss/**/*.stories.@(js|jsx|ts|tsx)', ], + features: { buildStoriesJson: true }, addons: [ '@storybook/addon-links', '@storybook/addon-a11y', diff --git a/.storybook/test-runner.js b/.storybook/test-runner.js new file mode 100644 index 000000000..a883233d7 --- /dev/null +++ b/.storybook/test-runner.js @@ -0,0 +1,38 @@ +const { getStoryContext } = require('@storybook/test-runner'); +const { injectAxe, checkA11y } = require('axe-playwright'); +/* + * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api + * to learn more about the test-runner hooks API. + */ +module.exports = { + async preVisit(page) { + await injectAxe(page); + }, + async postVisit(page, context) { + // Get the entire context of a story, including parameters, args, argTypes, etc. + const storyContext = await getStoryContext(page, context); + + // Do not run a11y tests on disabled stories. + if (storyContext.parameters?.a11y?.disable) { + return; + } + await checkA11y( + page, + '#root', + { + axeOptions: { + rules: { + // TODO: enable this rule again once all color contrast issues are fixed + 'color-contrast': { + enabled: false, + }, + }, + }, + detailedReport: true, + verbose: false, + }, + false, + 'v2' + ); + }, +}; diff --git a/package.json b/package.json index 031943a6a..171082f2f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "format:docs": "prettier --ignore-path .gitignore --write './**/*.{md,mdx}'", "format": "yarn format:js && yarn format:scss && yarn format:docs", "test": "script/test", + "test-storybook": "test-storybook --stories-json", "checks": "yarn lint && yarn css:stats && yarn test", "watch": "yarn build && yarn build --watch", "clean": "rimraf build/*", @@ -58,10 +59,12 @@ "@storybook/builder-webpack5": "^6.5.9", "@storybook/html": "^6.5.9", "@storybook/manager-webpack5": "^6.5.9", + "@storybook/test-runner": "^0.16.0", "a11y-dialog": "^7.1.0", "alpinejs": "^2.8.2", "analyze-css": "^2.1.50", "autoprefixer": "^10.4.8", + "axe-playwright": "^2.0.1", "babel-loader": "^8.2.5", "css-loader": "^6.7.1", "cssnano": "^5.1.13", diff --git a/scss/bitstyles/atoms/avatar/avatar.stories.mdx b/scss/bitstyles/atoms/avatar/avatar.stories.mdx index e38264e9e..f777149a0 100644 --- a/scss/bitstyles/atoms/avatar/avatar.stories.mdx +++ b/scss/bitstyles/atoms/avatar/avatar.stories.mdx @@ -10,7 +10,7 @@ A user’s visual representation — an image that will represent them in your U {`
- Username’s avatar + Username’s avatar
`}
@@ -23,7 +23,7 @@ Avatars are available at `m` and `l` sizes by default: {`
- Username’s avatar + Username’s avatar
Username
@@ -33,7 +33,7 @@ Avatars are available at `m` and `l` sizes by default: {`
- Username’s avatar + Username’s avatar
Username
diff --git a/scss/bitstyles/atoms/button/Button.js b/scss/bitstyles/atoms/button/Button.js index 4589c0b69..8c283bdbb 100644 --- a/scss/bitstyles/atoms/button/Button.js +++ b/scss/bitstyles/atoms/button/Button.js @@ -4,6 +4,8 @@ export default ({ children, colorVariant = [], shapeVariant = [], + role = undefined, + ariaLabel = undefined, ariaPressed = false, ariaDisabled = false, ariaCurrent = false, @@ -33,6 +35,8 @@ export default ({ button.classList.add(cls); }); if (element === 'button') button.type = 'button'; + if (role) button.setAttribute('role', role); + if (ariaLabel) button.setAttribute('aria-label', ariaLabel); if (ariaPressed) button.setAttribute('aria-pressed', ariaPressed); if (ariaDisabled) button.setAttribute('aria-disabled', ariaDisabled); if (ariaCurrent) button.setAttribute('aria-current', ariaCurrent); diff --git a/scss/bitstyles/atoms/button/button.stories.js b/scss/bitstyles/atoms/button/button.stories.js index 3462bc45f..b64bf2f01 100644 --- a/scss/bitstyles/atoms/button/button.stories.js +++ b/scss/bitstyles/atoms/button/button.stories.js @@ -111,8 +111,21 @@ DefaultOutline.parameters = { ], }; +const baseTabArgs = { + colorVariant: ['tab'], + role: 'tab', +}; + +const TabDecorator = (story) => { + const decorator = document.createElement('ul'); + decorator.setAttribute('role', 'tablist'); + decorator.appendChild(story()); + return decorator; +}; + export const DefaultTab = Template.bind({}); -DefaultTab.args = { colorVariant: ['tab'] }; +DefaultTab.decorators = [TabDecorator]; +DefaultTab.args = { ...baseTabArgs }; DefaultTab.parameters = { zeplinLink: [ { @@ -771,7 +784,8 @@ OutlinePressed.parameters = { }; export const TabPressed = Template.bind({}); -TabPressed.args = { colorVariant: ['tab'], ariaCurrent: 'page' }; +TabPressed.decorators = [TabDecorator]; +TabPressed.args = { ...baseTabArgs, ariaCurrent: 'page' }; TabPressed.parameters = { zeplinLink: 'https://app.zeplin.io/styleguide/63079b90d0bf4a646c46c227/components?coid=640094b52890b84043f89ba4', @@ -815,7 +829,8 @@ OutlineDisabled.parameters = { }; export const TabDisabled = Template.bind({}); -TabDisabled.args = { colorVariant: ['tab'], disabled: true }; +TabDisabled.decorators = [TabDecorator]; +TabDisabled.args = { ...baseTabArgs, disabled: true }; TabDisabled.parameters = { zeplinLink: 'https://app.zeplin.io/styleguide/63079b90d0bf4a646c46c227/components?coid=640094b30ee0b5408ad023fd', @@ -930,7 +945,8 @@ OutlineAnchor.parameters = { }; export const TabAnchor = Template.bind({}); -TabAnchor.args = { colorVariant: ['tab'], element: 'anchor' }; +TabAnchor.decorators = [TabDecorator]; +TabAnchor.args = { ...baseTabArgs, element: 'anchor' }; TabAnchor.parameters = { zeplinLink: [ { @@ -949,8 +965,9 @@ TabAnchor.parameters = { }; export const TabAnchorPressed = Template.bind({}); +TabAnchorPressed.decorators = [TabDecorator]; TabAnchorPressed.args = { - colorVariant: ['tab'], + ...baseTabArgs, element: 'anchor', ariaCurrent: 'page', }; @@ -1087,8 +1104,9 @@ OutlineAnchorDisabled.parameters = { }; export const TabAnchorDisabled = Template.bind({}); +TabAnchorDisabled.decorators = [TabDecorator]; TabAnchorDisabled.args = { - colorVariant: ['tab'], + ...baseTabArgs, ariaDisabled: true, element: 'a', }; @@ -1144,8 +1162,9 @@ DangerOutlineAnchorDisabled.parameters = { // ***** Icon-only tab buttons ****************** // export const TabIconOnlyBase = Template.bind({}); +TabIconOnlyBase.decorators = [TabDecorator]; TabIconOnlyBase.args = { - colorVariant: ['tab'], + ...baseTabArgs, shapeVariant: ['square'], children: `Add`, }; @@ -1167,8 +1186,9 @@ TabIconOnlyBase.parameters = { }; export const TabIconOnlySelected = Template.bind({}); +TabIconOnlySelected.decorators = [TabDecorator]; TabIconOnlySelected.args = { - colorVariant: ['tab'], + ...baseTabArgs, shapeVariant: ['square'], children: `Add`, ariaSelected: true, @@ -1179,8 +1199,9 @@ TabIconOnlySelected.parameters = { }; export const TabIconOnlyDisabled = Template.bind({}); +TabIconOnlyDisabled.decorators = [TabDecorator]; TabIconOnlyDisabled.args = { - colorVariant: ['tab'], + ...baseTabArgs, shapeVariant: ['square'], children: `Add`, disabled: true, diff --git a/scss/bitstyles/atoms/dropdown/dropdown.stories.js b/scss/bitstyles/atoms/dropdown/dropdown.stories.js index 4de435550..60543cb13 100644 --- a/scss/bitstyles/atoms/dropdown/dropdown.stories.js +++ b/scss/bitstyles/atoms/dropdown/dropdown.stories.js @@ -62,7 +62,7 @@ const menu = ` colorVariant: ['transparent'], shapeVariant: ['menu'], children: 'Settings', - element: 'a', + classname: ['u-width-full'], }).outerHTML } @@ -72,7 +72,7 @@ const menu = ` colorVariant: ['transparent'], shapeVariant: ['menu'], children: 'Help', - element: 'a', + classname: ['u-width-full'], }).outerHTML } @@ -82,18 +82,17 @@ const menu = ` colorVariant: ['transparent'], shapeVariant: ['menu'], children: 'Privacy', - element: 'a', + classname: ['u-width-full'], }).outerHTML } -
  • ${ Button({ colorVariant: ['transparent'], shapeVariant: ['menu'], children: 'Sign out', - element: 'a', + classname: ['u-width-full'], }).outerHTML }
  • diff --git a/scss/bitstyles/atoms/flash/Flash.js b/scss/bitstyles/atoms/flash/Flash.js index 28bb5c94e..e25d70b5e 100644 --- a/scss/bitstyles/atoms/flash/Flash.js +++ b/scss/bitstyles/atoms/flash/Flash.js @@ -28,6 +28,7 @@ export default ({ children, theme = 'brand-1', icon, onClick = null }) => { Button({ shapeVariant: ['x-small', 'square'], children: Icon({ name: 'cross' }), + ariaLabel: 'Close', onClick, }) ); diff --git a/scss/bitstyles/base/figure/figure.stories.mdx b/scss/bitstyles/base/figure/figure.stories.mdx index 6284704f0..72b681c0d 100644 --- a/scss/bitstyles/base/figure/figure.stories.mdx +++ b/scss/bitstyles/base/figure/figure.stories.mdx @@ -8,7 +8,7 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {`
    - +
    A portrait of a kitten
    `} diff --git a/scss/bitstyles/base/forms/forms.stories.mdx b/scss/bitstyles/base/forms/forms.stories.mdx index 455a6696e..2e939c522 100644 --- a/scss/bitstyles/base/forms/forms.stories.mdx +++ b/scss/bitstyles/base/forms/forms.stories.mdx @@ -143,7 +143,7 @@ This includes all standard `type="text"` inputs, plus all the text-based inputs {` - diff --git a/scss/bitstyles/base/forms/inputs.stories.mdx b/scss/bitstyles/base/forms/inputs.stories.mdx index af09c8afb..04c4a290d 100644 --- a/scss/bitstyles/base/forms/inputs.stories.mdx +++ b/scss/bitstyles/base/forms/inputs.stories.mdx @@ -32,12 +32,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -47,12 +47,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -62,12 +62,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -77,12 +77,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -92,12 +92,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -107,12 +107,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -122,12 +122,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -137,12 +137,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -167,12 +167,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -197,12 +197,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -212,12 +212,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} @@ -227,12 +227,12 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} diff --git a/scss/bitstyles/base/images/images.stories.mdx b/scss/bitstyles/base/images/images.stories.mdx index db871ebfe..380227440 100644 --- a/scss/bitstyles/base/images/images.stories.mdx +++ b/scss/bitstyles/base/images/images.stories.mdx @@ -9,7 +9,7 @@ Does the minimum to make images responsive — sets `max-width: 100%` {` - Always include an alt attribute, even if it’s empty + Always include an alt attribute, even if it’s empty `} diff --git a/scss/bitstyles/base/input-checkbox/input-checkbox.stories.mdx b/scss/bitstyles/base/input-checkbox/input-checkbox.stories.mdx index e941b8d7c..ebd6744b0 100644 --- a/scss/bitstyles/base/input-checkbox/input-checkbox.stories.mdx +++ b/scss/bitstyles/base/input-checkbox/input-checkbox.stories.mdx @@ -7,22 +7,22 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} {` - + `} {` - + `} diff --git a/scss/bitstyles/base/input-radio/input-radio.stories.mdx b/scss/bitstyles/base/input-radio/input-radio.stories.mdx index fa9b1855e..0dbdd781b 100644 --- a/scss/bitstyles/base/input-radio/input-radio.stories.mdx +++ b/scss/bitstyles/base/input-radio/input-radio.stories.mdx @@ -7,22 +7,22 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + `} {` - + `} {` - + `} {` - + `} diff --git a/scss/bitstyles/base/input-text/input-text.stories.mdx b/scss/bitstyles/base/input-text/input-text.stories.mdx index 8dfe8398d..1c012c768 100644 --- a/scss/bitstyles/base/input-text/input-text.stories.mdx +++ b/scss/bitstyles/base/input-text/input-text.stories.mdx @@ -13,12 +13,12 @@ You may find [Base/Forms](/docs/base-forms--fieldset) more informative. {` - + `} {` - + `} @@ -28,12 +28,12 @@ You may find [Base/Forms](/docs/base-forms--fieldset) more informative. {` - + `} {` - + `} @@ -43,12 +43,12 @@ You may find [Base/Forms](/docs/base-forms--fieldset) more informative. {` - + `} {` - + `} @@ -58,12 +58,12 @@ You may find [Base/Forms](/docs/base-forms--fieldset) more informative. {` - + `} {` - + `} @@ -73,12 +73,12 @@ You may find [Base/Forms](/docs/base-forms--fieldset) more informative. {` - + `} {` - + `} diff --git a/scss/bitstyles/base/select/select.stories.mdx b/scss/bitstyles/base/select/select.stories.mdx index 49a2b47f7..3a6b80233 100644 --- a/scss/bitstyles/base/select/select.stories.mdx +++ b/scss/bitstyles/base/select/select.stories.mdx @@ -7,29 +7,35 @@ import { Canvas, Meta, Story } from '@storybook/addon-docs'; {` - + {` - + {` - + diff --git a/scss/bitstyles/organisms/page-header/PageHeader.js b/scss/bitstyles/organisms/page-header/PageHeader.js index 4dc7380ba..51d20be19 100644 --- a/scss/bitstyles/organisms/page-header/PageHeader.js +++ b/scss/bitstyles/organisms/page-header/PageHeader.js @@ -91,7 +91,7 @@ breadCrumbsMenu.innerHTML = ` `; -export const header = document.createElement('header'); +export const header = document.createElement('div'); header.innerHTML = `
    diff --git a/scss/bitstyles/organisms/sidebar/Sidebar.js b/scss/bitstyles/organisms/sidebar/Sidebar.js index 7fb965bff..8d59bef1d 100644 --- a/scss/bitstyles/organisms/sidebar/Sidebar.js +++ b/scss/bitstyles/organisms/sidebar/Sidebar.js @@ -28,12 +28,9 @@ buttonList.classList.add( 'u-padding-s4-left' ); -const listItem = document.createElement('li'); -listItem.classList.add('u-margin-s2-bottom', 'u-flex'); - -const buttomComponent = (label) => +const buttonComponent = (label) => Button({ - classname: ['u-justify-start'], + classname: ['u-justify-start', 'u-flex-grow-1'], children: `${label}`, colorVariant: ['transparent'], }); @@ -47,9 +44,12 @@ const labels = [ 'Sales', ]; -labels.forEach((label) => - buttonList.appendChild(listItem.appendChild(buttomComponent(label))) -); +labels.forEach((label) => { + const listItem = document.createElement('li'); + listItem.classList.add('u-margin-s2-bottom', 'u-flex'); + listItem.appendChild(buttonComponent(label)); + buttonList.appendChild(listItem); +}); export const bottom = document.createElement('div'); bottom.innerHTML = ` @@ -67,7 +67,7 @@ bottom.innerHTML = ` >
    Jane Dobermann’s avatar Privacy -
  • Sign out
  • diff --git a/scss/bitstyles/ui/app-layouts.stories.mdx b/scss/bitstyles/ui/app-layouts.stories.mdx index cdf5ea559..71efe9c0d 100644 --- a/scss/bitstyles/ui/app-layouts.stories.mdx +++ b/scss/bitstyles/ui/app-layouts.stories.mdx @@ -77,7 +77,7 @@ Sidebar, header, and content that uses all available space.
    - Username’s avatar + Username’s avatar
    Jane Dobermann
    @@ -151,7 +151,7 @@ Sidebar, header, and content that uses all available space.
    - Username’s avatar + Username’s avatar
    Jane Dobermann
    @@ -204,27 +204,27 @@ Sidebar, header, and content that uses all available space.
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    +
    + + + +

    Users

    +
    +
    + +
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    +
    + + + +
    +

    Users

    +
    +
    + +

    Filter results