mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-21 05:02:39 +08:00
Merge branch 'next' into shilman/presets-docs
This commit is contained in:
commit
c0f025d0aa
68
.babelrc.js
68
.babelrc.js
@ -1,28 +1,68 @@
|
||||
const withTests = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{ shippedProposals: true, useBuiltIns: 'usage', corejs: '3', targets: { node: 'current' } },
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-require-context-hook',
|
||||
'babel-plugin-dynamic-import-node',
|
||||
'@babel/plugin-transform-runtime',
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage' }],
|
||||
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }],
|
||||
'@babel/preset-typescript',
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-flow',
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
|
||||
[
|
||||
'@babel/plugin-proposal-decorators',
|
||||
{
|
||||
legacy: true,
|
||||
},
|
||||
],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'babel-plugin-add-react-displayname',
|
||||
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
|
||||
'babel-plugin-macros',
|
||||
['emotion', { sourceMap: true, autoLabel: true }],
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
presets: [['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage' }]],
|
||||
plugins: ['babel-plugin-require-context-hook', 'babel-plugin-dynamic-import-node'],
|
||||
},
|
||||
test: withTests,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
test: './examples/vue-kitchen-sink',
|
||||
presets: ['babel-preset-vue'],
|
||||
env: {
|
||||
test: withTests,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: './lib',
|
||||
presets: [
|
||||
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }],
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'babel-plugin-macros',
|
||||
['emotion', { sourceMap: true, autoLabel: true }],
|
||||
'@babel/plugin-transform-react-constant-elements',
|
||||
'babel-plugin-add-react-displayname',
|
||||
],
|
||||
env: {
|
||||
test: withTests,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: [
|
||||
@ -33,7 +73,6 @@ module.exports = {
|
||||
'./addons/storysource/src/loader',
|
||||
'./app/**/src/server/**',
|
||||
'./app/**/src/bin/**',
|
||||
'./dangerfile.js',
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
@ -44,9 +83,20 @@ module.exports = {
|
||||
targets: {
|
||||
node: '8.11',
|
||||
},
|
||||
corejs: '3',
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'emotion',
|
||||
'babel-plugin-macros',
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
],
|
||||
env: {
|
||||
test: withTests,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { fail, danger } from 'danger';
|
||||
import { flatten, intersection, isEmpty } from 'lodash';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const pkg = require('./package.json'); // eslint-disable-line import/newline-after-import
|
||||
execSync('npm install lodash');
|
||||
|
||||
const { flatten, intersection, isEmpty } = require('lodash');
|
||||
|
||||
const pkg = require('../../package.json'); // eslint-disable-line import/newline-after-import
|
||||
const prLogConfig = pkg['pr-log'];
|
||||
|
||||
const Versions = {
|
||||
@ -20,18 +24,11 @@ const checkRequiredLabels = labels => {
|
||||
branchVersion === Versions.PATCH ? 'feature request' : [],
|
||||
]);
|
||||
|
||||
const requiredLabels = flatten([
|
||||
prLogConfig.skipLabels || [],
|
||||
(prLogConfig.validLabels || []).map(keyVal => keyVal[0]),
|
||||
]);
|
||||
const requiredLabels = flatten([prLogConfig.skipLabels || [], (prLogConfig.validLabels || []).map(keyVal => keyVal[0])]);
|
||||
|
||||
const blockingLabels = intersection(forbiddenLabels, labels);
|
||||
if (!isEmpty(blockingLabels)) {
|
||||
fail(
|
||||
`PR is marked with ${blockingLabels.map(label => `"${label}"`).join(', ')} label${
|
||||
blockingLabels.length > 1 ? 's' : ''
|
||||
}.`
|
||||
);
|
||||
fail(`PR is marked with ${blockingLabels.map(label => `"${label}"`).join(', ')} label${blockingLabels.length > 1 ? 's' : ''}.`);
|
||||
}
|
||||
|
||||
const foundLabels = intersection(requiredLabels, labels);
|
@ -40,6 +40,15 @@ jobs:
|
||||
- addons
|
||||
- app
|
||||
- lib
|
||||
chromatic:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Run chromatic on the pre-built storybook
|
||||
command: yarn chromatic -- -d ./storybook-static
|
||||
examples:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@ -109,17 +118,30 @@ jobs:
|
||||
command: |
|
||||
cd examples/preact-kitchen-sink
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: Build cra react15
|
||||
command: |
|
||||
cd examples/cra-react15
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: Build official-storybook
|
||||
command: |
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: Run image snapshots
|
||||
command: yarn test --image
|
||||
# - run:
|
||||
# name: Run image snapshots
|
||||
# command: yarn test --image
|
||||
- store_artifacts:
|
||||
path: examples/official-storybook/image-snapshots/__image_snapshots__
|
||||
destination: official_storybook_image_snapshots
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- node_modules
|
||||
- examples
|
||||
- addons
|
||||
- app
|
||||
- lib
|
||||
smoke-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@ -186,6 +208,11 @@ jobs:
|
||||
command: |
|
||||
cd examples/preact-kitchen-sink
|
||||
yarn storybook --smoke-test --quiet
|
||||
- run:
|
||||
name: Run cra reac15 (smoke test)
|
||||
command: |
|
||||
cd examples/cra-react15
|
||||
yarn storybook --smoke-test --quiet
|
||||
native-smoke-tests:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@ -194,12 +221,35 @@ jobs:
|
||||
at: .
|
||||
- run:
|
||||
name: Bootstrap
|
||||
command: yarn bootstrap --reactnativeapp
|
||||
command: yarn bootstrap --core
|
||||
- run:
|
||||
name: Run React-Native-App example
|
||||
command: |
|
||||
cd examples-native/crna-kitchen-sink
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: Publish React-Native-App example
|
||||
command: |
|
||||
./scripts/crna-publish.js
|
||||
frontpage:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
name: Restore core dependencies cache
|
||||
keys:
|
||||
- core-dependencies-v3-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn install
|
||||
- run:
|
||||
name: Trigger build
|
||||
command: ./scripts/build-frontpage.js
|
||||
- save_cache:
|
||||
name: Cache core dependencies
|
||||
key: core-dependencies-v3-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
docs:
|
||||
<<: *defaults
|
||||
steps:
|
||||
@ -245,7 +295,7 @@ jobs:
|
||||
at: .
|
||||
- run:
|
||||
name: Test
|
||||
command: yarn test --coverage --runInBand --core
|
||||
command: yarn test --coverage --w2 --core
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
@ -288,6 +338,7 @@ workflows:
|
||||
jobs:
|
||||
- build
|
||||
- docs
|
||||
- frontpage
|
||||
- lint:
|
||||
requires:
|
||||
- docs
|
||||
@ -313,3 +364,6 @@ workflows:
|
||||
- cli-test-latest-cra:
|
||||
requires:
|
||||
- build
|
||||
- chromatic:
|
||||
requires:
|
||||
- examples
|
||||
|
@ -7,10 +7,9 @@ docs/public
|
||||
storybook-static
|
||||
built-storybooks
|
||||
lib/cli/test
|
||||
scripts/storage
|
||||
*.bundle.js
|
||||
*.js.map
|
||||
*.ts
|
||||
*.tsx
|
||||
|
||||
!.remarkrc.js
|
||||
!.babelrc.js
|
||||
@ -18,3 +17,8 @@ lib/cli/test
|
||||
!.eslintrc-markdown.js
|
||||
!.jest.config.js
|
||||
!.storybook
|
||||
|
||||
REACT_NATIVE
|
||||
examples-native
|
||||
react-native
|
||||
ondevice-*
|
90
.eslintrc.js
90
.eslintrc.js
@ -1,26 +1,68 @@
|
||||
const error = 2;
|
||||
const warn = 1;
|
||||
const ignore = 0;
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:import/react-native',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
plugins: ['prettier', 'jest', 'import', 'react', 'jsx-a11y', 'json', 'html'],
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: { ecmaVersion: 8, sourceType: 'module' },
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'prettier',
|
||||
'jest',
|
||||
'import',
|
||||
'react',
|
||||
'jsx-a11y',
|
||||
'json',
|
||||
'html',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
env: { es6: true, node: true, 'jest/globals': true },
|
||||
settings: {
|
||||
'import/core-modules': ['enzyme'],
|
||||
'import/ignore': ['node_modules\\/(?!@storybook)'],
|
||||
'import/resolver': { node: { extensions: ['.js', '.ts'] } },
|
||||
'import/resolver': { node: { extensions: ['.js', '.ts', '.tsx', '.mjs'] } },
|
||||
'html/html-extensions': ['.html'],
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
error,
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'lodash.isequal',
|
||||
message:
|
||||
'Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead',
|
||||
},
|
||||
{
|
||||
name: 'lodash.mergewith',
|
||||
message:
|
||||
'Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead',
|
||||
},
|
||||
{
|
||||
name: 'lodash.pick',
|
||||
message:
|
||||
'Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead',
|
||||
},
|
||||
],
|
||||
// catch-all for any lodash modularised. The CVE is listed against the entire family for lodash < 4.17.11
|
||||
patterns: ['lodash.*'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': [warn],
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? error : ignore,
|
||||
'class-methods-use-this': ignore,
|
||||
@ -30,6 +72,7 @@ module.exports = {
|
||||
{
|
||||
js: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
mjs: 'never',
|
||||
},
|
||||
],
|
||||
@ -42,11 +85,11 @@ module.exports = {
|
||||
'**/example/**',
|
||||
'*.js',
|
||||
'**/*.test.js',
|
||||
'**/*.stories.js',
|
||||
'**/*.stories.*',
|
||||
'**/scripts/*.js',
|
||||
'**/stories/**/*.js',
|
||||
'**/__tests__/**/*.js',
|
||||
'**/.storybook/**/*.js',
|
||||
'**/.storybook/**/*.*',
|
||||
],
|
||||
peerDependencies: true,
|
||||
},
|
||||
@ -94,19 +137,50 @@ module.exports = {
|
||||
error,
|
||||
{ allow: ['__STORYBOOK_CLIENT_API__', '__STORYBOOK_ADDONS_CHANNEL__'] },
|
||||
],
|
||||
'@typescript-eslint/no-var-requires': ignore,
|
||||
'@typescript-eslint/camelcase': ignore,
|
||||
'@typescript-eslint/no-unused-vars': ignore,
|
||||
'@typescript-eslint/explicit-member-accessibility': ignore,
|
||||
'@typescript-eslint/explicit-function-return-type': ignore,
|
||||
'@typescript-eslint/no-explicit-any': ignore, // would prefer to enable this
|
||||
'@typescript-eslint/no-use-before-define': ignore, // this is duplicated
|
||||
'@typescript-eslint/interface-name-prefix': ignore, // I don't agree
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**',
|
||||
'**/*.test.js/**',
|
||||
'**/*.stories.js',
|
||||
'**/*.test.*',
|
||||
'**/*.stories.*',
|
||||
'**/storyshots/**/stories/**',
|
||||
'docs/src/new-components/lib/StoryLinkWrapper.js',
|
||||
'docs/src/stories/**',
|
||||
],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': ignore,
|
||||
},
|
||||
},
|
||||
{ files: '**/.storybook/config.js', rules: { 'global-require': ignore } },
|
||||
{
|
||||
files: ['**/*.stories.*'],
|
||||
rules: {
|
||||
'no-console': ignore,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.tsx', '**/*.ts'],
|
||||
rules: {
|
||||
'react/prop-types': ignore, // we should use types
|
||||
'no-dupe-class-members': ignore, // this is called overloads in typescript
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'vars-on-top': ignore,
|
||||
'no-var': ignore, // this is how typescript works
|
||||
'spaced-comment': ignore,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
29
.github/CODEOWNERS
vendored
29
.github/CODEOWNERS
vendored
@ -1,19 +1,20 @@
|
||||
.circleci/ @hypnosphi @ndelangen
|
||||
.circleci/ @ndelangen
|
||||
.teamcity/ @hypnosphi
|
||||
.github/ @danielduan
|
||||
|
||||
/addons/a11y/ @jbovenschen
|
||||
/addons/a11y/ @jbovenschen @codebyalex
|
||||
/addons/actions/ @rhalff
|
||||
/addons/backgrounds/ @ndelangen @hypnosphi
|
||||
/addons/backgrounds/ @ndelangen
|
||||
/addons/centered/ @kazupon
|
||||
/addons/events/ @z4o4z @ndelangen
|
||||
/addons/graphql/ @mnmtanish
|
||||
/addons/info/ @theinterned @z4o4z @UsulPro @hypnosphi @dangreenisrael @danielduan
|
||||
/addons/info/ @theinterned @z4o4z @UsulPro @dangreenisrael @danielduan
|
||||
/addons/jest/ @renaudtertrais
|
||||
/addons/knobs/ @alexandrebodin @theinterned @leonrodenburg @alterx @hypnosphi
|
||||
/addons/links/ @hypnosphi @ndelangen
|
||||
/addons/knobs/ @alexandrebodin @theinterned @leonrodenburg @alterx
|
||||
/addons/links/ @ndelangen
|
||||
/addons/notes/ @alexandrebodin
|
||||
/addons/options/ @danielduan @UsulPro
|
||||
/addons/storyshots/ @igor-dv @thomasbertet @hypnosphi
|
||||
/addons/storyshots/ @igor-dv @thomasbertet
|
||||
/addons/storysource/ @igor-dv
|
||||
/addons/viewport/ @saponifi3d
|
||||
|
||||
@ -24,12 +25,12 @@
|
||||
/app/vue/ @thomasbertet @kazupon
|
||||
/app/svelte/ @plumpNation
|
||||
|
||||
/docs/ @ndelangen @shilman @hypnosphi
|
||||
/docs/ @ndelangen @shilman
|
||||
|
||||
/examples/angular-cli/ @igor-dv @alterx
|
||||
/examples/cra-kitchen-sink/ @ndelangen @UsulPro @hypnosphi
|
||||
/examples/cra-kitchen-sink/ @ndelangen @UsulPro
|
||||
/examples/cra-ts-kitchen-sink/ @mucsi96
|
||||
/examples/official-storybook/ @hypnosphi @danielduan @UsulPro
|
||||
/examples/official-storybook/ @danielduan @UsulPro
|
||||
/examples/polymer-cli/ @naipath @igor-dv
|
||||
/examples/vue-kitchen-sink/ @igor-dv @alexandrebodin
|
||||
/examples/svelte-kitchen-sink/ @plumpNation
|
||||
@ -40,12 +41,12 @@
|
||||
/lib/channel-postmessage/ @mnmtanish @ndelangen
|
||||
/lib/channel-websocket/ @mnmtanish @ndelangen
|
||||
/lib/channels/ @mnmtanish @ndelangen
|
||||
/lib/cli/ @hypnosphi @ndelangen @shilman @stijnkoopal
|
||||
/lib/cli/ @ndelangen @shilman @stijnkoopal
|
||||
/lib/client-logger/ @dangreenisrael
|
||||
/lib/codemod/ @aaronmcadam @ndelangen
|
||||
/lib/components/ @ndelangen @hypnosphi @tmeasday
|
||||
/lib/components/ @ndelangen @tmeasday
|
||||
/lib/core/ @tmeasday @igor-dv @alterx
|
||||
/lib/node-logger/ @dangreenisrael
|
||||
/lib/ui/ @tmeasday @igor-dv @hypnosphi @ndelangen
|
||||
/lib/ui/ @tmeasday @igor-dv @ndelangen
|
||||
|
||||
/scripts/ @ndelangen @igor-dv @hypnosphi
|
||||
/scripts/ @ndelangen @igor-dv
|
||||
|
23
.github/automention.yml
vendored
Normal file
23
.github/automention.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
'app: angular': ['kroeder', 'igor-dv', 'MaximSagan']
|
||||
'app: ember': ['gabrielcsapo']
|
||||
'app: html': ['Hypnosphi']
|
||||
'app: marko': ['nm123github']
|
||||
'app: polymer': ['stijnkoopal', 'ndelangen']
|
||||
'app: preact': ['BartWaardenburg']
|
||||
'app: react-native': ['benoitdion', 'gongreg']
|
||||
'app: react-native-server': ['benoitdion', 'igor-dv']
|
||||
'app: vue': ['backbone87', 'elevatebart']
|
||||
'api: addons': ['ndelangen']
|
||||
'addon: a11y': ['CodeByAlex', 'Armanio', 'jsomsanith']
|
||||
'addon: contexts': ['leoyli']
|
||||
'addon: docs': ['shilman', 'elevatebart']
|
||||
'addon: info': ['shilman', 'elevatebart']
|
||||
'addon: knobs': ['leoyli', 'Armanio']
|
||||
'addon: storysource': ['igor-dv', 'libetl']
|
||||
typescript: ['kroeder', 'gaetanmaisse', 'ndelangen']
|
||||
theming: ['ndelangen', 'domyen']
|
||||
cra: ['mrmckeb']
|
||||
cli: ['Keraito']
|
||||
dependencies: ['ndelangen']
|
||||
'babel / webpack': ['ndelangen', 'igor-dv', 'shilman']
|
||||
'yarn / npm': ['ndelangen', 'tmeasday']
|
38
.github/main.workflow
vendored
38
.github/main.workflow
vendored
@ -1,12 +1,32 @@
|
||||
workflow "New workflow" {
|
||||
on = "push"
|
||||
resolves = ["Hello World"]
|
||||
action "Danger JS" {
|
||||
uses = "danger/danger-js@master"
|
||||
secrets = ["GITHUB_TOKEN"]
|
||||
args = "--dangerfile .ci/danger/dangerfile.ts"
|
||||
}
|
||||
|
||||
action "Hello World" {
|
||||
uses = "./ci/action-a"
|
||||
env = {
|
||||
MY_NAME = "Mona"
|
||||
}
|
||||
args = "\"Hello world, I'm $MY_NAME!\""
|
||||
workflow "Dangerfile JS Pull" {
|
||||
on = "pull_request"
|
||||
resolves = "Danger JS"
|
||||
}
|
||||
|
||||
workflow "Dangerfile JS Label" {
|
||||
on = "label"
|
||||
resolves = "Danger JS"
|
||||
}
|
||||
|
||||
# ===
|
||||
|
||||
action "Automention" {
|
||||
uses = "shilman/automention@master"
|
||||
secrets = ["GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
workflow "Automention Issues" {
|
||||
on = "issues"
|
||||
resolves = "Automention"
|
||||
}
|
||||
|
||||
workflow "Automention PRs" {
|
||||
on = "pull_request"
|
||||
resolves = "Automention"
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -24,3 +24,6 @@ integration/__image_snapshots__/__diff_output__
|
||||
/examples/cra-kitchen-sink/src/__image_snapshots__/__diff_output__/
|
||||
lib/*.jar
|
||||
lib/**/dll
|
||||
.expo/packager-info.json
|
||||
scripts/storage
|
||||
htpasswd
|
||||
|
@ -21,11 +21,8 @@ object Project : Project({
|
||||
buildType(OpenSourceProjects_Storybook_Bootstrap)
|
||||
buildType(OpenSourceProjects_Storybook_CliTestLatestCra)
|
||||
buildType(OpenSourceProjects_Storybook_Examples)
|
||||
buildType(OpenSourceProjects_Storybook_Danger)
|
||||
buildType(OpenSourceProjects_Storybook_NativeSmokeTests)
|
||||
buildType(OpenSourceProjects_Storybook_Docs)
|
||||
buildType(OpenSourceProjects_Storybook_Build_2)
|
||||
buildType(OpenSourceProjects_Storybook_CliTest)
|
||||
buildType(OpenSourceProjects_Storybook_Test)
|
||||
buildType(OpenSourceProjects_Storybook_Lint)
|
||||
buildType(OpenSourceProjects_Storybook_Lint_Warnings)
|
||||
|
@ -39,6 +39,7 @@ object OpenSourceProjects_Storybook_Build_2 : BuildType({
|
||||
}
|
||||
retryBuild {
|
||||
delaySeconds = 60
|
||||
enabled = false
|
||||
}
|
||||
finishBuildTrigger {
|
||||
enabled = false
|
||||
@ -95,11 +96,6 @@ object OpenSourceProjects_Storybook_Build_2 : BuildType({
|
||||
onDependencyCancel = FailureAction.ADD_PROBLEM
|
||||
}
|
||||
}
|
||||
dependency(OpenSourceProjects_Storybook.buildTypes.OpenSourceProjects_Storybook_NativeSmokeTests) {
|
||||
snapshot {
|
||||
onDependencyCancel = FailureAction.ADD_PROBLEM
|
||||
}
|
||||
}
|
||||
dependency(OpenSourceProjects_Storybook.buildTypes.OpenSourceProjects_Storybook_SmokeTests) {
|
||||
snapshot {
|
||||
onDependencyCancel = FailureAction.ADD_PROBLEM
|
||||
@ -115,10 +111,5 @@ object OpenSourceProjects_Storybook_Build_2 : BuildType({
|
||||
onDependencyCancel = FailureAction.ADD_PROBLEM
|
||||
}
|
||||
}
|
||||
dependency(OpenSourceProjects_Storybook.buildTypes.OpenSourceProjects_Storybook_CliTest) {
|
||||
snapshot {
|
||||
onDependencyCancel = FailureAction.ADD_PROBLEM
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -26,10 +26,10 @@ object OpenSourceProjects_Storybook_Chromatic : BuildType({
|
||||
scriptContent = """
|
||||
#!/bin/sh
|
||||
|
||||
set -e -x
|
||||
|
||||
yarn
|
||||
yarn chromatic
|
||||
# set -e -x
|
||||
# yarn
|
||||
# yarn chromatic
|
||||
echo "chromatic moved to cirlce CI"
|
||||
""".trimIndent()
|
||||
dockerImage = "node:%docker.node.version%"
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
package OpenSourceProjects_Storybook.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildFeatures.commitStatusPublisher
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildSteps.script
|
||||
|
||||
object OpenSourceProjects_Storybook_CliTest : BuildType({
|
||||
uuid = "b1db1a3a-a4cf-46ea-8f55-98b86611f92e"
|
||||
id = "OpenSourceProjects_Storybook_CliTest"
|
||||
name = "CLI test"
|
||||
|
||||
vcs {
|
||||
root(OpenSourceProjects_Storybook.vcsRoots.OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster)
|
||||
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
name = "Test"
|
||||
scriptContent = """
|
||||
#!/bin/sh
|
||||
|
||||
set -e -x
|
||||
|
||||
yarn
|
||||
yarn test --cli --teamcity
|
||||
""".trimIndent()
|
||||
dockerImage = "node:%docker.node.version%"
|
||||
}
|
||||
}
|
||||
|
||||
features {
|
||||
commitStatusPublisher {
|
||||
publisher = github {
|
||||
githubUrl = "https://api.github.com"
|
||||
authType = personalToken {
|
||||
token = "credentialsJSON:5ffe2d7e-531e-4f6f-b1fc-a41bfea26eaa"
|
||||
}
|
||||
}
|
||||
param("github_oauth_user", "Hypnosphi")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependency(OpenSourceProjects_Storybook.buildTypes.OpenSourceProjects_Storybook_Bootstrap) {
|
||||
snapshot {
|
||||
onDependencyFailure = FailureAction.FAIL_TO_START
|
||||
}
|
||||
|
||||
artifacts {
|
||||
artifactRules = "dist.zip!**"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requirements {
|
||||
doesNotContain("env.OS", "Windows")
|
||||
}
|
||||
|
||||
cleanup {
|
||||
artifacts(days = 1)
|
||||
}
|
||||
})
|
@ -43,7 +43,9 @@ object OpenSourceProjects_Storybook_CliTestLatestCra : BuildType({
|
||||
+:next
|
||||
""".trimIndent()
|
||||
}
|
||||
retryBuild {}
|
||||
retryBuild {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
features {
|
||||
|
@ -1,79 +0,0 @@
|
||||
package OpenSourceProjects_Storybook.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildFeatures.commitStatusPublisher
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildSteps.script
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.vcs
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.retryBuild
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.triggers.VcsTrigger
|
||||
|
||||
object OpenSourceProjects_Storybook_Danger : BuildType({
|
||||
uuid = "759f0116-2f7d-4c03-8220-56e4ab03be3a"
|
||||
id = "OpenSourceProjects_Storybook_Danger"
|
||||
name = "Danger"
|
||||
|
||||
params {
|
||||
password("env.DANGER_GITHUB_API_TOKEN", "credentialsJSON:9ac87388-d267-4def-a10e-3e596369f644")
|
||||
param("env.PULL_REQUEST_URL", "https://github.com/storybooks/storybook/%teamcity.build.branch%")
|
||||
}
|
||||
|
||||
vcs {
|
||||
root(OpenSourceProjects_Storybook.vcsRoots.OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1)
|
||||
|
||||
buildDefaultBranch = false
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
name = "Danger"
|
||||
scriptContent = """
|
||||
#!/bin/sh
|
||||
|
||||
set -e -x
|
||||
|
||||
yarn
|
||||
yarn danger ci
|
||||
""".trimIndent()
|
||||
dockerImage = "node:%docker.node.version%"
|
||||
}
|
||||
}
|
||||
|
||||
triggers {
|
||||
vcs {
|
||||
quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:master
|
||||
""".trimIndent()
|
||||
}
|
||||
retryBuild {
|
||||
delaySeconds = 3600
|
||||
}
|
||||
}
|
||||
|
||||
features {
|
||||
commitStatusPublisher {
|
||||
publisher = github {
|
||||
githubUrl = "https://api.github.com"
|
||||
authType = personalToken {
|
||||
token = "credentialsJSON:5ffe2d7e-531e-4f6f-b1fc-a41bfea26eaa"
|
||||
}
|
||||
}
|
||||
param("github_oauth_user", "Hypnosphi")
|
||||
}
|
||||
feature {
|
||||
type = "pullRequests"
|
||||
param("filterAuthorRole", "EVERYBODY")
|
||||
param("authenticationType", "token")
|
||||
param("secure:accessToken", "credentialsJSON:5ffe2d7e-531e-4f6f-b1fc-a41bfea26eaa")
|
||||
}
|
||||
}
|
||||
|
||||
requirements {
|
||||
doesNotContain("env.OS", "Windows")
|
||||
}
|
||||
|
||||
cleanup {
|
||||
artifacts(days = 1)
|
||||
}
|
||||
})
|
@ -1,66 +0,0 @@
|
||||
package OpenSourceProjects_Storybook.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildFeatures.commitStatusPublisher
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.buildSteps.script
|
||||
|
||||
object OpenSourceProjects_Storybook_NativeSmokeTests : BuildType({
|
||||
uuid = "ac276912-df1a-44f1-8de2-056276193ce8"
|
||||
id = "OpenSourceProjects_Storybook_NativeSmokeTests"
|
||||
name = "Native Smoke Tests"
|
||||
|
||||
params {
|
||||
param("env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD", "true")
|
||||
}
|
||||
|
||||
vcs {
|
||||
root(OpenSourceProjects_Storybook.vcsRoots.OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster)
|
||||
|
||||
cleanCheckout = true
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
name = "crna-kitchen-sink"
|
||||
scriptContent = """
|
||||
#!/bin/sh
|
||||
|
||||
set -e -x
|
||||
|
||||
yarn
|
||||
yarn bootstrap --reactnativeapp
|
||||
cd examples-native/crna-kitchen-sink
|
||||
yarn storybook --smoke-test
|
||||
""".trimIndent()
|
||||
dockerImage = "node:%docker.node.version%"
|
||||
}
|
||||
}
|
||||
|
||||
features {
|
||||
commitStatusPublisher {
|
||||
publisher = github {
|
||||
githubUrl = "https://api.github.com"
|
||||
authType = personalToken {
|
||||
token = "credentialsJSON:5ffe2d7e-531e-4f6f-b1fc-a41bfea26eaa"
|
||||
}
|
||||
}
|
||||
param("github_oauth_user", "Hypnosphi")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependency(OpenSourceProjects_Storybook.buildTypes.OpenSourceProjects_Storybook_Bootstrap) {
|
||||
snapshot {
|
||||
onDependencyFailure = FailureAction.FAIL_TO_START
|
||||
}
|
||||
|
||||
artifacts {
|
||||
artifactRules = "dist.zip!**"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requirements {
|
||||
doesNotContain("env.OS", "Windows")
|
||||
}
|
||||
})
|
@ -25,7 +25,7 @@ object OpenSourceProjects_Storybook_Test : BuildType({
|
||||
set -e -x
|
||||
|
||||
yarn
|
||||
yarn test --core --coverage --runInBand --teamcity
|
||||
yarn test --core --coverage --teamcity --w2
|
||||
""".trimIndent()
|
||||
dockerImage = "node:%docker.node.version%"
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import jetbrains.buildServer.configs.kotlin.v2017_2.failureConditions.failOnMetr
|
||||
enum class StorybookApp(val appName: String, val exampleDir: String, val merged: Boolean = true) {
|
||||
CRA("CRA", "cra-kitchen-sink"),
|
||||
CRA_TS("CRA TS", "cra-ts-kitchen-sink"),
|
||||
CRA_REACT15("CRA REACT15", "cra-react15"),
|
||||
VUE("Vue", "vue-kitchen-sink"),
|
||||
ANGULAR("Angular", "angular-cli"),
|
||||
POLYMER("Polymer", "polymer-cli"),
|
||||
|
@ -5,13 +5,12 @@ import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = 'b1db1a3a-a4cf-46ea-8f55-98b86611f92e' (id = 'OpenSourceProjects_Storybook_CliTest')
|
||||
To apply the patch, change the buildType with uuid = '1bda59b5-d08d-4fd8-b317-953e7d79d881' (id = 'OpenSourceProjects_Storybook_Docs')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("b1db1a3a-a4cf-46ea-8f55-98b86611f92e") {
|
||||
params {
|
||||
add {
|
||||
param("docker.node.version", "latest")
|
||||
}
|
||||
changeBuildType("1bda59b5-d08d-4fd8-b317-953e7d79d881") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '1ea2b5bd-28f6-44f5-8ab3-6c659ce8fbd6' (id = 'OpenSourceProjects_Storybook_SmokeTests')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("1ea2b5bd-28f6-44f5-8ab3-6c659ce8fbd6") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '2b9c73e2-0a6e-47ca-95ae-729cac42be2b' (id = 'OpenSourceProjects_Storybook_Build_2')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("2b9c73e2-0a6e-47ca-95ae-729cac42be2b") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '42cfbb9a-f35b-4f96-afae-0b508927a737' (id = 'OpenSourceProjects_Storybook_Lint')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("42cfbb9a-f35b-4f96-afae-0b508927a737") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '42cfbb9a-f35b-4f96-afae-0b508927a738' (id = 'OpenSourceProjects_Storybook_Lint_Warnings')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("42cfbb9a-f35b-4f96-afae-0b508927a738") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-angular' (id = 'OpenSourceProjects_Storybook_Angular')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-angular") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-chromatic' (id = 'OpenSourceProjects_Storybook_Chromatic')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-chromatic") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra' (id = 'OpenSourceProjects_Storybook_CRA')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_react15' (id = 'OpenSourceProjects_Storybook_CRA_REACT15')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_react15") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_ts' (id = 'OpenSourceProjects_Storybook_CRA_TS')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_ts") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-ember' (id = 'OpenSourceProjects_Storybook_Ember')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-ember") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-html' (id = 'OpenSourceProjects_Storybook_HTML')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-html") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-marko' (id = 'OpenSourceProjects_Storybook_Marko')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-marko") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-mithril' (id = 'OpenSourceProjects_Storybook_Mithril')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-mithril") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-polymer' (id = 'OpenSourceProjects_Storybook_Polymer')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-polymer") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-preact' (id = 'OpenSourceProjects_Storybook_Preact')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-preact") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-riot' (id = 'OpenSourceProjects_Storybook_Riot')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-riot") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-svelte' (id = 'OpenSourceProjects_Storybook_Svelte')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-svelte") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6-vue' (id = 'OpenSourceProjects_Storybook_Vue')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6-vue") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '8cc5f747-4ca7-4f0d-940d-b0c422f501a6' (id = 'OpenSourceProjects_Storybook_Examples')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("8cc5f747-4ca7-4f0d-940d-b0c422f501a6") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -9,6 +9,11 @@ To apply the patch, change the buildType with uuid = '9f9177e7-9ec9-4e2e-aabb-d3
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("9f9177e7-9ec9-4e2e-aabb-d304fd667711") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
|
||||
params {
|
||||
add {
|
||||
param("docker.node.version", "10.13")
|
||||
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = '9f9177e7-9ec9-4e2e-aabb-d304fd667712' (id = 'OpenSourceProjects_Storybook_Bootstrap')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("9f9177e7-9ec9-4e2e-aabb-d304fd667712") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package OpenSourceProjects_Storybook.patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with uuid = 'd4320bd8-6094-4dd6-9bed-e13d6f0d12e2' (id = 'OpenSourceProjects_Storybook_CliTestLatestCra')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType("d4320bd8-6094-4dd6-9bed-e13d6f0d12e2") {
|
||||
check(paused == false) {
|
||||
"Unexpected paused: '$paused'"
|
||||
}
|
||||
paused = true
|
||||
}
|
17
.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts
vendored
Normal file
17
.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package OpenSourceProjects_Storybook.patches.projects
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.Project
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the project with uuid = '69382d9b-7791-418a-9ff6-1c83b86ed6b5' (id = 'OpenSourceProjects_Storybook')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeProject("69382d9b-7791-418a-9ff6-1c83b86ed6b5") {
|
||||
check(archived == false) {
|
||||
"Unexpected archived: '$archived'"
|
||||
}
|
||||
archived = true
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
|
||||
| | [React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| [Svelte](app/svelte)| [Riot](app/riot)| [Ember](app/ember)| [Preact](app/preact)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[a11y](addons/a11y) |+| |+|+|+|+|+|+| | |+|+|
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|+|+|+|+|+|
|
||||
|[a11y](addons/a11y) |+| |+|+|+|+|+|+|+|+|+|+|
|
||||
|[actions](addons/actions) |+|+*|+|+|+|+|+|+|+|+|+|+|
|
||||
|[backgrounds](addons/backgrounds) |+|*|+|+|+|+|+|+|+|+|+|+|
|
||||
|[centered](addons/centered) |+| |+|+| |+|+| |+| |+|+|
|
||||
|[contexts](addons/contexts) |+| |+| | | | | | | | |+|
|
||||
|[events](addons/events) |+| |+|+|+|+|+|+| | |+|+|
|
||||
|[graphql](addons/graphql) |+| | | | | | | | | | | |
|
||||
|[google-analytics](addons/google-analytics) |+|+|+|+|+|+|+|+|+|+|+|+|
|
||||
|
8553
CHANGELOG.md
8553
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
121
CONTRIBUTING.md
121
CONTRIBUTING.md
@ -28,14 +28,16 @@ To test your project against the current latest version of storybook, you can cl
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
yarn bootstrap
|
||||
```
|
||||
|
||||
The bootstrap command might ask which sections of the codebase you want to bootstrap. Unless you're going to work with ReactNative or the Documentation, you can keep the default.
|
||||
|
||||
You can also pick directly from CLI:
|
||||
|
||||
yarn bootstrap --core
|
||||
```sh
|
||||
yarn bootstrap --core
|
||||
```
|
||||
|
||||
#### 2a. Run unit tests
|
||||
|
||||
@ -67,7 +69,7 @@ Before the tests are run, the project must be bootstrapped with core. You can ac
|
||||
This option executes tests from `<rootdir>/examples/official-storybook`
|
||||
In order for the image snapshots to be correctly generated, you must have a static build of the storybook up-to-date :
|
||||
|
||||
```javascript
|
||||
```sh
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
cd ../..
|
||||
@ -80,68 +82,118 @@ Puppeteer is used to launch and grab screenshots of example pages, while jest is
|
||||
|
||||
If you made any changes to the `lib/cli` package, the easiest way to verify that it doesn't break anything is to run e2e tests:
|
||||
|
||||
yarn test --cli
|
||||
```sh
|
||||
yarn test --cli
|
||||
```
|
||||
|
||||
This will run a bash script located at `lib/cli/test/run_tests.sh`. It will copy the contents of `fixtures` into a temporary `run` directory, run `getstorybook` in each of the subdirectories, and check that storybook starts successfully using `yarn storybook --smoke-test`.
|
||||
|
||||
After that, the `run` directory content will be compared with `snapshots`. You can update the snapshots by passing an `--update` flag:
|
||||
|
||||
yarn test --cli --update
|
||||
```sh
|
||||
yarn test --cli --update
|
||||
```
|
||||
|
||||
In that case, please check the git diff before committing to make sure it only contains the intended changes.
|
||||
|
||||
#### 2c. Link `storybook` and any other required dependencies:
|
||||
#### 2c. Run Linter
|
||||
|
||||
If you want to test your own existing project using the GitHub version of storybook, you need to `link` the packages you use in your project.
|
||||
We use eslint as a linter for all code (including typescript code).
|
||||
|
||||
All you have to run is:
|
||||
|
||||
```sh
|
||||
cd app/react
|
||||
yarn link
|
||||
|
||||
cd <your-project>
|
||||
yarn link @storybook/react
|
||||
|
||||
# repeat with whichever other parts of the monorepo you are using.
|
||||
yarn lint
|
||||
```
|
||||
|
||||
It can be immensely helpful to get feedback in your editor, if you're using VsCode, you should install the `eslint` plugin and configure it with these settings:
|
||||
|
||||
```plaintext
|
||||
"eslint.autoFixOnSave": true,
|
||||
"eslint.packageManager": "yarn",
|
||||
"eslint.options": {
|
||||
"cache": true,
|
||||
"cacheLocation": ".cache/eslint",
|
||||
"extensions": [".js", ".jsx", ".mjs", ".json", ".ts", ".tsx"]
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
{"language": "typescript", "autoFix": true },
|
||||
{"language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
"eslint.alwaysShowStatus": true
|
||||
```
|
||||
|
||||
This should enable auto-fix for all source files, and give linting warnings and errors within your editor.
|
||||
|
||||
### Reproductions
|
||||
|
||||
#### In the monorepo
|
||||
|
||||
The best way to help figure out an issue you are having is to produce a minimal reproduction against the `master` branch.
|
||||
|
||||
A good way to do that is using the example `cra-kitchen-sink` app embedded in this repository:
|
||||
|
||||
```sh
|
||||
# Download and build this repository:
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
# Download and build this repository:
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
|
||||
# make changes to try and reproduce the problem, such as adding components + stories
|
||||
cd examples/cra-kitchen-sink
|
||||
yarn storybook
|
||||
# make changes to try and reproduce the problem, such as adding components + stories
|
||||
cd examples/cra-kitchen-sink
|
||||
yarn storybook
|
||||
|
||||
# see if you can see the problem, if so, commit it:
|
||||
git checkout "branch-describing-issue"
|
||||
git add -A
|
||||
git commit -m "reproduction for issue #123"
|
||||
# see if you can see the problem, if so, commit it:
|
||||
git checkout "branch-describing-issue"
|
||||
git add -A
|
||||
git commit -m "reproduction for issue #123"
|
||||
|
||||
# fork the storybook repo to your account, then add the resulting remote
|
||||
git remote add <your-username> https://github.com/<your-username>/storybook.git
|
||||
git push -u <your-username> master
|
||||
# fork the storybook repo to your account, then add the resulting remote
|
||||
git remote add <your-username> https://github.com/<your-username>/storybook.git
|
||||
git push -u <your-username> master
|
||||
```
|
||||
|
||||
If you follow that process, you can then link to the GitHub repository in the issue. See <https://github.com/storybooks/storybook/issues/708#issuecomment-290589886> for an example.
|
||||
|
||||
**NOTE**: If your issue involves a webpack config, create-react-app will prevent you from modifying the _app's_ webpack config, however, you can still modify storybook's to mirror your app's version of the storybook. Alternatively, use `yarn eject` in the CRA app to get a modifiable webpack config.
|
||||
|
||||
#### Outside the monorepo
|
||||
|
||||
Sometimes your storybook is deeply ingrained in your own setup and it's hard to create a minimal viable reproduction somewhere else.
|
||||
|
||||
Inside the storybook repo we have a script that allows you to test the packages inside this repo in your own seperate project.
|
||||
|
||||
You can use `npm link` on all packages, but npm linking is cumbersome and has subtle differences from what happens in a registry-based installation.
|
||||
So the way our script works is that it:
|
||||
|
||||
- sets up a npm registry running on your own local machine
|
||||
- changes your default registry to this local one
|
||||
- builds all packages in the storybook repo
|
||||
- publishes all packages as latest
|
||||
|
||||
Our script leaves the local registry running, for **as long as you keep it running** you can install storybook packages from this local registry.
|
||||
|
||||
- Navigate to your own project and then change `package.json` so the storybook packages match the version of the one you just published.
|
||||
- Then just do the normal install procedure using `yarn` or `npm`
|
||||
- Start using your storybook as normally.
|
||||
|
||||
If you've made a change to storybook's codebase and would want this change to be reflected in your app:
|
||||
|
||||
- Ensure the storybook packages are transpiled, by either having run `yarn dev` or `yarn bootstrap --core`.
|
||||
- Go to the terminal where the local regitry is running and press `<Enter>`. This will kick off a new publish.
|
||||
- Run the install procedure again in your local repo, (you may need to clean out node_modules first).
|
||||
- Restart your storybook.
|
||||
|
||||
### Updating Tests
|
||||
|
||||
Before any contributes are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
|
||||
When creating new unit test files, the tests should adhere to a particular folder structure and naming convention, as defined below.
|
||||
|
||||
```sh
|
||||
#Proper naming convention and structure for js tests files
|
||||
# Proper naming convention and structure for js tests files
|
||||
+-- parentFolder
|
||||
| +-- [filename].js
|
||||
| +-- [filename].test.js
|
||||
@ -149,14 +201,9 @@ When creating new unit test files, the tests should adhere to a particular folde
|
||||
|
||||
## Pull Requests (PRs)
|
||||
|
||||
We welcome your contributions. There are many ways you can help us. This is few of those ways:
|
||||
We welcome all contributions. There are many ways you can help us. This is few of those ways:
|
||||
|
||||
- Fix typos and add more [documentation](https://github.com/storybooks/storybook/labels/needs%20docs).
|
||||
- Try to fix some [bugs](https://github.com/storybooks/storybook/labels/bug).
|
||||
- Work on [API](https://github.com/storybooks/storybook/labels/enhancement%3A%20api), [Addons](https://github.com/storybooks/storybook/labels/enhancement%3A%20addons), [UI](https://github.com/storybooks/storybook/labels/enhancement%3A%20ui) or [Webpack](https://github.com/storybooks/storybook/labels/enhancement%3A%20webpack) use enhancements and new [features](https://github.com/storybooks/storybook/labels/feature%20request).
|
||||
- Add more [tests](https://codecov.io/gh/storybooks/storybook/tree/master/packages) (especially for the [UI](https://codecov.io/gh/storybooks/storybook/tree/master/packages/storybook-ui/src)).
|
||||
|
||||
Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if tests are failing. If you need any help, create an issue and ask.
|
||||
Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if tests are failing. If you need any help, the best way is to [join the discord server and ask in the maintenance channel](https://discord.gg/sMFvFsG).
|
||||
|
||||
### Reviewing PRs
|
||||
|
||||
|
387
MIGRATION.md
387
MIGRATION.md
@ -1,5 +1,22 @@
|
||||
# Migration
|
||||
|
||||
- [From version 5.0.x to 5.1.x](#from-version-50x-to-51x)
|
||||
- [React native server](#react-native-server)
|
||||
- [From version 5.0.1 to 5.0.2](#from-version-501-to-502)
|
||||
- [Deprecate webpack extend mode](#deprecate-webpack-extend-mode)
|
||||
- [From version 4.1.x to 5.0.x](#from-version-41x-to-50x)
|
||||
- [Webpack config simplification](#webpack-config-simplification)
|
||||
- [Theming overhaul](#theming-overhaul)
|
||||
- [Story hierarchy defaults](#story-hierarchy-defaults)
|
||||
- [Options addon deprecated](#options-addon-deprecated)
|
||||
- [Individual story decorators](#individual-story-decorators)
|
||||
- [Addon backgrounds uses parameters](#addon-backgrounds-uses-parameters)
|
||||
- [Addon cssresources name attribute renamed](#addon-cssresources-name-attribute-renamed)
|
||||
- [Addon viewport uses parameters](#addon-viewport-uses-parameters)
|
||||
- [Addon a11y uses parameters](#addon-a11y-uses-parameters-decorator-renamed)
|
||||
- [New keyboard shortcuts defaults](#new-keyboard-shortcuts-defaults)
|
||||
- [New URL structure](#new-url-structure)
|
||||
- [Vue integration](#vue-integration)
|
||||
- [From version 4.0.x to 4.1.x](#from-version-40x-to-41x)
|
||||
- [Private addon config](#private-addon-config)
|
||||
- [React 15.x](#react-15x)
|
||||
@ -37,6 +54,354 @@
|
||||
- [Packages renaming](#packages-renaming)
|
||||
- [Deprecated embedded addons](#deprecated-embedded-addons)
|
||||
|
||||
## From version 5.0.x to 5.1.x
|
||||
|
||||
### React native server
|
||||
|
||||
Storybook 5.1 contains a major overhaul of `@storybook/react-native` as compared to 4.1 (we didn't ship a version of RN in 5.0 due to timing constraints). Storybook for RN consists of an an UI for browsing stories on-device or in a simulator, and an optional webserver which can also be used to browse stories and web addons.
|
||||
|
||||
5.1 refactors both pieces:
|
||||
|
||||
- `@storybook/react-native` no longer depends on the Storybook UI and only contains on-device functionality
|
||||
- `@storybook/react-native-server` is a new package for those who wish to run a web server alongside their device UI
|
||||
|
||||
In addition, both packages share more code with the rest of Storybook, which will reduce bugs and increase compatibility (e.g. with the latest versions of babel, etc.).
|
||||
|
||||
As a user with an existing 4.1.x RN setup, no migration should be necessary to your RN app. Simply upgrading the library should be enough.
|
||||
|
||||
If you wish to run the optional web server, you will need to do the following migration:
|
||||
|
||||
- Add `babel-loader` as a dev dependency
|
||||
- Add `@storybook/react-native-server` as a dev dependency
|
||||
- Change your "storybook" `package.json` script from `storybook start [-p ...]` to `start-storybook [-p ...]`
|
||||
|
||||
And with that you should be good to go!
|
||||
|
||||
## From version 5.0.1 to 5.0.2
|
||||
|
||||
### Deprecate webpack extend mode
|
||||
|
||||
Exporting an object from your custom webpack config puts storybook in "extend mode".
|
||||
|
||||
There was a bad bug in `v5.0.0` involving webpack "extend mode" that caused webpack issues for users migrating from `4.x`. We've fixed this problem in `v5.0.2` but it means that extend-mode has a different behavior if you're migrating from `5.0.0` or `5.0.1`. In short, `4.x` extended a base config with the custom config, whereas `5.0.0-1` extended the base with a richer config object that could conflict with the custom config in different ways from `4.x`.
|
||||
|
||||
We've also deprecated "extend mode" because it doesn't add a lot of value over "full control mode", but adds more code paths, documentation, user confusion etc. Starting in SB6.0 we will only support "full control mode" customization.
|
||||
|
||||
To migrate from extend-mode to full-control mode, if your extend-mode webpack config looks like this:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
/* ... */
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
In full control mode, you need modify the default config to have the rules of your liking:
|
||||
|
||||
```js
|
||||
module.exports = ({ config }) => ({
|
||||
...config,
|
||||
module: {
|
||||
...config.module,
|
||||
rules: [
|
||||
/* your own rules "..." here and/or some subset of config.module.rules */
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Please refer to the [current custom webpack documentation](https://github.com/storybooks/storybook/blob/next/docs/src/pages/configurations/custom-webpack-config/index.md) for more information on custom webpack config and to [Issue #6081](https://github.com/storybooks/storybook/issues/6081) for more information about the change.
|
||||
|
||||
## From version 4.1.x to 5.0.x
|
||||
|
||||
Storybook 5.0 includes sweeping UI changes as well as changes to the addon API and custom webpack configuration. We've tried to keep backwards compatibility in most cases, but there are some notable exceptions documented below.
|
||||
|
||||
## Webpack config simplification
|
||||
|
||||
The API for custom webpack configuration has been simplifed in 5.0, but it's a breaking change. Storybook's "full control mode" for webpack allows you to override the webpack config with a function that returns a configuration object.
|
||||
|
||||
In Storybook 5 there is a single signature for full-control mode that takes a parameters object with the fields `config` and `mode`:
|
||||
|
||||
```js
|
||||
module.exports = ({ config, mode }) => { config.module.rules.push(...); return config; }
|
||||
```
|
||||
|
||||
In contrast, the 4.x configuration function accepted either two or three arguments (`(baseConfig, mode)`, or `(baseConfig, mode, defaultConfig)`). The `config` object in the 5.x signature is equivalent to 4.x's `defaultConfig`.
|
||||
|
||||
Please see the [current custom webpack documentation](https://github.com/storybooks/storybook/blob/next/docs/src/pages/configurations/custom-webpack-config/index.md) for more information on custom webpack config.
|
||||
|
||||
## Theming overhaul
|
||||
|
||||
Theming has been rewritten in v5. If you used theming in v4, please consult the [theming docs](https://github.com/storybooks/storybook/blob/next/docs/src/pages/configurations/theming/index.md) to learn about the new API.
|
||||
|
||||
## Story hierarchy defaults
|
||||
|
||||
Storybook's UI contains a hierarchical tree of stories that can be configured by `hierarchySeparator` and `hierarchyRootSeparator` [options](./addons/options/README.md).
|
||||
|
||||
In Storybook 4.x the values defaulted to `null` for both of these options, so that there would be no hierarchy by default.
|
||||
|
||||
In 5.0, we now provide recommended defaults:
|
||||
|
||||
```js
|
||||
{
|
||||
hierarchyRootSeparator: '|',
|
||||
hierarchySeparator: /\/|\./,
|
||||
}
|
||||
```
|
||||
|
||||
This means if you use the characters { `|`, `/`, `.` } in your story kinds it will triggger the story hierarchy to appear. For example `storiesOf('UI|Widgets/Basics/Button')` will create a story root called `UI` containing a `Widgets/Basics` group, containing a `Button` component.
|
||||
|
||||
If you wish to opt-out of this new behavior and restore the flat UI, simply set them back to `null` in your storybook config, or remove { `|`, `/`, `.` } from your story kinds:
|
||||
|
||||
```js
|
||||
addParameters({
|
||||
options: {
|
||||
hierarchyRootSeparator: null,
|
||||
hierarchySeparator: null,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Options addon deprecated
|
||||
|
||||
In 4.x we added story parameters. In 5.x we've deprecated the options addon in favor of [global parameters](./docs/src/pages/configurations/options-parameter/index.md), and we've also renamed some of the options in the process (though we're maintaining backwards compatibility until 6.0).
|
||||
|
||||
Here's an old configuration:
|
||||
|
||||
```js
|
||||
addDecorator(
|
||||
withOptions({
|
||||
name: 'Storybook',
|
||||
url: 'https://storybook.js.org',
|
||||
goFullScreen: false,
|
||||
addonPanelInRight: true,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
And here's its new counterpart:
|
||||
|
||||
```js
|
||||
import { create } from '@storybook/theming';
|
||||
addParameters({
|
||||
options: {
|
||||
theme: create({
|
||||
base: 'light',
|
||||
brandTitle: 'Storybook',
|
||||
brandUrl: 'https://storybook.js.org',
|
||||
// To control appearance:
|
||||
// brandImage: 'http://url.of/some.svg',
|
||||
}),
|
||||
isFullscreen: false,
|
||||
panelPosition: 'right',
|
||||
isToolshown: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Here is the mapping from old options to new:
|
||||
|
||||
| Old | New |
|
||||
| ----------------- | ---------------- |
|
||||
| name | theme.brandTitle |
|
||||
| url | theme.brandUrl |
|
||||
| goFullScreen | isFullscreen |
|
||||
| showStoriesPanel | showNav |
|
||||
| showAddonPanel | showPanel |
|
||||
| addonPanelInRight | panelPosition |
|
||||
| showSearchBox | |
|
||||
| | isToolshown |
|
||||
|
||||
Storybook v5 removes the search dialog box in favor of a quick search in the navigation view, so `showSearchBox` has been removed.
|
||||
|
||||
Storybook v5 introduce a new tool bar above the story view and you can show\hide it with the new `isToolshown` option.
|
||||
|
||||
## Individual story decorators
|
||||
|
||||
The behavior of adding decorators to a kind has changed in SB5 ([#5781](https://github.com/storybooks/storybook/issues/5781)).
|
||||
|
||||
In SB4 it was possible to add decorators to only a subset of the stories of a kind.
|
||||
|
||||
```js
|
||||
storiesOf('Stories', module)
|
||||
.add('noncentered', () => 'Hello')
|
||||
.addDecorator(centered)
|
||||
.add('centered', () => 'Hello');
|
||||
```
|
||||
|
||||
The semantics has changed in SB5 so that calling `addDecorator` on a kind adds a decorator to all its stories, no matter the order. So in the previous example, both stories would be centered.
|
||||
|
||||
To allow for a subset of the stories in a kind to be decorated, we've added the ability to add decorators to individual stories using parameters:
|
||||
|
||||
```js
|
||||
storiesOf('Stories', module)
|
||||
.add('noncentered', () => 'Hello')
|
||||
.add('centered', () => 'Hello', { decorators: [centered] });
|
||||
```
|
||||
|
||||
## Addon backgrounds uses parameters
|
||||
|
||||
Similarly, `@storybook/addon-backgrounds` uses parameters to pass background options. If you previously had:
|
||||
|
||||
```js
|
||||
import { withBackgrounds } from `@storybook/addon-backgrounds`;
|
||||
|
||||
storiesOf('Stories', module)
|
||||
.addDecorator(withBackgrounds(options));
|
||||
```
|
||||
|
||||
You should replace it with:
|
||||
|
||||
```js
|
||||
storiesOf('Stories', module).addParameters({ backgrounds: options });
|
||||
```
|
||||
|
||||
You can pass `backgrounds` parameters at the global level (via `addParameters` imported from `@storybook/react` et al.), and the story level (via the third argument to `.add()`).
|
||||
|
||||
## Addon cssresources name attribute renamed
|
||||
|
||||
In the options object for `@storybook/addon-cssresources`, the `name` attribute for each resource has been renamed to `id`. If you previously had:
|
||||
|
||||
```js
|
||||
import { withCssResources } from '@storybook/addon-cssresources';
|
||||
import { addDecorator } from '@storybook/react';
|
||||
|
||||
addDecorator(
|
||||
withCssResources({
|
||||
cssresources: [
|
||||
{
|
||||
name: `bluetheme`, // Previous
|
||||
code: `<style>body { background-color: lightblue; }</style>`,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
You should replace it with:
|
||||
|
||||
```js
|
||||
import { withCssResources } from '@storybook/addon-cssresources';
|
||||
import { addDecorator } from '@storybook/react';
|
||||
|
||||
addDecorator(
|
||||
withCssResources({
|
||||
cssresources: [
|
||||
{
|
||||
id: `bluetheme`, // Renamed
|
||||
code: `<style>body { background-color: lightblue; }</style>`,
|
||||
picked: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
## Addon viewport uses parameters
|
||||
|
||||
Similarly, `@storybook/addon-viewport` uses parameters to pass viewport options. If you previously had:
|
||||
|
||||
```js
|
||||
import { configureViewport } from `@storybook/addon-viewport`;
|
||||
|
||||
configureViewport(options);
|
||||
```
|
||||
|
||||
You should replace it with:
|
||||
|
||||
```js
|
||||
import { addParameters } from '@storybook/react'; // or others
|
||||
|
||||
addParameters({ viewport: options });
|
||||
```
|
||||
|
||||
The `withViewport` decorator is also no longer supported and should be replaced with a parameter based API as above. Also the `onViewportChange` callback is no longer supported.
|
||||
|
||||
See the [viewport addon README](https://github.com/storybooks/storybook/blob/master/addons/viewport/README.md) for more information.
|
||||
|
||||
## Addon a11y uses parameters, decorator renamed
|
||||
|
||||
Similarly, `@storybook/addon-a11y` uses parameters to pass a11y options. If you previously had:
|
||||
|
||||
```js
|
||||
import { configureA11y } from `@storybook/addon-a11y`;
|
||||
|
||||
configureA11y(options);
|
||||
```
|
||||
|
||||
You should replace it with:
|
||||
|
||||
```js
|
||||
import { addParameters } from '@storybook/react'; // or others
|
||||
|
||||
addParameters({ a11y: options });
|
||||
```
|
||||
|
||||
You can also pass `a11y` parameters at the component level (via `storiesOf(...).addParameters`), and the story level (via the third argument to `.add()`).
|
||||
|
||||
Furthermore, the decorator `checkA11y` has been deprecated and renamed to `withA11y` to make it consistent with other Storybook decorators.
|
||||
|
||||
See the [a11y addon README](https://github.com/storybooks/storybook/blob/master/addons/a11y/README.md) for more information.
|
||||
|
||||
## New keyboard shortcuts defaults
|
||||
|
||||
Storybook's keyboard shortcuts are updated in 5.0, but they are configurable via the menu so if you want to set them back you can:
|
||||
|
||||
| Shorctut | Old | New |
|
||||
| ---------------------- | ----------- | ----- |
|
||||
| Toggle sidebar | cmd-shift-X | S |
|
||||
| Toggle addons panel | cmd-shift-Z | A |
|
||||
| Toggle addons position | cmd-shift-G | D |
|
||||
| Toggle fullscreen | cmd-shift-F | F |
|
||||
| Next story | cmd-shift-→ | alt-→ |
|
||||
| Prev story | cmd-shift-← | alt-← |
|
||||
| Next component | | alt-↓ |
|
||||
| Prev component | | alt-↑ |
|
||||
| Search | | / |
|
||||
|
||||
## New URL structure
|
||||
|
||||
We've update Storybook's URL structure in 5.0. The old structure used URL parameters to save the UI state, resulting in long ugly URLs. v5 respects the old URL parameters, but largely does away with them.
|
||||
|
||||
The old structure encoded `selectedKind` and `selectedStory` among other parameters. Storybook v5 respects these parameters but will issue a deprecation message in the browser console warning of potential future removal.
|
||||
|
||||
The new URL structure looks like:
|
||||
|
||||
```
|
||||
https://url-of-storybook?path=/story/<storyId>
|
||||
```
|
||||
|
||||
The structure of `storyId` is a slugified `<selectedKind>--<selectedStory>` (slugified = lowercase, hyphen-separated). Each `storyId` must be unique. We plan to build more features into Storybook in upcoming versions based on this new structure.
|
||||
|
||||
## Rename of the `--secure` cli parameter to `--https`
|
||||
|
||||
Storybook for React Native's start commands & the Web versions' start command were a bit different, for no reason.
|
||||
We've changed the start command for Reactnative to match the other.
|
||||
|
||||
This means that when you previously used the `--secure` flag like so:
|
||||
|
||||
```sh
|
||||
start-storybook --secure
|
||||
# or
|
||||
start-storybook --s
|
||||
```
|
||||
|
||||
You have to replace it with:
|
||||
|
||||
```sh
|
||||
start-storybook --https
|
||||
```
|
||||
|
||||
## Vue integration
|
||||
|
||||
The Vue integration was updated, so that every story returned from a story or decorator function is now being normalized with `Vue.extend` **and** is being wrapped by a functional component. Returning a string from a story or decorator function is still supported and is treated as a component with the returned string as the template.
|
||||
|
||||
Currently there is no recommended way of accessing the component options of a story inside a decorator.
|
||||
|
||||
## From version 4.0.x to 4.1.x
|
||||
|
||||
There are are a few migrations you should be aware of in 4.1, including one unintentionally breaking change for advanced addon usage.
|
||||
@ -66,17 +431,19 @@ However, if you're developing React components, this means you need to upgrade t
|
||||
Also, here's the error you'll get if you're running an older version of React:
|
||||
|
||||
```
|
||||
|
||||
core.browser.esm.js:15 Uncaught TypeError: Object(...) is not a function
|
||||
at Module../node_modules/@emotion/core/dist/core.browser.esm.js (core.browser.esm.js:15)
|
||||
at __webpack_require__ (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Module../node_modules/@emotion/styled-base/dist/styled-base.browser.esm.js (styled-base.browser.esm.js:1)
|
||||
at __webpack_require__ (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Module../node_modules/@emotion/styled/dist/styled.esm.js (styled.esm.js:1)
|
||||
at __webpack_require__ (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Object../node_modules/@storybook/components/dist/navigation/MenuLink.js (MenuLink.js:12)
|
||||
at Module../node_modules/@emotion/core/dist/core.browser.esm.js (core.browser.esm.js:15)
|
||||
at **webpack_require** (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Module../node_modules/@emotion/styled-base/dist/styled-base.browser.esm.js (styled-base.browser.esm.js:1)
|
||||
at **webpack_require** (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Module../node_modules/@emotion/styled/dist/styled.esm.js (styled.esm.js:1)
|
||||
at **webpack_require** (bootstrap:724)
|
||||
at fn (bootstrap:101)
|
||||
at Object../node_modules/@storybook/components/dist/navigation/MenuLink.js (MenuLink.js:12)
|
||||
|
||||
```
|
||||
|
||||
### Generic addons
|
||||
|
36
README.md
36
README.md
@ -85,18 +85,18 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
|
||||
|
||||
| Framework | Demo | |
|
||||
| -------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| [React](app/react) | [v4.0.0](https://storybooks-official.netlify.com) | [](app/react) |
|
||||
| [React](app/react) | [v5.0.0](https://storybooks-official.netlify.com) | [](app/react) |
|
||||
| [React Native](app/react-native) | - | [](app/react-native) |
|
||||
| [Vue](app/vue) | [v4.0.0](https://storybooks-vue.netlify.com/) | [](app/vue) |
|
||||
| [Angular](app/angular) | [v4.0.0](https://storybooks-angular.netlify.com/) | [](app/angular) |
|
||||
| [Polymer](app/polymer) | [v4.0.0](https://storybooks-polymer.netlify.com/) | [](app/polymer) |
|
||||
| [Mithril](app/mithril) | [v4.0.0](https://storybooks-mithril.netlify.com/) | [](app/mithril) |
|
||||
| [Marko](app/marko) | [v4.0.0](https://storybooks-marko.netlify.com/) | [](app/marko) |
|
||||
| [HTML](app/html) | [v4.0.0](https://storybooks-html.netlify.com/) | [](app/html) |
|
||||
| [Svelte](app/svelte) | [v4.0.0](https://storybooks-svelte.netlify.com/) | [](app/svelte) |
|
||||
| [Riot](app/riot) | [v4.0.0](https://storybooks-riot.netlify.com/) | [](app/riot) |
|
||||
| [Ember](app/ember) | [v4.0.0](https://storybooks-ember.netlify.com/) | [](app/ember) |
|
||||
| [Preact](app/preact) | [v4.0.0](https://storybooks-preact.netlify.com/) | [](app/preact) |
|
||||
| [Vue](app/vue) | [v5.0.0](https://storybooks-vue.netlify.com/) | [](app/vue) |
|
||||
| [Angular](app/angular) | [v5.0.0](https://storybooks-angular.netlify.com/) | [](app/angular) |
|
||||
| [Polymer](app/polymer) | [v5.0.0](https://storybooks-polymer.netlify.com/) | [](app/polymer) |
|
||||
| [Mithril](app/mithril) | [v5.0.0](https://storybooks-mithril.netlify.com/) | [](app/mithril) |
|
||||
| [Marko](app/marko) | [v5.0.0](https://storybooks-marko.netlify.com/) | [](app/marko) |
|
||||
| [HTML](app/html) | [v5.0.0](https://storybooks-html.netlify.com/) | [](app/html) |
|
||||
| [Svelte](app/svelte) | [v5.0.0](https://storybooks-svelte.netlify.com/) | [](app/svelte) |
|
||||
| [Riot](app/riot) | [v5.0.0](https://storybooks-riot.netlify.com/) | [](app/riot) |
|
||||
| [Ember](app/ember) | [v5.0.0](https://storybooks-ember.netlify.com/) | [](app/ember) |
|
||||
| [Preact](app/preact) | [v5.0.0](https://storybooks-preact.netlify.com/) | [](app/preact) |
|
||||
|
||||
### Sub Projects
|
||||
|
||||
@ -111,6 +111,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
|
||||
| [actions](addons/actions/) | Log actions as users interact with components in the Storybook UI |
|
||||
| [backgrounds](addons/backgrounds/) | Let users choose backgrounds in the Storybook UI |
|
||||
| [centered](addons/centered/) | Center the alignment of your components within the Storybook UI |
|
||||
| [contexts](addons/contexts/) | Interactively inject component contexts for stories in the Storybook UI |
|
||||
| [cssresources](addons/cssresources/) | Dynamically add/remove css resources to the component iframe |
|
||||
| [events](addons/events/) | Interactively fire events to components that respond to EventEmitter |
|
||||
| [graphql](addons/graphql/) | Query a GraphQL server within Storybook stories |
|
||||
@ -131,10 +132,10 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
|
||||
|
||||
We have a badge! Link it to your live Storybook example.
|
||||
|
||||

|
||||

|
||||
|
||||
```md
|
||||
[](link to site)
|
||||
[](link to site)
|
||||
```
|
||||
|
||||
If you're looking for material to use in your presentation about storybook, like logo's video material and the colors we use etc, you can find all of that at our [press repo](https://github.com/storybooks/press).
|
||||
@ -153,15 +154,15 @@ We welcome contributions to Storybook!
|
||||
|
||||
- 📥 Pull requests and 🌟 Stars are always welcome.
|
||||
- Read our [contributing guide](CONTRIBUTING.md) to get started.
|
||||
or find us on [Discord](https://discord.gg/sMFvFsG), we're will take the time to guide you
|
||||
or find us on [Discord](https://discord.gg/sMFvFsG), we're will take the time to guide you
|
||||
|
||||
Looking for a first issue to tackle?
|
||||
Looking for a first issue to tackle?
|
||||
|
||||
- We tag issues with [](https://github.com/storybooks/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) when we think they are well suited for people who are new to the codebase or OSS in general.
|
||||
- [Talk to us](https://discord.gg/sMFvFsG), we'll find something to suits your skills and learning interest.
|
||||
|
||||
### Development scripts
|
||||
|
||||
Storybook is organized as a monorepo using [Lerna](https://lernajs.io). Useful scripts include:
|
||||
#### `yarn bootstrap`
|
||||
|
||||
> Installs package dependencies and links packages together - using lerna
|
||||
@ -195,7 +196,6 @@ Become a sponsor and get your logo on our README on Github with a link to your s
|
||||
<a href="https://opencollective.com/storybook/sponsor/2/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/3/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/4/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://applitools.com/" target="_blank"><img src="https://file-xvimrfykua.now.sh/"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/5/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/6/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/7/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/7/avatar.svg"></a>
|
||||
@ -261,4 +261,4 @@ Support us with a monthly donation and help us continue our activities. \[[Becom
|
||||
|
||||
[MIT](https://github.com/storybooks/storybook/blob/master/LICENSE)
|
||||
|
||||
-the end-
|
||||
-the end-
|
||||
|
@ -4,7 +4,7 @@ This storybook addon can be helpful to make your UI components more accessible.
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting started
|
||||
|
||||
@ -20,16 +20,16 @@ Add this line to your `addons.js` file (create this file inside your storybook c
|
||||
import '@storybook/addon-a11y/register';
|
||||
```
|
||||
|
||||
import the `withA11Y` decorator to check your stories for violations within your components.
|
||||
import the `withA11y` decorator to check your stories for violations within your components.
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { withA11Y } from '@storybook/addon-a11y';
|
||||
import { storiesOf, addDecorator } from '@storybook/react';
|
||||
import { withA11y } from '@storybook/addon-a11y';
|
||||
|
||||
// should only be added once
|
||||
// best place is in config.js
|
||||
addDecorator(withA11Y)
|
||||
addDecorator(withA11y)
|
||||
|
||||
storiesOf('button', module)
|
||||
.add('Accessible', () => (
|
||||
@ -51,17 +51,18 @@ You can override these options at story level too.
|
||||
import React from 'react';
|
||||
import { storiesOf, addDecorator, addParameters } from '@storybook/react';
|
||||
|
||||
import { withA11Y } from '@storybook/addon-a11y';
|
||||
import { withA11y } from '@storybook/addon-a11y';
|
||||
|
||||
addDecorator(withA11Y)
|
||||
addDecorator(withA11y)
|
||||
addParameters({
|
||||
a11y: {
|
||||
// ... axe options
|
||||
element: '#root', // optional selector which element to inspect
|
||||
config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
|
||||
options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
storiesOf('button', module)
|
||||
.add('Accessible', () => (
|
||||
<button style={{ backgroundColor: 'black', color: 'white', }}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "5.0.0-alpha.7",
|
||||
"version": "5.1.0-rc.0",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -16,30 +16,38 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/storybooks/storybook.git"
|
||||
"url": "git+https://github.com/storybooks/storybook.git",
|
||||
"directory": "addons/a11y"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.0.0-alpha.7",
|
||||
"@storybook/client-logger": "5.0.0-alpha.7",
|
||||
"@storybook/components": "5.0.0-alpha.7",
|
||||
"@storybook/core-events": "5.0.0-alpha.7",
|
||||
"@storybook/theming": "5.0.0-alpha.7",
|
||||
"axe-core": "^3.1.2",
|
||||
"@storybook/addons": "5.1.0-rc.0",
|
||||
"@storybook/api": "5.1.0-rc.0",
|
||||
"@storybook/client-logger": "5.1.0-rc.0",
|
||||
"@storybook/components": "5.1.0-rc.0",
|
||||
"@storybook/core-events": "5.1.0-rc.0",
|
||||
"@storybook/theming": "5.1.0-rc.0",
|
||||
"axe-core": "^3.2.2",
|
||||
"common-tags": "^1.8.0",
|
||||
"core-js": "^2.6.2",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"memoizerific": "^1.11.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react": "^16.8.4",
|
||||
"react-redux": "^7.0.2",
|
||||
"react-sizeme": "^2.5.2",
|
||||
"redux": "^4.0.1",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/common-tags": "^1.8.0",
|
||||
"@types/react-redux": "^7.0.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
222
addons/a11y/src/components/A11YPanel.test.js
Normal file
222
addons/a11y/src/components/A11YPanel.test.js
Normal file
@ -0,0 +1,222 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { ThemeProvider, themes, convert } from '@storybook/theming';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { ScrollArea } from '@storybook/components';
|
||||
|
||||
import { A11YPanel } from './A11YPanel';
|
||||
import { EVENTS } from '../constants';
|
||||
|
||||
function createApi() {
|
||||
return {
|
||||
emit: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
const axeResult = {
|
||||
incomplete: [
|
||||
{
|
||||
id: 'color-contrast',
|
||||
impact: 'serious',
|
||||
tags: ['cat.color', 'wcag2aa', 'wcag143'],
|
||||
description:
|
||||
'Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds',
|
||||
help: 'Elements must have sufficient color contrast',
|
||||
helpUrl: 'https://dequeuniversity.com/rules/axe/3.2/color-contrast?application=axeAPI',
|
||||
nodes: [],
|
||||
},
|
||||
],
|
||||
passes: [
|
||||
{
|
||||
id: 'aria-allowed-attr',
|
||||
impact: null,
|
||||
tags: ['cat.aria', 'wcag2a', 'wcag412'],
|
||||
description: "Ensures ARIA attributes are allowed for an element's role",
|
||||
help: 'Elements must only use allowed ARIA attributes',
|
||||
helpUrl: 'https://dequeuniversity.com/rules/axe/3.2/aria-allowed-attr?application=axeAPI',
|
||||
nodes: [],
|
||||
},
|
||||
],
|
||||
violations: [
|
||||
{
|
||||
id: 'color-contrast',
|
||||
impact: 'serious',
|
||||
tags: ['cat.color', 'wcag2aa', 'wcag143'],
|
||||
description:
|
||||
'Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds',
|
||||
help: 'Elements must have sufficient color contrast',
|
||||
helpUrl: 'https://dequeuniversity.com/rules/axe/3.2/color-contrast?application=axeAPI',
|
||||
nodes: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function ThemedA11YPanel(props) {
|
||||
return (
|
||||
<ThemeProvider theme={convert(themes.light)}>
|
||||
<A11YPanel {...props} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('A11YPanel', () => {
|
||||
it('should register STORY_RENDERED and RESULT updater on mount', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
expect(api.on).not.toHaveBeenCalled();
|
||||
|
||||
// when
|
||||
mount(<ThemedA11YPanel api={api} />);
|
||||
|
||||
// then
|
||||
expect(api.on.mock.calls.length).toBe(2);
|
||||
expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED);
|
||||
expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT);
|
||||
});
|
||||
|
||||
it('should request a run on tab activation', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} />);
|
||||
expect(api.emit).not.toHaveBeenCalled();
|
||||
|
||||
// when
|
||||
wrapper.setProps({ active: true });
|
||||
wrapper.update();
|
||||
|
||||
// then
|
||||
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
|
||||
expect(wrapper.find(ScrollArea).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should deregister STORY_RENDERED and RESULT updater on unmount', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} />);
|
||||
expect(api.off).not.toHaveBeenCalled();
|
||||
|
||||
// when
|
||||
wrapper.unmount();
|
||||
|
||||
// then
|
||||
expect(api.off.mock.calls.length).toBe(2);
|
||||
expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED);
|
||||
expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT);
|
||||
});
|
||||
|
||||
it('should update run result', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} active />);
|
||||
const onUpdate = api.on.mock.calls.find(([event]) => event === EVENTS.RESULT)[1];
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.last()
|
||||
.text()
|
||||
.trim()
|
||||
).toBe('Rerun tests');
|
||||
|
||||
// when
|
||||
onUpdate(axeResult);
|
||||
|
||||
// then
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.last()
|
||||
.text()
|
||||
.trim()
|
||||
).toBe('Tests completed');
|
||||
});
|
||||
|
||||
it('should request run', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} active />);
|
||||
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.last()
|
||||
.text()
|
||||
.trim()
|
||||
).toBe('Rerun tests');
|
||||
expect(api.emit).not.toHaveBeenCalled();
|
||||
|
||||
// when
|
||||
request();
|
||||
|
||||
// then
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.last()
|
||||
.text()
|
||||
.trim()
|
||||
).toBe('Running test');
|
||||
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
|
||||
});
|
||||
|
||||
it('should NOT request run on inactive tab', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
mount(<ThemedA11YPanel api={api} active={false} />);
|
||||
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
|
||||
expect(api.emit).not.toHaveBeenCalled();
|
||||
|
||||
// when
|
||||
request();
|
||||
|
||||
// then
|
||||
expect(api.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render report', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} active />);
|
||||
const onUpdate = api.on.mock.calls.find(([event]) => event === EVENTS.RESULT)[1];
|
||||
|
||||
// when
|
||||
onUpdate(axeResult);
|
||||
|
||||
// then
|
||||
expect(wrapper.find(A11YPanel)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render loader when it's running", () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} active />);
|
||||
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
|
||||
|
||||
// when
|
||||
request();
|
||||
wrapper.update();
|
||||
|
||||
// then
|
||||
expect(wrapper.find('ScrollArea').length).toBe(0);
|
||||
expect(wrapper.find('Loader').length).toBe(1);
|
||||
expect(wrapper.find('ActionBar').length).toBe(1);
|
||||
expect(wrapper.find('Loader')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should NOT anything when tab is not active', () => {
|
||||
// given
|
||||
const api = createApi();
|
||||
|
||||
// when
|
||||
const wrapper = mount(<ThemedA11YPanel api={api} active={false} />);
|
||||
|
||||
// then
|
||||
expect(wrapper.find('ScrollArea').length).toBe(0);
|
||||
expect(wrapper.find('ActionBar').length).toBe(0);
|
||||
});
|
||||
});
|
223
addons/a11y/src/components/A11YPanel.tsx
Normal file
223
addons/a11y/src/components/A11YPanel.tsx
Normal file
@ -0,0 +1,223 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { ActionBar, Icons, ScrollArea } from '@storybook/components';
|
||||
|
||||
import { AxeResults, Result } from 'axe-core';
|
||||
import { API } from '@storybook/api';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Report } from './Report';
|
||||
import { Tabs } from './Tabs';
|
||||
import { EVENTS } from '../constants';
|
||||
|
||||
import store, { clearElements } from '../redux-config';
|
||||
|
||||
export enum RuleType {
|
||||
VIOLATION,
|
||||
PASS,
|
||||
INCOMPLETION,
|
||||
}
|
||||
|
||||
const Icon = styled(Icons)(
|
||||
{
|
||||
height: '12px',
|
||||
width: '12px',
|
||||
marginRight: '4px',
|
||||
},
|
||||
({ status, theme }: any) =>
|
||||
status === 'running'
|
||||
? {
|
||||
animation: `${theme.animation.rotate360} 1s linear infinite;`,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
const Passes = styled.span(({ theme }) => ({
|
||||
color: theme.color.positive,
|
||||
}));
|
||||
|
||||
const Violations = styled.span(({ theme }) => ({
|
||||
color: theme.color.negative,
|
||||
}));
|
||||
|
||||
const Incomplete = styled.span(({ theme }) => ({
|
||||
color: theme.color.warning,
|
||||
}));
|
||||
|
||||
const Loader = styled(({ className }) => (
|
||||
<div className={className}>
|
||||
<Icon inline icon="sync" status="running" /> Please wait while the accessibility scan is running
|
||||
...
|
||||
</div>
|
||||
))({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
});
|
||||
Loader.displayName = 'Loader';
|
||||
|
||||
interface A11YPanelState {
|
||||
status: string;
|
||||
passes: Result[];
|
||||
violations: Result[];
|
||||
incomplete: Result[];
|
||||
}
|
||||
|
||||
interface A11YPanelProps {
|
||||
active: boolean;
|
||||
api: API;
|
||||
}
|
||||
|
||||
export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
|
||||
state: A11YPanelState = {
|
||||
status: 'ready',
|
||||
passes: [],
|
||||
violations: [],
|
||||
incomplete: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { api } = this.props;
|
||||
|
||||
api.on(STORY_RENDERED, this.request);
|
||||
api.on(EVENTS.RESULT, this.onUpdate);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: A11YPanelProps) {
|
||||
// TODO: might be able to remove this
|
||||
const { active } = this.props;
|
||||
|
||||
if (!prevProps.active && active) {
|
||||
// removes all elements from the redux map in store from the previous panel
|
||||
store.dispatch(clearElements());
|
||||
this.request();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
api.off(STORY_RENDERED, this.request);
|
||||
api.off(EVENTS.RESULT, this.onUpdate);
|
||||
}
|
||||
|
||||
onUpdate = ({ passes, violations, incomplete }: AxeResults) => {
|
||||
this.setState(
|
||||
{
|
||||
status: 'ran',
|
||||
passes,
|
||||
violations,
|
||||
incomplete,
|
||||
},
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
const { status } = this.state;
|
||||
if (status === 'ran') {
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
});
|
||||
}
|
||||
}, 900);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
request = () => {
|
||||
const { api, active } = this.props;
|
||||
|
||||
if (active) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'running',
|
||||
},
|
||||
() => {
|
||||
api.emit(EVENTS.REQUEST);
|
||||
// removes all elements from the redux map in store from the previous panel
|
||||
store.dispatch(clearElements());
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { passes, violations, incomplete, status } = this.state;
|
||||
const { active } = this.props;
|
||||
|
||||
let actionTitle;
|
||||
if (status === 'ready') {
|
||||
actionTitle = 'Rerun tests';
|
||||
} else if (status === 'running') {
|
||||
actionTitle = (
|
||||
<Fragment>
|
||||
<Icon inline icon="sync" status={status} /> Running test
|
||||
</Fragment>
|
||||
);
|
||||
} else if (status === 'ran') {
|
||||
actionTitle = (
|
||||
<Fragment>
|
||||
<Icon inline icon="check" /> Tests completed
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return active ? (
|
||||
<Fragment>
|
||||
<Provider store={store}>
|
||||
{status === 'running' ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<ScrollArea vertical horizontal>
|
||||
<Tabs
|
||||
key="tabs"
|
||||
tabs={[
|
||||
{
|
||||
label: <Violations>{violations.length} Violations</Violations>,
|
||||
panel: (
|
||||
<Report
|
||||
items={violations}
|
||||
type={RuleType.VIOLATION}
|
||||
empty="No accessibility violations found."
|
||||
/>
|
||||
),
|
||||
items: violations,
|
||||
type: RuleType.VIOLATION,
|
||||
},
|
||||
{
|
||||
label: <Passes>{passes.length} Passes</Passes>,
|
||||
panel: (
|
||||
<Report
|
||||
items={passes}
|
||||
type={RuleType.PASS}
|
||||
empty="No accessibility checks passed."
|
||||
/>
|
||||
),
|
||||
items: passes,
|
||||
type: RuleType.PASS,
|
||||
},
|
||||
{
|
||||
label: <Incomplete>{incomplete.length} Incomplete</Incomplete>,
|
||||
panel: (
|
||||
<Report
|
||||
items={incomplete}
|
||||
type={RuleType.INCOMPLETION}
|
||||
empty="No accessibility checks incomplete."
|
||||
/>
|
||||
),
|
||||
items: incomplete,
|
||||
type: RuleType.INCOMPLETION,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ScrollArea>
|
||||
)}
|
||||
<ActionBar
|
||||
key="actionbar"
|
||||
actionItems={[{ title: actionTitle, onClick: this.request }]}
|
||||
/>
|
||||
</Provider>
|
||||
</Fragment>
|
||||
) : null;
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { Popout, Item, Icons, Icon, IconButton, Title, List } from '@storybook/components';
|
||||
|
||||
const getIframe = memoize(1)(() => document.getElementById('storybook-preview-iframe'));
|
||||
|
||||
const ColorIcon = styled.span(
|
||||
{
|
||||
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
|
||||
},
|
||||
({ filter }) => ({
|
||||
filter: filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`,
|
||||
})
|
||||
);
|
||||
|
||||
const Hidden = styled.div(() => ({
|
||||
display: 'none',
|
||||
}));
|
||||
|
||||
class ColorBlindness extends Component {
|
||||
state = {
|
||||
filter: false,
|
||||
};
|
||||
|
||||
setFilter = filter => {
|
||||
const iframe = getIframe();
|
||||
|
||||
if (iframe) {
|
||||
iframe.style.filter = filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`;
|
||||
|
||||
this.setState({
|
||||
filter,
|
||||
});
|
||||
} else {
|
||||
logger.error('Cannot find Storybook iframe');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { filter } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Hidden>
|
||||
<svg key="svg">
|
||||
<defs>
|
||||
<filter id="protanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.567, 0.433, 0, 0, 0 0.558, 0.442, 0, 0, 0 0, 0.242, 0.758, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="protanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.817, 0.183, 0, 0, 0 0.333, 0.667, 0, 0, 0 0, 0.125, 0.875, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.625, 0.375, 0, 0, 0 0.7, 0.3, 0, 0, 0 0, 0.3, 0.7, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.8, 0.2, 0, 0, 0 0.258, 0.742, 0, 0, 0 0, 0.142, 0.858, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.95, 0.05, 0, 0, 0 0, 0.433, 0.567, 0, 0 0, 0.475, 0.525, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.967, 0.033, 0, 0, 0 0, 0.733, 0.267, 0, 0 0, 0.183, 0.817, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatopsia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.618, 0.320, 0.062, 0, 0 0.163, 0.775, 0.062, 0, 0 0.163, 0.320, 0.516, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</Hidden>
|
||||
<Popout key="filters">
|
||||
<IconButton key="filter" active={!!filter} title="Color Blindness Emulation">
|
||||
<Icons icon="mirror" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{[
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
].map(i => (
|
||||
<Item
|
||||
key={i}
|
||||
onClick={() => {
|
||||
this.setFilter(filter === i ? null : i);
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter={i} />} />
|
||||
<Title>{i}</Title>
|
||||
</Item>
|
||||
))}
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setFilter(filter === 'mono' ? null : 'mono');
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter="mono" />} />
|
||||
<Title>mono</Title>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setFilter(null);
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon />} />
|
||||
<Title>Off</Title>
|
||||
</Item>
|
||||
</List>
|
||||
)}
|
||||
</Popout>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorBlindness;
|
126
addons/a11y/src/components/ColorBlindness.tsx
Normal file
126
addons/a11y/src/components/ColorBlindness.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components';
|
||||
|
||||
const getIframe = memoize(1)(() => document.getElementById('storybook-preview-iframe'));
|
||||
|
||||
const getFilter = (filter: string | null) => {
|
||||
if (filter === null) {
|
||||
return 'none';
|
||||
}
|
||||
if (filter === 'mono') {
|
||||
return 'grayscale(100%)';
|
||||
}
|
||||
return `url('#${filter}')`;
|
||||
};
|
||||
|
||||
const ColorIcon = styled.span(
|
||||
{
|
||||
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
|
||||
borderRadius: '1rem',
|
||||
display: 'block',
|
||||
height: '1rem',
|
||||
width: '1rem',
|
||||
},
|
||||
({ filter }: { filter: string | null }) => ({
|
||||
filter: getFilter(filter),
|
||||
}),
|
||||
({ theme }) => ({
|
||||
boxShadow: `${theme.appBorderColor} 0 0 0 1px inset`,
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface ColorBlindnessProps {}
|
||||
|
||||
interface ColorBlindnessState {
|
||||
expanded: boolean;
|
||||
filter: string | null;
|
||||
}
|
||||
|
||||
export class ColorBlindness extends Component<ColorBlindnessProps, ColorBlindnessState> {
|
||||
state: ColorBlindnessState = {
|
||||
expanded: false,
|
||||
filter: null,
|
||||
};
|
||||
|
||||
setFilter = (filter: string | null) => {
|
||||
const iframe = getIframe();
|
||||
|
||||
if (iframe) {
|
||||
iframe.style.filter = getFilter(filter);
|
||||
this.setState({
|
||||
expanded: false,
|
||||
filter,
|
||||
});
|
||||
} else {
|
||||
logger.error('Cannot find Storybook iframe');
|
||||
}
|
||||
};
|
||||
|
||||
onVisibilityChange = (s: boolean) => {
|
||||
const { expanded } = this.state;
|
||||
if (expanded !== s) {
|
||||
this.setState({ expanded: s });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { filter, expanded } = this.state;
|
||||
|
||||
let colorList = [
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
'mono',
|
||||
].map(i => ({
|
||||
id: i,
|
||||
title: i.charAt(0).toUpperCase() + i.slice(1),
|
||||
onClick: () => {
|
||||
this.setFilter(i);
|
||||
},
|
||||
right: <ColorIcon filter={i} />,
|
||||
active: filter === i,
|
||||
}));
|
||||
|
||||
if (filter !== null) {
|
||||
colorList = [
|
||||
{
|
||||
id: 'reset',
|
||||
title: 'Reset color filter',
|
||||
onClick: () => {
|
||||
this.setFilter(null);
|
||||
},
|
||||
right: undefined,
|
||||
active: false,
|
||||
},
|
||||
...colorList,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={this.onVisibilityChange}
|
||||
tooltip={<TooltipLinkList links={colorList} />}
|
||||
closeOnClick
|
||||
onDoubleClick={() => this.setFilter(null)}
|
||||
>
|
||||
<IconButton key="filter" active={!!filter} title="Color Blindness Emulation">
|
||||
<Icons icon="mirror" />
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { ActionBar, ActionButton, Icons } from '@storybook/components';
|
||||
|
||||
import EVENTS from '../constants';
|
||||
|
||||
import Tabs from './Tabs';
|
||||
import Report from './Report';
|
||||
|
||||
const Passes = styled.span(({ theme }) => ({
|
||||
color: theme.successColor,
|
||||
}));
|
||||
|
||||
const Violations = styled.span(({ theme }) => ({
|
||||
color: theme.failColor,
|
||||
}));
|
||||
|
||||
class A11YPanel extends Component {
|
||||
static propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
api: PropTypes.shape({
|
||||
on: PropTypes.func,
|
||||
emit: PropTypes.func,
|
||||
off: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
status: 'ready',
|
||||
passes: [],
|
||||
violations: [],
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { api } = this.props;
|
||||
|
||||
api.on(STORY_RENDERED, this.request);
|
||||
api.on(EVENTS.RESULT, this.onUpdate);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// TODO: might be able to remove this
|
||||
const { active } = this.props;
|
||||
|
||||
if (!prevProps.active && active) {
|
||||
this.request();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
|
||||
api.off(STORY_RENDERED, this.request);
|
||||
api.off(EVENTS.RESULT, this.onUpdate);
|
||||
}
|
||||
|
||||
onUpdate = ({ passes, violations }) => {
|
||||
this.setState(
|
||||
{
|
||||
status: 'ran',
|
||||
passes,
|
||||
violations,
|
||||
},
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
const { status } = this.state;
|
||||
if (status === 'ran') {
|
||||
this.setState({
|
||||
status: 'ready',
|
||||
});
|
||||
}
|
||||
}, 900);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
request = () => {
|
||||
const { api, active } = this.props;
|
||||
|
||||
if (active) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'running',
|
||||
},
|
||||
() => {
|
||||
api.emit(EVENTS.REQUEST);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { passes, violations, status } = this.state;
|
||||
const { active } = this.props;
|
||||
|
||||
return active ? (
|
||||
<Fragment>
|
||||
<Tabs
|
||||
key="tabs"
|
||||
tabs={[
|
||||
{
|
||||
label: <Violations>{violations.length} Violations</Violations>,
|
||||
panel: <Report passes={false} items={violations} empty="No a11y violations found." />,
|
||||
},
|
||||
{
|
||||
label: <Passes>{passes.length} Passes</Passes>,
|
||||
panel: <Report passes items={passes} empty="No a11y check passed" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ActionBar key="actionbar">
|
||||
<ActionButton onClick={this.request}>
|
||||
{status === 'ready' ? <span>RERUN TEST</span> : null}
|
||||
{status === 'running' ? (
|
||||
<Fragment>
|
||||
<Icons inline icon="timer" /> <span>Running test</span>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{status === 'ran' ? (
|
||||
<Fragment>
|
||||
<Icons inline icon="check" /> <span>Tests completed</span>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</ActionButton>
|
||||
</ActionBar>
|
||||
</Fragment>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default A11YPanel;
|
@ -1,60 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import Rules from './Rules';
|
||||
|
||||
const Item = styled.li({
|
||||
fontWeight: 600,
|
||||
});
|
||||
const ItemTitle = styled.span({
|
||||
borderBottom: '1px solid rgb(130, 130, 130)',
|
||||
width: '100%',
|
||||
display: 'inline-block',
|
||||
paddingBottom: '4px',
|
||||
marginBottom: '4px',
|
||||
});
|
||||
|
||||
function Element({ element, passes }) {
|
||||
const { any, all, none } = element;
|
||||
|
||||
const rules = [...any, ...all, ...none];
|
||||
|
||||
return (
|
||||
<Item>
|
||||
<ItemTitle>{element.target[0]}</ItemTitle>
|
||||
<Rules rules={rules} passes={passes} />
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
Element.propTypes = {
|
||||
element: PropTypes.shape({
|
||||
any: PropTypes.array.isRequired,
|
||||
all: PropTypes.array.isRequired,
|
||||
none: PropTypes.array.isRequired,
|
||||
}).isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
const Elements = ({ elements, passes }) => (
|
||||
<ol>
|
||||
{elements.map((element, index) => (
|
||||
<Element passes={passes} element={element} key={index} />
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
|
||||
Elements.propTypes = {
|
||||
elements: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
any: PropTypes.array.isRequired,
|
||||
all: PropTypes.array.isRequired,
|
||||
none: PropTypes.array.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Elements;
|
71
addons/a11y/src/components/Report/Elements.tsx
Normal file
71
addons/a11y/src/components/Report/Elements.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { NodeResult } from 'axe-core';
|
||||
import { Rules } from './Rules';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
import HighlightToggle from './HighlightToggle';
|
||||
|
||||
const Item = styled.li({
|
||||
fontWeight: 600,
|
||||
});
|
||||
|
||||
const ItemTitle = styled.span(({ theme }) => ({
|
||||
borderBottom: `1px solid ${theme.appBorderColor}`,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
paddingBottom: '6px',
|
||||
marginBottom: '6px',
|
||||
justifyContent: 'space-between',
|
||||
}));
|
||||
|
||||
const HighlightToggleElement = styled.span({
|
||||
fontWeight: 'normal',
|
||||
alignSelf: 'center',
|
||||
paddingRight: '15px',
|
||||
input: { margin: 0 },
|
||||
});
|
||||
|
||||
interface ElementProps {
|
||||
element: NodeResult;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
const Element: FunctionComponent<ElementProps> = ({ element, type }) => {
|
||||
const { any, all, none } = element;
|
||||
const rules = [...any, ...all, ...none];
|
||||
const highlightToggleId = `${type}-${element.target[0]}`;
|
||||
const highlightLabel = `Highlight`;
|
||||
|
||||
return (
|
||||
<Item>
|
||||
<ItemTitle>
|
||||
{element.target[0]}
|
||||
<HighlightToggleElement>
|
||||
<HighlightToggle
|
||||
toggleId={highlightToggleId}
|
||||
type={type}
|
||||
elementsToHighlight={[element]}
|
||||
label={highlightLabel}
|
||||
/>
|
||||
</HighlightToggleElement>
|
||||
</ItemTitle>
|
||||
<Rules rules={rules} />
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
interface ElementsProps {
|
||||
elements: NodeResult[];
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
export const Elements: FunctionComponent<ElementsProps> = ({ elements, type }) => (
|
||||
<ol>
|
||||
{elements.map((element, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Element element={element} key={index} type={type} />
|
||||
))}
|
||||
</ol>
|
||||
);
|
40
addons/a11y/src/components/Report/HighlightToggle.test.js
Normal file
40
addons/a11y/src/components/Report/HighlightToggle.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider, themes, convert } from '@storybook/theming';
|
||||
import HighlightToggle from './HighlightToggle';
|
||||
import store from '../../redux-config';
|
||||
|
||||
function ThemedHighlightToggle(props) {
|
||||
return (
|
||||
<ThemeProvider theme={convert(themes.normal)}>
|
||||
<HighlightToggle {...props} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('HighlightToggle component', () => {
|
||||
test('should render', () => {
|
||||
// given
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ThemedHighlightToggle />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
// then
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
// given
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ThemedHighlightToggle />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
// then
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
191
addons/a11y/src/components/Report/HighlightToggle.tsx
Normal file
191
addons/a11y/src/components/Report/HighlightToggle.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { styled, themes, convert } from '@storybook/theming';
|
||||
import memoize from 'memoizerific';
|
||||
|
||||
import { NodeResult } from 'axe-core';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
import { addElement } from '../../redux-config';
|
||||
import { IFRAME } from '../../constants';
|
||||
|
||||
export class HighlightedElementData {
|
||||
originalOutline: string;
|
||||
|
||||
isHighlighted: boolean;
|
||||
}
|
||||
|
||||
interface ToggleProps {
|
||||
elementsToHighlight: NodeResult[];
|
||||
type: RuleType;
|
||||
addElement?: (data: any) => void;
|
||||
highlightedElementsMap?: Map<HTMLElement, HighlightedElementData>;
|
||||
isToggledOn?: boolean;
|
||||
toggleId?: string;
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
|
||||
enum CheckBoxStates {
|
||||
CHECKED,
|
||||
UNCHECKED,
|
||||
INDETERMINATE,
|
||||
}
|
||||
|
||||
const Checkbox = styled.input(({ disabled }) => ({
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
}));
|
||||
|
||||
const colorsByType = [
|
||||
convert(themes.normal).color.negative, // VIOLATION,
|
||||
convert(themes.normal).color.positive, // PASS,
|
||||
convert(themes.normal).color.warning, // INCOMPLETION,
|
||||
];
|
||||
|
||||
const getIframe = memoize(1)(() => document.getElementsByTagName(IFRAME)[0]);
|
||||
|
||||
function getElementBySelectorPath(elementPath: string): HTMLElement {
|
||||
const iframe = getIframe();
|
||||
if (iframe && iframe.contentDocument && elementPath) {
|
||||
return iframe.contentDocument.querySelector(elementPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setElementOutlineStyle(targetElement: HTMLElement, outlineStyle: string): void {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
targetElement.style.outline = outlineStyle;
|
||||
}
|
||||
|
||||
function areAllRequiredElementsHighlighted(
|
||||
elementsToHighlight: NodeResult[],
|
||||
highlightedElementsMap: Map<HTMLElement, HighlightedElementData>
|
||||
): CheckBoxStates {
|
||||
const highlightedCount = elementsToHighlight.filter(item => {
|
||||
const targetElement = getElementBySelectorPath(item.target[0]);
|
||||
return (
|
||||
highlightedElementsMap.has(targetElement) &&
|
||||
highlightedElementsMap.get(targetElement).isHighlighted
|
||||
);
|
||||
}).length;
|
||||
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return highlightedCount === 0
|
||||
? CheckBoxStates.UNCHECKED
|
||||
: highlightedCount === elementsToHighlight.length
|
||||
? CheckBoxStates.CHECKED
|
||||
: CheckBoxStates.INDETERMINATE;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any) {
|
||||
return {
|
||||
addElement: (data: { element: HTMLElement; data: HighlightedElementData }) =>
|
||||
dispatch(addElement(data)),
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any, ownProps: any) => {
|
||||
const checkBoxState = areAllRequiredElementsHighlighted(
|
||||
ownProps.elementsToHighlight || [],
|
||||
state.highlightedElementsMap
|
||||
);
|
||||
return {
|
||||
highlightedElementsMap: state.highlightedElementsMap,
|
||||
isToggledOn: checkBoxState === CheckBoxStates.CHECKED,
|
||||
indeterminate: checkBoxState === CheckBoxStates.INDETERMINATE,
|
||||
};
|
||||
};
|
||||
|
||||
class HighlightToggle extends Component<ToggleProps> {
|
||||
static defaultProps: Partial<ToggleProps> = {
|
||||
elementsToHighlight: [],
|
||||
};
|
||||
|
||||
private checkBoxRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
componentDidMount() {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (targetElement && !highlightedElementsMap.has(targetElement)) {
|
||||
this.saveElementDataToMap(targetElement, false, targetElement.style.outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<ToggleProps>): void {
|
||||
const { indeterminate } = this.props;
|
||||
if (this.checkBoxRef.current) {
|
||||
this.checkBoxRef.current.indeterminate = indeterminate;
|
||||
}
|
||||
}
|
||||
|
||||
onToggle = (): void => {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (!highlightedElementsMap.has(targetElement)) {
|
||||
return;
|
||||
}
|
||||
const { originalOutline } = highlightedElementsMap.get(targetElement);
|
||||
const { isHighlighted } = highlightedElementsMap.get(targetElement);
|
||||
const { isToggledOn } = this.props;
|
||||
if ((isToggledOn && isHighlighted) || (!isToggledOn && !isHighlighted)) {
|
||||
const addHighlight = !isToggledOn && !isHighlighted;
|
||||
this.highlightRuleLocation(targetElement, addHighlight);
|
||||
this.saveElementDataToMap(targetElement, addHighlight, originalOutline);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
highlightRuleLocation(targetElement: HTMLElement, addHighlight: boolean): void {
|
||||
const { highlightedElementsMap, type } = this.props;
|
||||
if (!targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (addHighlight) {
|
||||
setElementOutlineStyle(targetElement, `${colorsByType[type]} dotted 1px`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (highlightedElementsMap.has(targetElement)) {
|
||||
setElementOutlineStyle(
|
||||
targetElement,
|
||||
highlightedElementsMap.get(targetElement).originalOutline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
saveElementDataToMap(
|
||||
targetElement: HTMLElement,
|
||||
isHighlighted: boolean,
|
||||
originalOutline: string
|
||||
): void {
|
||||
const { addElement: localAddElement } = this.props;
|
||||
const data: HighlightedElementData = new HighlightedElementData();
|
||||
data.isHighlighted = isHighlighted;
|
||||
data.originalOutline = originalOutline;
|
||||
const payload = { element: targetElement, highlightedElementData: data };
|
||||
localAddElement(payload);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { toggleId, elementsToHighlight, isToggledOn } = this.props;
|
||||
return (
|
||||
<Checkbox
|
||||
ref={this.checkBoxRef}
|
||||
id={toggleId}
|
||||
type="checkbox"
|
||||
aria-label="Highlight result"
|
||||
disabled={!elementsToHighlight.length}
|
||||
onChange={this.onToggle}
|
||||
checked={isToggledOn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(HighlightToggle);
|
@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Result } from 'axe-core';
|
||||
|
||||
const Wrapper = styled.div(({ theme }) => ({
|
||||
backgroundColor: theme.barFill,
|
||||
const Wrapper = styled.div({
|
||||
padding: '12px',
|
||||
marginBottom: '10px',
|
||||
}));
|
||||
});
|
||||
const Help = styled.p({
|
||||
margin: '0 0 12px',
|
||||
});
|
||||
@ -18,7 +17,11 @@ const Link = styled.a({
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
function Info({ item }) {
|
||||
interface InfoProps {
|
||||
item: Result;
|
||||
}
|
||||
|
||||
export const Info: FunctionComponent<InfoProps> = ({ item }) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Help>{item.help}</Help>
|
||||
@ -27,13 +30,4 @@ function Info({ item }) {
|
||||
</Link>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
Info.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
help: PropTypes.node,
|
||||
helpUrl: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default Info;
|
@ -1,80 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Icons } from '@storybook/components';
|
||||
|
||||
import Info from './Info';
|
||||
import Tags from './Tags';
|
||||
import Elements from './Elements';
|
||||
|
||||
const Wrapper = styled.div();
|
||||
|
||||
const HeaderBar = styled.button(({ theme }) => ({
|
||||
padding: theme.layoutMargin,
|
||||
paddingLeft: theme.layoutMargin - 3,
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
border: 0,
|
||||
background: 'none',
|
||||
color: 'inherit',
|
||||
|
||||
borderLeft: '3px solid transparent',
|
||||
|
||||
'&:focus': {
|
||||
outline: '0 none',
|
||||
borderLeft: `3px solid ${theme.highlightColor}`,
|
||||
},
|
||||
}));
|
||||
|
||||
class Item extends Component {
|
||||
static propTypes = {
|
||||
item: PropTypes.shape({
|
||||
description: PropTypes.string,
|
||||
nodes: PropTypes.array,
|
||||
tags: PropTypes.array,
|
||||
}).isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
onToggle = () =>
|
||||
this.setState(prevState => ({
|
||||
open: !prevState.open,
|
||||
}));
|
||||
|
||||
render() {
|
||||
const { item, passes } = this.props;
|
||||
const { open } = this.state;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<HeaderBar onClick={this.onToggle}>
|
||||
<Icons
|
||||
icon="chevrondown"
|
||||
size={10}
|
||||
color="#9DA5AB"
|
||||
style={{
|
||||
marginRight: '10px',
|
||||
transform: `rotate(${open ? 0 : -90}deg)`,
|
||||
transition: 'transform 0.1s ease-in-out',
|
||||
}}
|
||||
/>
|
||||
{item.description}
|
||||
</HeaderBar>
|
||||
{open ? (
|
||||
<Fragment>
|
||||
<Info item={item} key="info" />
|
||||
<Elements elements={item.nodes} passes={passes} key="elements" />
|
||||
<Tags tags={item.tags} key="tags" />
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Item;
|
113
addons/a11y/src/components/Report/Item.tsx
Normal file
113
addons/a11y/src/components/Report/Item.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Icons } from '@storybook/components';
|
||||
|
||||
import { Result } from 'axe-core';
|
||||
import { Info } from './Info';
|
||||
import { Elements } from './Elements';
|
||||
import { Tags } from './Tags';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
import HighlightToggle from './HighlightToggle';
|
||||
|
||||
const Wrapper = styled.div(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
borderBottom: `1px solid ${theme.appBorderColor}`,
|
||||
'&:hover': {
|
||||
background: theme.background.hoverable,
|
||||
},
|
||||
}));
|
||||
|
||||
const Icon = styled<any, any>(Icons)(({ theme }) => ({
|
||||
height: 10,
|
||||
width: 10,
|
||||
minWidth: 10,
|
||||
color: theme.color.mediumdark,
|
||||
marginRight: '10px',
|
||||
transition: 'transform 0.1s ease-in-out',
|
||||
alignSelf: 'center',
|
||||
display: 'inline-flex',
|
||||
}));
|
||||
|
||||
const HeaderBar = styled.div(({ theme }) => ({
|
||||
padding: theme.layoutMargin,
|
||||
paddingLeft: theme.layoutMargin - 3,
|
||||
background: 'none',
|
||||
color: 'inherit',
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
borderLeft: '3px solid transparent',
|
||||
width: '100%',
|
||||
|
||||
'&:focus': {
|
||||
outline: '0 none',
|
||||
borderLeft: `3px solid ${theme.color.secondary}`,
|
||||
},
|
||||
}));
|
||||
|
||||
const HighlightToggleElement = styled.span({
|
||||
fontWeight: 'normal',
|
||||
float: 'right',
|
||||
marginRight: '15px',
|
||||
alignSelf: 'center',
|
||||
input: { margin: 0 },
|
||||
});
|
||||
|
||||
interface ItemProps {
|
||||
item: Result;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
interface ItemState {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export class Item extends Component<ItemProps, ItemState> {
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
onToggle = () =>
|
||||
this.setState(prevState => ({
|
||||
open: !prevState.open,
|
||||
}));
|
||||
|
||||
render() {
|
||||
const { item, type } = this.props;
|
||||
const { open } = this.state;
|
||||
const highlightToggleId = `${type}-${item.id}`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Wrapper>
|
||||
<HeaderBar onClick={this.onToggle} role="button">
|
||||
<Icon
|
||||
icon="chevrondown"
|
||||
size={10}
|
||||
color="#9DA5AB"
|
||||
style={{
|
||||
transform: `rotate(${open ? 0 : -90}deg)`,
|
||||
}}
|
||||
/>
|
||||
{item.description}
|
||||
</HeaderBar>
|
||||
<HighlightToggleElement>
|
||||
<HighlightToggle
|
||||
toggleId={highlightToggleId}
|
||||
type={type}
|
||||
elementsToHighlight={item ? item.nodes : null}
|
||||
/>
|
||||
</HighlightToggleElement>
|
||||
</Wrapper>
|
||||
{open ? (
|
||||
<Fragment>
|
||||
<Info item={item} key="info" />
|
||||
<Elements elements={item.nodes} type={type} key="elements" />
|
||||
<Tags tags={item.tags} key="tags" />
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { Icons } from '@storybook/components';
|
||||
|
||||
const impactColors = {
|
||||
minor: '#f1c40f',
|
||||
moderate: '#e67e22',
|
||||
serious: '#e74c3c',
|
||||
critical: '#c0392b',
|
||||
success: '#2ecc71',
|
||||
};
|
||||
|
||||
const List = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '4px',
|
||||
fontWeight: '400',
|
||||
});
|
||||
|
||||
const Item = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: '6px',
|
||||
});
|
||||
|
||||
const Message = styled.div({
|
||||
paddingLeft: '6px',
|
||||
});
|
||||
|
||||
const Status = styled.div(({ passes, impact }) => ({
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '10px',
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
flex: '0 0 16px',
|
||||
color: passes ? impactColors.success : impactColors[impact],
|
||||
}));
|
||||
|
||||
const Rule = ({ rule, passes }) => (
|
||||
<Item>
|
||||
<Status passes={passes || undefined} impact={rule.impact}>
|
||||
{passes ? <Icons icon="check" /> : <Icons icon="cross" />}
|
||||
</Status>
|
||||
<Message>{rule.message}</Message>
|
||||
</Item>
|
||||
);
|
||||
|
||||
Rule.propTypes = {
|
||||
rule: PropTypes.shape({
|
||||
message: PropTypes.node,
|
||||
}).isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
function Rules({ rules, passes }) {
|
||||
return (
|
||||
<List>
|
||||
{rules.map((rule, index) => (
|
||||
<Rule passes={passes} rule={rule} key={index} />
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
Rules.propTypes = {
|
||||
rules: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
message: PropTypes.node,
|
||||
})
|
||||
).isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Rules;
|
120
addons/a11y/src/components/Report/Rules.tsx
Normal file
120
addons/a11y/src/components/Report/Rules.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Badge, Icons } from '@storybook/components';
|
||||
import { CheckResult } from 'axe-core';
|
||||
import { SizeMe } from 'react-sizeme';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
|
||||
const impactColors = {
|
||||
minor: '#f1c40f',
|
||||
moderate: '#e67e22',
|
||||
serious: '#e74c3c',
|
||||
critical: '#c0392b',
|
||||
success: '#2ecc71',
|
||||
};
|
||||
|
||||
const List = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingBottom: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '4px',
|
||||
fontWeight: '400',
|
||||
} as any);
|
||||
|
||||
const Item = styled.div(({ elementWidth }: { elementWidth: number }) => {
|
||||
const maxWidthBeforeBreak = 407;
|
||||
return {
|
||||
flexDirection: elementWidth > maxWidthBeforeBreak ? 'row' : 'inherit',
|
||||
marginBottom: elementWidth > maxWidthBeforeBreak ? '6px' : '12px',
|
||||
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
|
||||
};
|
||||
});
|
||||
|
||||
const StyledBadge = styled(Badge)(({ status }: { status: string }) => ({
|
||||
padding: '2px 8px',
|
||||
marginBottom: '3px',
|
||||
minWidth: '65px',
|
||||
maxWidth: 'fit-content',
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const Message = styled.div({
|
||||
paddingLeft: '6px',
|
||||
paddingRight: '23px',
|
||||
});
|
||||
|
||||
const Status = styled.div(({ passes, impact }: { passes: boolean; impact: string }) => ({
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: passes ? impactColors.success : (impactColors as any)[impact],
|
||||
'& > svg': {
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
},
|
||||
}));
|
||||
|
||||
export enum ImpactValue {
|
||||
MINOR = 'minor',
|
||||
MODERATE = 'moderate',
|
||||
SERIOUS = 'serious',
|
||||
CRITICAL = 'critical',
|
||||
}
|
||||
|
||||
interface RuleProps {
|
||||
rule: CheckResult;
|
||||
}
|
||||
|
||||
const formatSeverityText = (severity: string) => {
|
||||
return severity
|
||||
.charAt(0)
|
||||
.toUpperCase()
|
||||
.concat(severity.slice(1));
|
||||
};
|
||||
|
||||
const Rule: FunctionComponent<RuleProps> = ({ rule }) => {
|
||||
let badgeType: any = null;
|
||||
switch (rule.impact) {
|
||||
case ImpactValue.CRITICAL:
|
||||
badgeType = 'critical';
|
||||
break;
|
||||
case ImpactValue.SERIOUS:
|
||||
badgeType = 'negative';
|
||||
break;
|
||||
case ImpactValue.MODERATE:
|
||||
badgeType = 'warning';
|
||||
break;
|
||||
case ImpactValue.MINOR:
|
||||
badgeType = 'neutral';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<SizeMe refreshMode="debounce">
|
||||
{({ size }: { size: any }) => (
|
||||
<Item elementWidth={size.width}>
|
||||
<StyledBadge status={badgeType}>{formatSeverityText(rule.impact)}</StyledBadge>
|
||||
<Message>{rule.message}</Message>
|
||||
</Item>
|
||||
)}
|
||||
</SizeMe>
|
||||
);
|
||||
};
|
||||
|
||||
interface RulesProps {
|
||||
rules: CheckResult[];
|
||||
}
|
||||
|
||||
export const Rules: FunctionComponent<RulesProps> = ({ rules }) => {
|
||||
return (
|
||||
<List>
|
||||
{rules.map((rule, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Rule rule={rule} key={index} />
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { TagValue } from 'axe-core';
|
||||
|
||||
const Wrapper = styled.div({
|
||||
display: 'flex',
|
||||
@ -12,11 +12,15 @@ const Wrapper = styled.div({
|
||||
const Item = styled.div(({ theme }) => ({
|
||||
margin: '0 6px',
|
||||
padding: '5px',
|
||||
border: theme.mainBorder,
|
||||
borderRadius: theme.mainBorderRadius,
|
||||
border: `1px solid ${theme.appBorderColor}`,
|
||||
borderRadius: theme.appBorderRadius,
|
||||
}));
|
||||
|
||||
function Tags({ tags }) {
|
||||
interface TagsProps {
|
||||
tags: TagValue[];
|
||||
}
|
||||
|
||||
export const Tags: FunctionComponent<TagsProps> = ({ tags }) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
{tags.map(tag => (
|
||||
@ -24,9 +28,4 @@ function Tags({ tags }) {
|
||||
))}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
Tags.propTypes = {
|
||||
tags: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||
};
|
||||
|
||||
export default Tags;
|
@ -0,0 +1,353 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HighlightToggle component should match snapshot 1`] = `
|
||||
.emotion-0 {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<ThemedHighlightToggle>
|
||||
<ThemeProvider
|
||||
theme={
|
||||
Object {
|
||||
"addonActionsTheme": Object {
|
||||
"ARROW_ANIMATION_DURATION": "0",
|
||||
"ARROW_COLOR": "rgba(0,0,0,0.3)",
|
||||
"ARROW_FONT_SIZE": 8,
|
||||
"ARROW_MARGIN_RIGHT": 4,
|
||||
"BASE_BACKGROUND_COLOR": "transparent",
|
||||
"BASE_COLOR": "#333333",
|
||||
"BASE_FONT_FAMILY": "\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace",
|
||||
"BASE_FONT_SIZE": 13,
|
||||
"BASE_LINE_HEIGHT": "18px",
|
||||
"HTML_ATTRIBUTE_NAME_COLOR": "rgb(153, 69, 0)",
|
||||
"HTML_ATTRIBUTE_VALUE_COLOR": "rgb(26, 26, 166)",
|
||||
"HTML_COMMENT_COLOR": "rgb(35, 110, 37)",
|
||||
"HTML_DOCTYPE_COLOR": "rgb(192, 192, 192)",
|
||||
"HTML_TAGNAME_COLOR": "rgb(136, 18, 128)",
|
||||
"HTML_TAGNAME_TEXT_TRANSFORM": "lowercase",
|
||||
"HTML_TAG_COLOR": "rgb(168, 148, 166)",
|
||||
"OBJECT_NAME_COLOR": "rgb(136, 19, 145)",
|
||||
"OBJECT_PREVIEW_ARRAY_MAX_PROPERTIES": 10,
|
||||
"OBJECT_PREVIEW_OBJECT_MAX_PROPERTIES": 5,
|
||||
"OBJECT_VALUE_BOOLEAN_COLOR": "rgb(28, 0, 207)",
|
||||
"OBJECT_VALUE_FUNCTION_PREFIX_COLOR": "rgb(13, 34, 170)",
|
||||
"OBJECT_VALUE_NULL_COLOR": "rgb(128, 128, 128)",
|
||||
"OBJECT_VALUE_NUMBER_COLOR": "rgb(28, 0, 207)",
|
||||
"OBJECT_VALUE_REGEXP_COLOR": "rgb(196, 26, 22)",
|
||||
"OBJECT_VALUE_STRING_COLOR": "rgb(196, 26, 22)",
|
||||
"OBJECT_VALUE_SYMBOL_COLOR": "rgb(196, 26, 22)",
|
||||
"OBJECT_VALUE_UNDEFINED_COLOR": "rgb(128, 128, 128)",
|
||||
"TABLE_BORDER_COLOR": "#aaa",
|
||||
"TABLE_DATA_BACKGROUND_IMAGE": "linear-gradient(to bottom, white, white 50%, rgb(234, 243, 255) 50%, rgb(234, 243, 255))",
|
||||
"TABLE_DATA_BACKGROUND_SIZE": "128px 32px",
|
||||
"TABLE_SORT_ICON_COLOR": "#6e6e6e",
|
||||
"TABLE_TH_BACKGROUND_COLOR": "#eee",
|
||||
"TABLE_TH_HOVER_COLOR": "hsla(0, 0%, 90%, 1)",
|
||||
"TREENODE_FONT_FAMILY": "\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace",
|
||||
"TREENODE_FONT_SIZE": 13,
|
||||
"TREENODE_LINE_HEIGHT": "18px",
|
||||
"TREENODE_PADDING_LEFT": 12,
|
||||
},
|
||||
"animation": Object {
|
||||
"float": Object {
|
||||
"anim": 1,
|
||||
"name": "animation-6tolu8",
|
||||
"styles": "@keyframes animation-6tolu8{
|
||||
0% { transform: translateY(1px); }
|
||||
25% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-3px); }
|
||||
100% { transform: translateY(1px); }
|
||||
}",
|
||||
"toString": [Function],
|
||||
},
|
||||
"glow": Object {
|
||||
"anim": 1,
|
||||
"name": "animation-r0iffl",
|
||||
"styles": "@keyframes animation-r0iffl{
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: .4; }
|
||||
}",
|
||||
"toString": [Function],
|
||||
},
|
||||
"hoverable": Object {
|
||||
"map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBd0NxQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */",
|
||||
"name": "1023qba-hoverable",
|
||||
"styles": "transition:all 150ms ease-out;transform:translate3d(0,0,0);&:hover{transform:translate3d(0,-2px,0);}&:active{transform:translate3d(0,0,0);}label:hoverable;",
|
||||
},
|
||||
"inlineGlow": Object {
|
||||
"map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUNzQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */",
|
||||
"name": "1euta6d-inlineGlow",
|
||||
"next": Object {
|
||||
"name": "animation-r0iffl",
|
||||
"next": undefined,
|
||||
"styles": "@keyframes animation-r0iffl{
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: .4; }
|
||||
}",
|
||||
},
|
||||
"styles": "animation:animation-r0iffl 1.5s ease-in-out infinite;color:transparent;cursor:progress;label:inlineGlow;",
|
||||
},
|
||||
"jiggle": Object {
|
||||
"anim": 1,
|
||||
"name": "animation-ynpq7w",
|
||||
"styles": "@keyframes animation-ynpq7w{
|
||||
0%, 100% { transform:translate3d(0,0,0); }
|
||||
12.5%, 62.5% { transform:translate3d(-4px,0,0); }
|
||||
37.5%, 87.5% { transform: translate3d(4px,0,0); }
|
||||
}",
|
||||
"toString": [Function],
|
||||
},
|
||||
"rotate360": Object {
|
||||
"anim": 1,
|
||||
"name": "animation-u07e3c",
|
||||
"styles": "@keyframes animation-u07e3c{
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}",
|
||||
"toString": [Function],
|
||||
},
|
||||
},
|
||||
"appBorderColor": "rgba(0,0,0,.1)",
|
||||
"appBorderRadius": 4,
|
||||
"background": Object {
|
||||
"app": "#F6F9FC",
|
||||
"bar": "#FFFFFF",
|
||||
"content": "#FFFFFF",
|
||||
"critical": "#FF4400",
|
||||
"gridCellSize": 10,
|
||||
"hoverable": "rgba(0,0,0,.05)",
|
||||
"negative": "#FEDED2",
|
||||
"positive": "#E1FFD4",
|
||||
"warning": "#FFF5CF",
|
||||
},
|
||||
"barBg": "#FFFFFF",
|
||||
"barSelectedColor": "#1EA7FD",
|
||||
"barTextColor": "#999999",
|
||||
"base": "light",
|
||||
"brand": Object {
|
||||
"image": undefined,
|
||||
"title": undefined,
|
||||
"url": undefined,
|
||||
},
|
||||
"code": Object {
|
||||
"language-json .token.boolean": Object {
|
||||
"color": "#0000ff",
|
||||
},
|
||||
"language-json .token.number": Object {
|
||||
"color": "#0000ff",
|
||||
},
|
||||
"language-json .token.property": Object {
|
||||
"color": "#2B91AF",
|
||||
},
|
||||
"namespace": Object {
|
||||
"opacity": 0.7,
|
||||
},
|
||||
"token": Object {
|
||||
"&.atrule": Object {
|
||||
"color": "#0000ff",
|
||||
},
|
||||
"&.attr-name": Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
"&.attr-value": Object {
|
||||
"color": "#0000ff",
|
||||
},
|
||||
"&.bold": Object {
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
"&.boolean": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.cdata": Object {
|
||||
"color": "#008000",
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"&.class-name": Object {
|
||||
"color": "#2B91AF",
|
||||
},
|
||||
"&.comment": Object {
|
||||
"color": "#008000",
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"&.constant": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.deleted": Object {
|
||||
"color": "#9a050f",
|
||||
},
|
||||
"&.directive.tag .tag": Object {
|
||||
"background": "#ffff00",
|
||||
"color": "#393A34",
|
||||
},
|
||||
"&.doctype": Object {
|
||||
"color": "#008000",
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"&.entity": Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
"&.function": Object {
|
||||
"color": "#393A34",
|
||||
},
|
||||
"&.important": Object {
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
"&.inserted": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.italic": Object {
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"&.keyword": Object {
|
||||
"color": "#0000ff",
|
||||
},
|
||||
"&.number": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.operator": Object {
|
||||
"color": "#393A34",
|
||||
},
|
||||
"&.prolog": Object {
|
||||
"color": "#008000",
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"&.property": Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
"&.punctuation": Object {
|
||||
"color": "#393A34",
|
||||
},
|
||||
"&.regex": Object {
|
||||
"color": "#ff0000",
|
||||
},
|
||||
"&.selector": Object {
|
||||
"color": "#800000",
|
||||
},
|
||||
"&.string": Object {
|
||||
"color": "#A31515",
|
||||
},
|
||||
"&.symbol": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.tag": Object {
|
||||
"color": "#800000",
|
||||
},
|
||||
"&.url": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"&.variable": Object {
|
||||
"color": "#36acaa",
|
||||
},
|
||||
"WebkitFontSmoothing": "antialiased",
|
||||
"fontFamily": "\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace",
|
||||
},
|
||||
},
|
||||
"color": Object {
|
||||
"ancillary": "#22a699",
|
||||
"border": "rgba(0,0,0,.1)",
|
||||
"critical": "#FFFFFF",
|
||||
"dark": "#666666",
|
||||
"darker": "#444444",
|
||||
"darkest": "#333333",
|
||||
"defaultText": "#333333",
|
||||
"gold": "#FFAE00",
|
||||
"green": "#66BF3C",
|
||||
"inverseText": "#FFFFFF",
|
||||
"light": "#F3F3F3",
|
||||
"lighter": "#F8F8F8",
|
||||
"lightest": "#FFFFFF",
|
||||
"medium": "#DDDDDD",
|
||||
"mediumdark": "#999999",
|
||||
"mediumlight": "#EEEEEE",
|
||||
"negative": "#FF4400",
|
||||
"orange": "#FC521F",
|
||||
"positive": "#66BF3C",
|
||||
"primary": "#FF4785",
|
||||
"purple": "#6F2CAC",
|
||||
"seafoam": "#37D5D3",
|
||||
"secondary": "#1EA7FD",
|
||||
"tertiary": "#FAFBFC",
|
||||
"ultraviolet": "#2A0481",
|
||||
"warning": "#E69D00",
|
||||
},
|
||||
"easing": Object {
|
||||
"rubber": "cubic-bezier(0.175, 0.885, 0.335, 1.05)",
|
||||
},
|
||||
"input": Object {
|
||||
"background": "#FFFFFF",
|
||||
"border": "rgba(0,0,0,.1)",
|
||||
"borderRadius": 4,
|
||||
"color": "#333333",
|
||||
},
|
||||
"layoutMargin": 10,
|
||||
"typography": Object {
|
||||
"fonts": Object {
|
||||
"base": "\\"Nunito Sans\\", -apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Helvetica Neue\\", Helvetica, Arial, sans-serif",
|
||||
"mono": "\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace",
|
||||
},
|
||||
"size": Object {
|
||||
"code": 90,
|
||||
"l1": 32,
|
||||
"l2": 40,
|
||||
"l3": 48,
|
||||
"m1": 20,
|
||||
"m2": 24,
|
||||
"m3": 28,
|
||||
"s1": 12,
|
||||
"s2": 14,
|
||||
"s3": 16,
|
||||
},
|
||||
"weight": Object {
|
||||
"black": 900,
|
||||
"bold": 700,
|
||||
"regular": 400,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<ConnectFunction>
|
||||
<HighlightToggle
|
||||
addElement={[Function]}
|
||||
elementsToHighlight={Array []}
|
||||
highlightedElementsMap={Map {}}
|
||||
indeterminate={false}
|
||||
isToggledOn={false}
|
||||
>
|
||||
<Styled(input)
|
||||
aria-label="Highlight result"
|
||||
checked={false}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
>
|
||||
<input
|
||||
aria-label="Highlight result"
|
||||
checked={false}
|
||||
className="emotion-0"
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
</Styled(input)>
|
||||
</HighlightToggle>
|
||||
</ConnectFunction>
|
||||
</ThemeProvider>
|
||||
</ThemedHighlightToggle>
|
||||
</Provider>
|
||||
`;
|
@ -1,29 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Placeholder } from '@storybook/components';
|
||||
|
||||
import Item from './Item';
|
||||
|
||||
const Report = ({ items, empty, passes }) => (
|
||||
<Fragment>
|
||||
{items.length ? (
|
||||
items.map(item => <Item passes={passes} item={item} key={item.id} />)
|
||||
) : (
|
||||
<Placeholder key="placeholder">{empty}</Placeholder>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
Report.propTypes = {
|
||||
items: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
description: PropTypes.string,
|
||||
nodes: PropTypes.array,
|
||||
tags: PropTypes.array,
|
||||
})
|
||||
).isRequired,
|
||||
empty: PropTypes.string.isRequired,
|
||||
passes: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Report;
|
21
addons/a11y/src/components/Report/index.tsx
Normal file
21
addons/a11y/src/components/Report/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { Fragment, FunctionComponent } from 'react';
|
||||
import { Placeholder } from '@storybook/components';
|
||||
import { Result } from 'axe-core';
|
||||
import { Item } from './Item';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
|
||||
export interface ReportProps {
|
||||
items: Result[];
|
||||
empty: string;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
export const Report: FunctionComponent<ReportProps> = ({ items, empty, type }) => (
|
||||
<Fragment>
|
||||
{items && items.length ? (
|
||||
items.map(item => <Item item={item} key={`${type}:${item.id}`} type={type} />)
|
||||
) : (
|
||||
<Placeholder key="placeholder">{empty}</Placeholder>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
@ -1,91 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
const Container = styled.div({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
minHeight: '100%',
|
||||
});
|
||||
|
||||
const List = styled.div(({ theme }) => ({
|
||||
borderBottom: theme.mainBorder,
|
||||
flexWrap: 'wrap',
|
||||
display: 'flex',
|
||||
}));
|
||||
|
||||
const Item = styled.button(
|
||||
({ active }) =>
|
||||
active
|
||||
? {
|
||||
opacity: 1,
|
||||
fontWeight: 600,
|
||||
}
|
||||
: {},
|
||||
({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
textTransform: 'uppercase',
|
||||
padding: '10px 15px',
|
||||
letterSpacing: '1px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
opacity: 0.7,
|
||||
border: 'none',
|
||||
borderTop: '3px solid transparent',
|
||||
borderBottom: '3px solid transparent',
|
||||
background: 'none',
|
||||
flex: 1,
|
||||
|
||||
'&:focus': {
|
||||
outline: '0 none',
|
||||
borderBottom: `3px solid ${theme.highlightColor}`,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
class Tabs extends Component {
|
||||
state = {
|
||||
active: 0,
|
||||
};
|
||||
|
||||
onToggle = index => {
|
||||
this.setState({
|
||||
active: index,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabs } = this.props;
|
||||
const { active } = this.state;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<List>
|
||||
{tabs.map((tab, index) => (
|
||||
<Item
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
active={active === index ? true : undefined}
|
||||
onClick={() => this.onToggle(index)}
|
||||
>
|
||||
{tab.label}
|
||||
</Item>
|
||||
))}
|
||||
</List>
|
||||
{tabs[active].panel}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tabs.propTypes = {
|
||||
tabs: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
label: PropTypes.node,
|
||||
panel: PropTypes.node,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export default Tabs;
|
161
addons/a11y/src/components/Tabs.tsx
Normal file
161
addons/a11y/src/components/Tabs.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import React, { Component, SyntheticEvent } from 'react';
|
||||
|
||||
import { styled, themes } from '@storybook/theming';
|
||||
import { NodeResult, Result } from 'axe-core';
|
||||
import { SizeMe } from 'react-sizeme';
|
||||
import store, { clearElements } from '../redux-config';
|
||||
import HighlightToggle from './Report/HighlightToggle';
|
||||
import { RuleType } from './A11YPanel';
|
||||
|
||||
// TODO: reuse the Tabs component from @storybook/theming instead of re-building identical functionality
|
||||
|
||||
const Container = styled.div({
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
minHeight: '100%',
|
||||
});
|
||||
|
||||
const HighlightToggleLabel = styled.label(({ theme }) => ({
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
marginBottom: '3px',
|
||||
marginRight: '3px',
|
||||
color: theme.color.dark,
|
||||
}));
|
||||
|
||||
const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => {
|
||||
const maxWidthBeforeBreak = 450;
|
||||
return {
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
padding: elementWidth > maxWidthBeforeBreak ? '12px 15px 10px 0' : '12px 0px 3px 12px',
|
||||
height: '40px',
|
||||
border: 'none',
|
||||
marginTop: elementWidth > maxWidthBeforeBreak ? '-40px' : '0px',
|
||||
float: elementWidth > maxWidthBeforeBreak ? 'right' : 'left',
|
||||
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
|
||||
alignItems: 'center',
|
||||
width: elementWidth > maxWidthBeforeBreak ? 'auto' : '100%',
|
||||
borderBottom: elementWidth > maxWidthBeforeBreak ? 'none' : '1px solid rgba(0,0,0,.1)',
|
||||
|
||||
input: {
|
||||
marginLeft: '10',
|
||||
marginRight: '0',
|
||||
marginTop: '0',
|
||||
marginBottom: '0',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Item = styled.button(
|
||||
({ theme }) => ({
|
||||
textDecoration: 'none',
|
||||
padding: '10px 15px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: theme.typography.weight.bold,
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
lineHeight: 1,
|
||||
height: 40,
|
||||
border: 'none',
|
||||
borderTop: '3px solid transparent',
|
||||
borderBottom: '3px solid transparent',
|
||||
background: 'transparent',
|
||||
|
||||
'&:focus': {
|
||||
outline: '0 none',
|
||||
borderBottom: `3px solid ${theme.color.secondary}`,
|
||||
},
|
||||
}),
|
||||
({ active, theme }: any) =>
|
||||
active
|
||||
? {
|
||||
opacity: 1,
|
||||
borderBottom: `3px solid ${theme.color.secondary}`,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
const TabsWrapper = styled.div({});
|
||||
|
||||
const List = styled.div(({ theme }) => ({
|
||||
boxShadow: `${theme.appBorderColor} 0 -1px 0 0 inset`,
|
||||
background: 'rgba(0, 0, 0, .05)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
whiteSpace: 'nowrap',
|
||||
}));
|
||||
|
||||
interface TabsProps {
|
||||
tabs: {
|
||||
label: JSX.Element;
|
||||
panel: JSX.Element;
|
||||
items: Result[];
|
||||
type: RuleType;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface TabsState {
|
||||
active: number;
|
||||
}
|
||||
|
||||
function retrieveAllNodesFromResults(items: Result[]): NodeResult[] {
|
||||
return items.reduce((acc, item) => acc.concat(item.nodes), []);
|
||||
}
|
||||
|
||||
export class Tabs extends Component<TabsProps, TabsState> {
|
||||
state: TabsState = {
|
||||
active: 0,
|
||||
};
|
||||
|
||||
onToggle = (event: SyntheticEvent) => {
|
||||
this.setState({
|
||||
active: parseInt(event.currentTarget.getAttribute('data-index'), 10),
|
||||
});
|
||||
// removes all elements from the redux map in store from the previous panel
|
||||
store.dispatch(clearElements());
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabs } = this.props;
|
||||
const { active } = this.state;
|
||||
const highlightToggleId = `${tabs[active].type}-global-checkbox`;
|
||||
const highlightLabel = `Highlight results`;
|
||||
return (
|
||||
<SizeMe refreshMode="debounce">
|
||||
{({ size }: { size: any }) => (
|
||||
<Container>
|
||||
<List>
|
||||
<TabsWrapper>
|
||||
{tabs.map((tab, index) => (
|
||||
<Item
|
||||
/* eslint-disable-next-line react/no-array-index-key */
|
||||
key={index}
|
||||
data-index={index}
|
||||
active={active === index}
|
||||
onClick={this.onToggle}
|
||||
>
|
||||
{tab.label}
|
||||
</Item>
|
||||
))}
|
||||
</TabsWrapper>
|
||||
</List>
|
||||
{tabs[active].items.length > 0 ? (
|
||||
<GlobalToggle elementWidth={size.width}>
|
||||
<HighlightToggleLabel htmlFor={highlightToggleId}>
|
||||
{highlightLabel}
|
||||
</HighlightToggleLabel>
|
||||
<HighlightToggle
|
||||
toggleId={highlightToggleId}
|
||||
type={tabs[active].type}
|
||||
elementsToHighlight={retrieveAllNodesFromResults(tabs[active].items)}
|
||||
label={highlightLabel}
|
||||
/>
|
||||
</GlobalToggle>
|
||||
) : null}
|
||||
{tabs[active].panel}
|
||||
</Container>
|
||||
)}
|
||||
</SizeMe>
|
||||
);
|
||||
}
|
||||
}
|
1109
addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap
Normal file
1109
addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,10 @@
|
||||
export const ADDON_ID = 'storybook/a11y';
|
||||
export const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
export const PARAM_KEY = `a11y`;
|
||||
|
||||
export const IFRAME = 'iframe';
|
||||
export const ADD_ELEMENT = 'ADD_ELEMENT';
|
||||
export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS';
|
||||
const RESULT = `${ADDON_ID}/result`;
|
||||
const REQUEST = `${ADDON_ID}/request`;
|
||||
|
||||
export default { RESULT, REQUEST };
|
||||
export const EVENTS = { RESULT, REQUEST };
|
@ -1,78 +0,0 @@
|
||||
import { document } from 'global';
|
||||
import axe from 'axe-core';
|
||||
import deprecate from 'util-deprecate';
|
||||
import { stripIndents } from 'common-tags';
|
||||
|
||||
import addons, { makeDecorator } from '@storybook/addons';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import EVENTS, { PARAM_KEY } from './constants';
|
||||
|
||||
const channel = addons.getChannel();
|
||||
let progress = Promise.resolve();
|
||||
let options;
|
||||
|
||||
const getElement = () => {
|
||||
const storyRoot = document.getElementById('story-root');
|
||||
|
||||
if (storyRoot) {
|
||||
return storyRoot.children;
|
||||
}
|
||||
return document.getElementById('root');
|
||||
};
|
||||
|
||||
const report = input => {
|
||||
channel.emit(EVENTS.RESULT, input);
|
||||
};
|
||||
|
||||
const run = o => {
|
||||
progress = progress.then(() => {
|
||||
axe.reset();
|
||||
if (o) {
|
||||
axe.configure(o);
|
||||
}
|
||||
return axe.run(getElement()).then(report);
|
||||
});
|
||||
};
|
||||
|
||||
export const withA11Y = makeDecorator({
|
||||
name: 'withA11Y',
|
||||
parameterName: PARAM_KEY,
|
||||
skipIfNoParametersOrOptions: false,
|
||||
allowDeprecatedUsage: false,
|
||||
|
||||
wrapper: (getStory, context, opt) => {
|
||||
options = opt.parameters || opt.options;
|
||||
|
||||
return getStory(context);
|
||||
},
|
||||
});
|
||||
|
||||
channel.on(STORY_RENDERED, () => run(options));
|
||||
channel.on(EVENTS.REQUEST, () => run(options));
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
||||
|
||||
// TODO: REMOVE at v6.0.0
|
||||
export const checkA11y = deprecate(
|
||||
(...args) => withA11Y(...args),
|
||||
'checkA11y has been replaced with withA11Y'
|
||||
);
|
||||
|
||||
// TODO: REMOVE at v6.0.0
|
||||
export const configureA11y = deprecate(
|
||||
config => {
|
||||
options = config;
|
||||
},
|
||||
stripIndents`
|
||||
configureA11y is deprecated, please configure addon-a11y using the addParameter api:
|
||||
|
||||
addParameters({
|
||||
a11y: {
|
||||
// ... axe options
|
||||
element: '#root', // optional selector which element to inspect
|
||||
},
|
||||
});
|
||||
`
|
||||
);
|
93
addons/a11y/src/index.ts
Normal file
93
addons/a11y/src/index.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
|
||||
import { document } from 'global';
|
||||
import axe, { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core';
|
||||
import deprecate from 'util-deprecate';
|
||||
import { stripIndents } from 'common-tags';
|
||||
|
||||
import addons, { makeDecorator } from '@storybook/addons';
|
||||
import { EVENTS, PARAM_KEY } from './constants';
|
||||
|
||||
let progress = Promise.resolve();
|
||||
interface Setup {
|
||||
element?: ElementContext;
|
||||
config: Spec;
|
||||
options: RunOptions;
|
||||
}
|
||||
let setup: Setup = { element: null, config: {}, options: {} };
|
||||
|
||||
const getElement = () => {
|
||||
const storyRoot = document.getElementById('story-root');
|
||||
|
||||
if (storyRoot) {
|
||||
return storyRoot.children;
|
||||
}
|
||||
return document.getElementById('root');
|
||||
};
|
||||
|
||||
const report = (input: AxeResults) => addons.getChannel().emit(EVENTS.RESULT, input);
|
||||
|
||||
const run = (element: ElementContext, config: Spec, options: RunOptions) => {
|
||||
progress = progress.then(() => {
|
||||
axe.reset();
|
||||
if (config) {
|
||||
axe.configure(config);
|
||||
}
|
||||
return axe
|
||||
.run(
|
||||
element || getElement(),
|
||||
options ||
|
||||
({
|
||||
restoreScroll: true,
|
||||
} as RunOptions) // cast to RunOptions is necessary because axe types are not up to date
|
||||
)
|
||||
.then(report);
|
||||
});
|
||||
};
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
||||
|
||||
export const withA11y = makeDecorator({
|
||||
name: 'withA11Y',
|
||||
parameterName: PARAM_KEY,
|
||||
wrapper: (getStory, context, { parameters }) => {
|
||||
if (parameters) {
|
||||
setup = parameters as Setup;
|
||||
}
|
||||
addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options));
|
||||
|
||||
return getStory(context);
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: REMOVE at v6.0.0
|
||||
export const withA11Y = deprecate(
|
||||
// @ts-ignore
|
||||
(...args: any[]) => withA11y(...args),
|
||||
'withA11Y has been renamed withA11y'
|
||||
);
|
||||
|
||||
// TODO: REMOVE at v6.0.0
|
||||
export const checkA11y = deprecate(
|
||||
// @ts-ignore
|
||||
(...args: any[]) => withA11y(...args),
|
||||
'checkA11y has been renamed withA11y'
|
||||
);
|
||||
|
||||
// TODO: REMOVE at v6.0.0
|
||||
export const configureA11y = deprecate(
|
||||
(config: any) => {
|
||||
setup = config;
|
||||
},
|
||||
stripIndents`
|
||||
configureA11y is deprecated, please configure addon-a11y using the addParameter api:
|
||||
|
||||
addParameters({
|
||||
a11y: {
|
||||
// ... axe options
|
||||
element: '#root', // optional selector which element to inspect
|
||||
},
|
||||
});
|
||||
`
|
||||
);
|
44
addons/a11y/src/redux-config.tsx
Normal file
44
addons/a11y/src/redux-config.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { createStore } from 'redux';
|
||||
import { ADD_ELEMENT, CLEAR_ELEMENTS } from './constants';
|
||||
import { HighlightedElementData } from './components/Report/HighlightToggle';
|
||||
|
||||
// actions
|
||||
|
||||
// add element is passed a HighlightedElementData object as the payload
|
||||
export function addElement(payload: { element: HTMLElement; data: HighlightedElementData }) {
|
||||
return { type: ADD_ELEMENT, payload };
|
||||
}
|
||||
|
||||
// clear elements is a function to remove elements from the map and reset elements to their original state
|
||||
export function clearElements() {
|
||||
return { type: CLEAR_ELEMENTS };
|
||||
}
|
||||
|
||||
// reducers
|
||||
const initialState = {
|
||||
highlightedElementsMap: new Map(),
|
||||
};
|
||||
|
||||
function rootReducer(state = initialState, action: any) {
|
||||
if (action.type === ADD_ELEMENT) {
|
||||
return {
|
||||
...state,
|
||||
highlightedElementsMap: state.highlightedElementsMap.set(
|
||||
action.payload.element,
|
||||
action.payload.highlightedElementData
|
||||
),
|
||||
};
|
||||
}
|
||||
if (action.type === CLEAR_ELEMENTS) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Array.from(state.highlightedElementsMap.keys())) {
|
||||
key.style.outline = state.highlightedElementsMap.get(key).originalOutline;
|
||||
state.highlightedElementsMap.delete(key);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// store
|
||||
const store = createStore(rootReducer);
|
||||
export default store;
|
@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
|
||||
import Panel from './components/Panel';
|
||||
import ColorBlindness from './components/ColorBlindness';
|
||||
|
||||
import { ADDON_ID, PANEL_ID } from './constants';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.TOOL,
|
||||
render: () => <ColorBlindness />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: 'Accessibility',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active, key }) => <Panel key={key} api={api} active={active} />,
|
||||
});
|
||||
});
|
104
addons/a11y/src/register.tsx
Normal file
104
addons/a11y/src/register.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { Fragment, FunctionComponent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { addons, types } from '@storybook/addons';
|
||||
import { ADDON_ID, PANEL_ID } from './constants';
|
||||
import { ColorBlindness } from './components/ColorBlindness';
|
||||
import { A11YPanel } from './components/A11YPanel';
|
||||
|
||||
const Hidden = styled.div(() => ({
|
||||
'&, & svg': {
|
||||
position: 'absolute',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const PreviewWrapper: FunctionComponent<{}> = p => (
|
||||
<Fragment>
|
||||
{p.children}
|
||||
<Hidden>
|
||||
<svg key="svg">
|
||||
<defs>
|
||||
<filter id="protanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.567, 0.433, 0, 0, 0 0.558, 0.442, 0, 0, 0 0, 0.242, 0.758, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="protanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.817, 0.183, 0, 0, 0 0.333, 0.667, 0, 0, 0 0, 0.125, 0.875, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.625, 0.375, 0, 0, 0 0.7, 0.3, 0, 0, 0 0, 0.3, 0.7, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.8, 0.2, 0, 0, 0 0.258, 0.742, 0, 0, 0 0, 0.142, 0.858, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.95, 0.05, 0, 0, 0 0, 0.433, 0.567, 0, 0 0, 0.475, 0.525, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.967, 0.033, 0, 0, 0 0, 0.733, 0.267, 0, 0 0, 0.183, 0.817, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatopsia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.618, 0.320, 0.062, 0, 0 0.163, 0.775, 0.062, 0, 0 0.163, 0.320, 0.516, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</Hidden>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.add(PANEL_ID, {
|
||||
title: '',
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode }) => viewMode === 'story',
|
||||
render: () => <ColorBlindness />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
title: 'Accessibility',
|
||||
type: types.PANEL,
|
||||
render: ({ active, key }) => <A11YPanel key={key} api={api} active={active} />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
title: '',
|
||||
type: types.PREVIEW,
|
||||
render: PreviewWrapper as any,
|
||||
});
|
||||
});
|
2
addons/a11y/src/typings.d.ts
vendored
Normal file
2
addons/a11y/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
declare module 'global';
|
||||
declare module 'react-sizeme';
|
13
addons/a11y/tsconfig.json
Normal file
13
addons/a11y/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"types": ["webpack-env"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/__tests__/**/*"
|
||||
]
|
||||
}
|
@ -4,7 +4,7 @@ Storybook Addon Actions can be used to display data received by event handlers i
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "5.0.0-alpha.7",
|
||||
"version": "5.1.0-rc.0",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -11,29 +11,35 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybooks/storybook.git"
|
||||
"url": "https://github.com/storybooks/storybook.git",
|
||||
"directory": "addons/actions"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.0.0-alpha.7",
|
||||
"@storybook/components": "5.0.0-alpha.7",
|
||||
"@storybook/core-events": "5.0.0-alpha.7",
|
||||
"@storybook/theming": "5.0.0-alpha.7",
|
||||
"core-js": "^2.6.2",
|
||||
"@storybook/addons": "5.1.0-rc.0",
|
||||
"@storybook/api": "5.1.0-rc.0",
|
||||
"@storybook/components": "5.1.0-rc.0",
|
||||
"@storybook/core-events": "5.1.0-rc.0",
|
||||
"@storybook/theming": "5.1.0-rc.0",
|
||||
"core-js": "^3.0.1",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"global": "^4.3.2",
|
||||
"lodash": "^4.17.11",
|
||||
"make-error": "^1.3.5",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-inspector": "^2.3.0",
|
||||
"polished": "^3.3.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.4",
|
||||
"react-inspector": "^3.0.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.129",
|
||||
"@types/uuid": "^3.4.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Inspector from 'react-inspector';
|
||||
|
||||
import { withTheme } from '@storybook/theming';
|
||||
import { ActionBar, ActionButton } from '@storybook/components';
|
||||
|
||||
import { Actions, Action, Wrapper, InspectorContainer, Countwrap, Counter } from './style';
|
||||
|
||||
const ActionLogger = withTheme(({ actions, onClear, theme }) => (
|
||||
<Wrapper>
|
||||
<Actions>
|
||||
{actions.map(action => (
|
||||
<Action key={action.id}>
|
||||
<Countwrap>{action.count > 1 && <Counter>{action.count}</Counter>}</Countwrap>
|
||||
<InspectorContainer>
|
||||
<Inspector
|
||||
theme={theme.addonActionsTheme || 'chromeLight'}
|
||||
sortObjectKeys
|
||||
showNonenumerable={false}
|
||||
name={action.data.name}
|
||||
data={action.data.args || action.data}
|
||||
/>
|
||||
</InspectorContainer>
|
||||
</Action>
|
||||
))}
|
||||
</Actions>
|
||||
|
||||
<ActionBar>
|
||||
<ActionButton onClick={onClear}>CLEAR</ActionButton>
|
||||
</ActionBar>
|
||||
</Wrapper>
|
||||
));
|
||||
|
||||
ActionLogger.propTypes = {
|
||||
onClear: PropTypes.func.isRequired,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
count: PropTypes.node,
|
||||
data: PropTypes.shape({
|
||||
name: PropTypes.node.isRequired,
|
||||
args: PropTypes.any,
|
||||
}),
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export default ActionLogger;
|
56
addons/actions/src/components/ActionLogger/index.tsx
Normal file
56
addons/actions/src/components/ActionLogger/index.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { styled, withTheme, Theme } from '@storybook/theming';
|
||||
|
||||
import Inspector from 'react-inspector';
|
||||
import { ActionBar, ScrollArea } from '@storybook/components';
|
||||
|
||||
import { Action, InspectorContainer, Counter } from './style';
|
||||
import { ActionDisplay } from '../../models';
|
||||
|
||||
export const Wrapper = styled(({ children, className }) => (
|
||||
<ScrollArea horizontal vertical className={className}>
|
||||
{children}
|
||||
</ScrollArea>
|
||||
))({
|
||||
margin: 0,
|
||||
padding: '10px 5px 20px',
|
||||
});
|
||||
|
||||
interface InspectorProps {
|
||||
theme: Theme;
|
||||
sortObjectKeys: boolean;
|
||||
showNonenumerable: boolean;
|
||||
name: any;
|
||||
data: any;
|
||||
}
|
||||
|
||||
const ThemedInspector = withTheme(({ theme, ...props }: InspectorProps) => (
|
||||
<Inspector theme={theme.addonActionsTheme || 'chromeLight'} {...props} />
|
||||
));
|
||||
|
||||
interface ActionLoggerProps {
|
||||
actions: ActionDisplay[];
|
||||
onClear: () => void;
|
||||
}
|
||||
|
||||
export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => (
|
||||
<Fragment>
|
||||
<Wrapper title="actionslogger">
|
||||
{actions.map((action: ActionDisplay) => (
|
||||
<Action key={action.id}>
|
||||
{action.count > 1 && <Counter>{action.count}</Counter>}
|
||||
<InspectorContainer>
|
||||
<ThemedInspector
|
||||
sortObjectKeys
|
||||
showNonenumerable={false}
|
||||
name={action.data.name}
|
||||
data={action.data.args || action.data}
|
||||
/>
|
||||
</InspectorContainer>
|
||||
</Action>
|
||||
))}
|
||||
</Wrapper>
|
||||
|
||||
<ActionBar actionItems={[{ title: 'Clear', onClick: onClear }]} />
|
||||
</Fragment>
|
||||
);
|
@ -1,42 +0,0 @@
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
export const Actions = styled.pre({
|
||||
flex: 1,
|
||||
margin: 0,
|
||||
padding: '8px 2px 20px 0',
|
||||
overflowY: 'auto',
|
||||
color: '#666',
|
||||
});
|
||||
|
||||
export const Action = styled.div({
|
||||
display: 'flex',
|
||||
padding: '3px 3px 3px 0',
|
||||
borderLeft: '5px solid transparent',
|
||||
borderBottom: '1px solid transparent',
|
||||
transition: 'all 0.1s',
|
||||
alignItems: 'start',
|
||||
});
|
||||
|
||||
export const Counter = styled.div({
|
||||
margin: '0 5px 0 5px',
|
||||
backgroundColor: '#777777',
|
||||
color: '#ffffff',
|
||||
padding: '1px 5px',
|
||||
borderRadius: '20px',
|
||||
});
|
||||
|
||||
export const Countwrap = styled.div({
|
||||
paddingBottom: 2,
|
||||
});
|
||||
|
||||
export const InspectorContainer = styled.div({
|
||||
flex: 1,
|
||||
padding: '0 0 0 5px',
|
||||
});
|
||||
|
||||
export const Wrapper = styled.div({
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
});
|
27
addons/actions/src/components/ActionLogger/style.tsx
Normal file
27
addons/actions/src/components/ActionLogger/style.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { styled } from '@storybook/theming';
|
||||
import { opacify } from 'polished';
|
||||
|
||||
export const Action = styled.div({
|
||||
display: 'flex',
|
||||
padding: '0',
|
||||
borderLeft: '5px solid transparent',
|
||||
borderBottom: '1px solid transparent',
|
||||
transition: 'all 0.1s',
|
||||
alignItems: 'flex-start',
|
||||
});
|
||||
|
||||
export const Counter = styled.div(({ theme }) => ({
|
||||
backgroundColor: opacify(0.5, theme.appBorderColor),
|
||||
color: theme.color.inverseText,
|
||||
fontSize: theme.typography.size.s1,
|
||||
fontWeight: theme.typography.weight.bold,
|
||||
lineHeight: 1,
|
||||
padding: '1px 5px',
|
||||
borderRadius: '20px',
|
||||
margin: '2px 0px',
|
||||
}));
|
||||
|
||||
export const InspectorContainer = styled.div({
|
||||
flex: 1,
|
||||
padding: '0 0 0 5px',
|
||||
});
|
@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
|
||||
import ActionLoggerComponent from '../../components/ActionLogger';
|
||||
import { EVENT_ID } from '../..';
|
||||
|
||||
export default class ActionLogger extends React.Component {
|
||||
state = { actions: [] };
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { api } = this.props;
|
||||
|
||||
api.on(EVENT_ID, this.addAction);
|
||||
api.on(STORY_RENDERED, this.handleStoryChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
const { api } = this.props;
|
||||
|
||||
api.off(STORY_RENDERED, this.handleStoryChange);
|
||||
api.off(EVENT_ID, this.addAction);
|
||||
}
|
||||
|
||||
handleStoryChange = () => {
|
||||
const { actions } = this.state;
|
||||
if (actions.length > 0 && actions[0].options.clearOnStoryChange) {
|
||||
this.clearActions();
|
||||
}
|
||||
};
|
||||
|
||||
addAction = action => {
|
||||
let { actions = [] } = this.state;
|
||||
actions = [...actions];
|
||||
|
||||
const previous = actions.length && actions[0];
|
||||
|
||||
if (previous && deepEqual(previous.data, action.data, { strict: true })) {
|
||||
previous.count++; // eslint-disable-line
|
||||
} else {
|
||||
action.count = 1; // eslint-disable-line
|
||||
actions.unshift(action);
|
||||
}
|
||||
this.setState({ actions: actions.slice(0, action.options.limit) });
|
||||
};
|
||||
|
||||
clearActions = () => {
|
||||
this.setState({ actions: [] });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { actions = [] } = this.state;
|
||||
const { active } = this.props;
|
||||
const props = {
|
||||
actions,
|
||||
onClear: this.clearActions,
|
||||
};
|
||||
return active ? <ActionLoggerComponent {...props} /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
ActionLogger.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
channel: PropTypes.shape({
|
||||
emit: PropTypes.func,
|
||||
on: PropTypes.func,
|
||||
removeListener: PropTypes.func,
|
||||
}).isRequired,
|
||||
api: PropTypes.shape({
|
||||
on: PropTypes.func,
|
||||
getQueryParam: PropTypes.func,
|
||||
setQueryParams: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
87
addons/actions/src/containers/ActionLogger/index.tsx
Normal file
87
addons/actions/src/containers/ActionLogger/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { Component } from 'react';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { API } from '@storybook/api';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
|
||||
import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger';
|
||||
import { EVENT_ID } from '../..';
|
||||
import { ActionDisplay } from '../../models';
|
||||
|
||||
interface ActionLoggerProps {
|
||||
active: boolean;
|
||||
api: API;
|
||||
}
|
||||
|
||||
interface ActionLoggerState {
|
||||
actions: ActionDisplay[];
|
||||
}
|
||||
|
||||
const safeDeepEqual = (a: any, b: any): boolean => {
|
||||
try {
|
||||
return deepEqual(a, b);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default class ActionLogger extends Component<ActionLoggerProps, ActionLoggerState> {
|
||||
private mounted: boolean;
|
||||
|
||||
constructor(props: ActionLoggerProps) {
|
||||
super(props);
|
||||
|
||||
this.state = { actions: [] };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { api } = this.props;
|
||||
|
||||
api.on(EVENT_ID, this.addAction);
|
||||
api.on(STORY_RENDERED, this.handleStoryChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
const { api } = this.props;
|
||||
|
||||
api.off(STORY_RENDERED, this.handleStoryChange);
|
||||
api.off(EVENT_ID, this.addAction);
|
||||
}
|
||||
|
||||
handleStoryChange = () => {
|
||||
const { actions } = this.state;
|
||||
if (actions.length > 0 && actions[0].options.clearOnStoryChange) {
|
||||
this.clearActions();
|
||||
}
|
||||
};
|
||||
|
||||
addAction = (action: ActionDisplay) => {
|
||||
this.setState((prevState: ActionLoggerState) => {
|
||||
const actions = [...prevState.actions];
|
||||
const previous = actions.length && actions[0];
|
||||
if (previous && safeDeepEqual(previous.data, action.data)) {
|
||||
previous.count++; // eslint-disable-line
|
||||
} else {
|
||||
action.count = 1; // eslint-disable-line
|
||||
actions.unshift(action);
|
||||
}
|
||||
return { actions: actions.slice(0, action.options.limit) };
|
||||
});
|
||||
};
|
||||
|
||||
clearActions = () => {
|
||||
this.setState({ actions: [] });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { actions = [] } = this.state;
|
||||
const { active } = this.props;
|
||||
const props = {
|
||||
actions,
|
||||
onClear: this.clearActions,
|
||||
};
|
||||
return active ? <ActionLoggerComponent {...props} /> : null;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import {
|
||||
action,
|
||||
actions,
|
||||
decorate,
|
||||
configureActions,
|
||||
decorateAction,
|
||||
withActions,
|
||||
} from './preview';
|
||||
|
||||
// addons, panels and events get unique names using a prefix
|
||||
import { ADDON_ID, PANEL_ID, EVENT_ID } from './constants';
|
||||
|
||||
export {
|
||||
action,
|
||||
actions,
|
||||
decorate,
|
||||
configureActions,
|
||||
decorateAction,
|
||||
withActions,
|
||||
ADDON_ID,
|
||||
PANEL_ID,
|
||||
EVENT_ID,
|
||||
};
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
7
addons/actions/src/index.ts
Normal file
7
addons/actions/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './constants';
|
||||
export * from './models';
|
||||
export * from './preview';
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -5,13 +5,9 @@ import { ADDON_ID, PANEL_ID } from '.';
|
||||
|
||||
export function register() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title: 'Action Logger',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active, key }) => (
|
||||
<ActionLogger key={key} channel={channel} api={api} active={active} />
|
||||
),
|
||||
title: 'Actions',
|
||||
render: ({ active, key }) => <ActionLogger key={key} api={api} active={active} />,
|
||||
});
|
||||
});
|
||||
}
|
11
addons/actions/src/models/ActionDisplay.ts
Normal file
11
addons/actions/src/models/ActionDisplay.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ActionOptions } from './ActionOptions';
|
||||
|
||||
export interface ActionDisplay {
|
||||
id: string;
|
||||
data: {
|
||||
name: string;
|
||||
args: any[];
|
||||
};
|
||||
count: number;
|
||||
options: ActionOptions;
|
||||
}
|
5
addons/actions/src/models/ActionOptions.ts
Normal file
5
addons/actions/src/models/ActionOptions.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ActionOptions {
|
||||
depth?: number;
|
||||
clearOnStoryChange?: boolean;
|
||||
limit?: number;
|
||||
}
|
5
addons/actions/src/models/ActionsMap.ts
Normal file
5
addons/actions/src/models/ActionsMap.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { HandlerFunction } from './HandlerFunction';
|
||||
|
||||
export interface ActionsMap {
|
||||
[key: string]: HandlerFunction;
|
||||
}
|
1
addons/actions/src/models/DecoratorFunction.ts
Normal file
1
addons/actions/src/models/DecoratorFunction.ts
Normal file
@ -0,0 +1 @@
|
||||
export type DecoratorFunction = (args: any[]) => any[];
|
1
addons/actions/src/models/HandlerFunction.ts
Normal file
1
addons/actions/src/models/HandlerFunction.ts
Normal file
@ -0,0 +1 @@
|
||||
export type HandlerFunction = (...args: any[]) => void;
|
5
addons/actions/src/models/index.ts
Normal file
5
addons/actions/src/models/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './ActionDisplay';
|
||||
export * from './ActionOptions';
|
||||
export * from './ActionsMap';
|
||||
export * from './DecoratorFunction';
|
||||
export * from './HandlerFunction';
|
@ -1,22 +0,0 @@
|
||||
import uuid from 'uuid/v1';
|
||||
import addons from '@storybook/addons';
|
||||
import { EVENT_ID } from '../constants';
|
||||
|
||||
export default function action(name, options = {}) {
|
||||
const actionOptions = {
|
||||
...options,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
const handler = function action(...args) {
|
||||
const channel = addons.getChannel();
|
||||
const id = uuid();
|
||||
channel.emit(EVENT_ID, {
|
||||
id,
|
||||
data: { name, args },
|
||||
options: actionOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return handler;
|
||||
}
|
29
addons/actions/src/preview/action.ts
Normal file
29
addons/actions/src/preview/action.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import uuid from 'uuid/v1';
|
||||
import { addons } from '@storybook/addons';
|
||||
import { EVENT_ID } from '../constants';
|
||||
import { ActionDisplay, ActionOptions, HandlerFunction } from '../models';
|
||||
|
||||
export function action(name: string, options: ActionOptions = {}): HandlerFunction {
|
||||
const actionOptions = {
|
||||
...options,
|
||||
};
|
||||
|
||||
const handler = function actionHandler(...args: any[]) {
|
||||
const channel = addons.getChannel();
|
||||
const id = uuid();
|
||||
const minDepth = 5; // anything less is really just storybook internals
|
||||
|
||||
const actionDisplayToEmit: ActionDisplay = {
|
||||
id,
|
||||
count: 0,
|
||||
data: { name, args },
|
||||
options: {
|
||||
...actionOptions,
|
||||
depth: minDepth + (actionOptions.depth || 3),
|
||||
},
|
||||
};
|
||||
channel.emit(EVENT_ID, actionDisplayToEmit);
|
||||
};
|
||||
|
||||
return handler;
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import action from './action';
|
||||
import { action } from './action';
|
||||
import { ActionOptions, ActionsMap } from '../models';
|
||||
|
||||
export default function actions(...args) {
|
||||
let options = {};
|
||||
export function actions(...args: any[]): ActionsMap {
|
||||
let options: ActionOptions = {};
|
||||
const names = args;
|
||||
// last argument can be options
|
||||
if (names.length !== 1 && typeof args[args.length - 1] !== 'string') {
|
||||
@ -16,7 +17,7 @@ export default function actions(...args) {
|
||||
});
|
||||
}
|
||||
|
||||
const actionsObject = {};
|
||||
const actionsObject: ActionsMap = {};
|
||||
Object.keys(namesObject).forEach(name => {
|
||||
actionsObject[name] = action(namesObject[name], options);
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
export const config = {
|
||||
depth: 10,
|
||||
clearOnStoryChange: true,
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
export function configureActions(options = {}) {
|
||||
Object.assign(config, options);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user