diff --git a/.babelrc.js b/.babelrc.js index cb85c9eb2b75..252bdc33b896 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -31,6 +31,7 @@ module.exports = { }, ], ['@babel/plugin-proposal-class-properties', { loose: true }], + ['@babel/plugin-proposal-private-methods', { loose: true }], '@babel/plugin-proposal-export-default-from', '@babel/plugin-syntax-dynamic-import', ['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }], @@ -43,18 +44,11 @@ module.exports = { overrides: [ { test: './examples/vue-kitchen-sink', - presets: ['babel-preset-vue'], + presets: ['@vue/babel-preset-jsx'], env: { test: withTests, }, }, - { - test: './examples/rax-kitchen-sink', - presets: [ - ['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }], - ['babel-preset-rax', { development: process.env.BABEL_ENV === 'development' }], - ], - }, { test: './lib', presets: [ @@ -75,11 +69,6 @@ module.exports = { test: withTests, }, }, - { - test: './app/react-native', - presets: ['module:metro-react-native-babel-preset'], - plugins: ['babel-plugin-macros', ['emotion', { sourceMap: true, autoLabel: true }]], - }, { test: [ './lib/node-logger', @@ -104,6 +93,10 @@ module.exports = { plugins: [ 'emotion', 'babel-plugin-macros', + '@babel/plugin-transform-arrow-functions', + '@babel/plugin-transform-shorthand-properties', + '@babel/plugin-transform-block-scoping', + '@babel/plugin-transform-destructuring', ['@babel/plugin-proposal-class-properties', { loose: true }], '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-export-default-from', diff --git a/.ci/danger/dangerfile.ts b/.ci/danger/dangerfile.ts index 2a1223028e31..e7d267591d89 100644 --- a/.ci/danger/dangerfile.ts +++ b/.ci/danger/dangerfile.ts @@ -3,7 +3,9 @@ import { execSync } from 'child_process'; execSync('npm install lodash'); -const { flatten, intersection, isEmpty } = require('lodash'); +const flatten = require('lodash/flatten'); +const intersection = require('lodash/intersection'); +const isEmpty = require('lodash/isEmpty'); const pkg = require('../../package.json'); // eslint-disable-line import/newline-after-import const prLogConfig = pkg['pr-log']; diff --git a/.circleci/config.yml b/.circleci/config.yml index 211098ee8415..3658e56fe2fe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,33 +4,29 @@ aliases: - &defaults working_directory: /tmp/storybook docker: - - image: circleci/node:10 + - image: circleci/node:10-browsers jobs: - build: + install: <<: *defaults steps: - checkout - restore_cache: name: Restore core dependencies cache keys: - - core-dependencies-v4-{{ checksum "yarn.lock" }} + - core-dependencies-v5-{{ checksum "yarn.lock" }} + - core-dependencies-v5- - run: name: Install dependencies command: yarn install - run: name: Check that yarn.lock is not corrupted command: yarn repo-dirty-check - - run: - name: Bootstrap - command: yarn bootstrap --core - save_cache: name: Cache core dependencies - key: core-dependencies-v4-{{ checksum "yarn.lock" }} + key: core-dependencies-v5-{{ checksum "yarn.lock" }} paths: - - ~/.cache - node_modules - - /root/.cache - persist_to_workspace: root: . paths: @@ -40,67 +36,34 @@ jobs: - dev-kits - app - lib - chromatic: + build: <<: *defaults steps: - checkout - attach_workspace: at: . - run: - name: Run chromatic on the pre-built official example - command: yarn chromatic --storybook-build-dir="built-storybooks/official-storybook" --exit-zero-on-changes --app-code="ab7m45tp9p" - - run: - name: Run chromatic on the pre-built angular example - command: yarn chromatic --storybook-build-dir="built-storybooks/angular-cli" --app-code="tl92yzsj6w" - - run: - name: Run chromatic on the pre-built cra-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/cra-kitchen-sink" --app-code="tg55gajmdt" - - run: - name: Run chromatic on the pre-built cra-react15 example - command: yarn chromatic --storybook-build-dir="built-storybooks/cra-react15" --app-code="gxk7iqej3wt" - - run: - name: Run chromatic on the pre-built cra-ts-essentials example - command: yarn chromatic --storybook-build-dir="built-storybooks/cra-ts-essentials" --app-code="b311ypk6of" - - run: - name: Run chromatic on the pre-built cra-ts-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/cra-ts-kitchen-sink" --app-code="19whyj1tlac" - - run: - name: Run chromatic on the pre-built dev-kits example - command: yarn chromatic --storybook-build-dir="built-storybooks/dev-kits" --app-code="7yykp9ifdxx" - - run: - name: Run chromatic on the pre-built ember-cli example - command: yarn chromatic --storybook-build-dir="built-storybooks/ember-cli" --app-code="19z23qxndju" - - run: - name: Run chromatic on the pre-built html-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/html-kitchen-sink" --app-code="e8zolxoyg8o" - - run: - name: Run chromatic on the pre-built marko-cli example - command: yarn chromatic --storybook-build-dir="built-storybooks/marko-cli" --app-code="qaegx64axu" - - run: - name: Run chromatic on the pre-built mithril-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/mithril-kitchen-sink" --app-code="8adgm46jzk8" - - run: - name: Run chromatic on the pre-built polymer-cli example - command: yarn chromatic --storybook-build-dir="built-storybooks/polymer-cli" --app-code="o6jl9kmh0qd" - - run: - name: Run chromatic on the pre-built preact-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/preact-kitchen-sink" --app-code="ls0ikhnwqt" - - run: - name: Run chromatic on the pre-built rax-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/rax-kitchen-sink" --app-code="4co6vptx8qo" - - run: - name: Run chromatic on the pre-built riot-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/riot-kitchen-sink" --app-code="g2dp3lnr34a" - - run: - name: Run chromatic on the pre-built svelte-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/svelte-kitchen-sink" --app-code="8ob73wgl995" - - run: - name: Run chromatic on the pre-built vue-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/vue-kitchen-sink" --app-code="cyxj0e38bqj" + name: Bootstrap + command: yarn bootstrap --core + - persist_to_workspace: + root: . + paths: + - examples + - addons + - dev-kits + - app + - lib + chromatic: + <<: *defaults + parallelism: 11 + steps: + - checkout + - attach_workspace: + at: . - run: - name: Run chromatic on the pre-built web-components-kitchen-sink example - command: yarn chromatic --storybook-build-dir="built-storybooks/web-components-kitchen-sink" --app-code="npm5gsofwkf" - + name: examples + command: | + yarn run-chromatics packtracker: <<: *defaults steps: @@ -114,7 +77,7 @@ jobs: yarn packtracker examples: <<: *defaults - parallelism: 4 + parallelism: 11 steps: - checkout - attach_workspace: @@ -127,26 +90,89 @@ jobs: root: . paths: - built-storybooks - e2e: + publish: + <<: *defaults + steps: + - checkout + - attach_workspace: + at: . + - run: + name: running local registry + command: yarn local-registry --publish + - persist_to_workspace: + root: . + paths: + - .verdaccio-cache + examples-v2: + docker: + - image: cypress/included:4.7.0 + environment: + TERM: xterm working_directory: /tmp/storybook + parallelism: 10 + steps: + - checkout + - attach_workspace: + at: . + - run: + name: running local registry + command: yarn local-registry --port 6000 --open + background: true + - run: + name: wait for registry + command: yarn wait-on http://localhost:6000 + - run: + name: set registry + command: yarn config set registry http://localhost:6000/ + - run: + name: test local registry + command: yarn info @storybook/core + - run: + name: run e2e tests + command: yarn test:e2e-framework + - store_artifacts: + path: /tmp/storybook/cypress + destination: cypress + examples-v2-yarn-2: docker: - - image: cypress/base:8 + - image: cypress/included:4.7.0 environment: TERM: xterm + working_directory: /tmp/storybook + # parallelism: 10 steps: - checkout - attach_workspace: at: . - run: - name: install cypress - command: yarn cypress install - - save_cache: - name: Cache core dependencies - key: core-dependencies-v4-{{ checksum "yarn.lock" }} - paths: - - ~/.cache - - node_modules - - /root/.cache + name: running local registry + command: yarn local-registry --port 6000 --open + background: true + - run: + name: wait for registry + command: yarn wait-on http://localhost:6000 + - run: + name: set registry + command: yarn config set registry http://localhost:6000/ + - run: + name: test local registry + command: yarn info @storybook/core + - run: + name: run e2e tests + command: yarn test:e2e-framework yarn2Cra + - store_artifacts: + path: /tmp/storybook/cypress + destination: cypress + e2e: + working_directory: /tmp/storybook + docker: + - image: cypress/included:4.7.0 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: . - run: name: running example command: yarn serve-storybooks @@ -156,7 +182,7 @@ jobs: command: yarn await-serve-storybooks - run: name: cypress run - command: yarn cypress run + command: yarn test:e2e smoke-tests: <<: *defaults @@ -194,11 +220,6 @@ jobs: command: | cd examples/ember-cli yarn storybook --smoke-test --quiet - - run: - name: Run polymer-cli (smoke test) - command: | - cd examples/polymer-cli - yarn storybook --smoke-test --quiet - run: name: Run marko-cli (smoke test) command: | @@ -229,24 +250,6 @@ jobs: command: | cd examples/cra-react15 yarn storybook --smoke-test --quiet - native-smoke-tests: - <<: *defaults - steps: - - checkout - - attach_workspace: - at: . - - run: - name: Bootstrap - 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: @@ -254,7 +257,7 @@ jobs: - restore_cache: name: Restore core dependencies cache keys: - - core-dependencies-v4-{{ checksum "yarn.lock" }} + - core-dependencies-v5-{{ checksum "yarn.lock" }} - run: name: Install dependencies command: yarn bootstrap --install @@ -265,10 +268,6 @@ jobs: <<: *defaults steps: - checkout - - restore_cache: - name: Restore docs dependencies cache - keys: - - docs-dependencies-v2-{{ checksum "docs/yarn.lock" }} - run: name: Install dependencies command: | @@ -279,11 +278,6 @@ jobs: command: | cd docs yarn build - - save_cache: - name: Cache docs dependencies - key: docs-dependencies-v2-{{ checksum "docs/yarn.lock" }} - paths: - - ~/.cache lint: <<: *defaults steps: @@ -315,10 +309,14 @@ jobs: - run: name: Upload coverage command: yarn coverage + workflows: test: jobs: - - build + - install + - build: + requires: + - install - lint: requires: - build @@ -334,9 +332,6 @@ workflows: - packtracker: requires: - build - - native-smoke-tests: - requires: - - build - test: requires: - build @@ -346,6 +341,15 @@ workflows: - chromatic: requires: - examples + - publish: + requires: + - build + - examples-v2: + requires: + - publish + - examples-v2-yarn-2: + requires: + - publish deploy: jobs: - docs diff --git a/.eslintignore b/.eslintignore index ad542e107759..a2f20ebea5a3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -18,7 +18,9 @@ examples/cra-ts-kitchen-sink/*.json examples/cra-ts-kitchen-sink/public/* examples/cra-ts-essentials/*.json examples/cra-ts-essentials/public/* - +examples/rax-kitchen-sink/src/document/* +ember-output +.yarn !.remarkrc.js !.babelrc.js !.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 3c53e6faad79..99d83c78c2f6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,45 +1,12 @@ module.exports = { root: true, extends: ['@storybook/eslint-config-storybook'], - rules: { - 'import/extensions': [ - 'error', - 'never', - { ignorePackages: true, md: 'always', svg: 'always', json: 'always', tag: 'always' }, - ], - 'import/no-unresolved': ['error', { ignore: ['@storybook'] }], - 'react/state-in-constructor': 'off', - 'react/static-property-placement': 'off', - 'react/jsx-props-no-spreading': 'off', - 'react/jsx-fragments': 'off', - '@typescript-eslint/ban-ts-ignore': 'off', - '@typescript-eslint/no-object-literal-type-assertion': 'off', - 'react/sort-comp': [ - 'error', - { - order: [ - 'staticLifecycle', - 'static-methods', - 'instance-variables', - 'lifecycle', - '/^on.+$/', - '/^(get|set)(?!(DerivedStateFromProps|SnapshotBeforeUpdate$)).+$/', - 'instance-methods', - 'instance-variables', - 'everything-else', - 'render', - ], - groups: { - staticLifecycle: ['displayName', 'propTypes', 'defaultProps', 'getDerivedStateFromProps'], - }, - }, - ], - 'max-classes-per-file': 'off', - }, overrides: [ { files: [ '**/__tests__/**', + 'scripts/**', + '**/__testfixtures__/**', '**/*.test.*', '**/*.stories.*', '**/storyshots/**/stories/**', @@ -51,7 +18,16 @@ module.exports = { 'import/no-extraneous-dependencies': 'off', }, }, + { + files: ['**/__testfixtures__/**'], + rules: { + 'react/forbid-prop-types': 'off', + 'react/no-unused-prop-types': 'off', + 'react/require-default-props': 'off', + }, + }, { files: '**/.storybook/config.js', rules: { 'global-require': 'off' } }, + { files: 'cypress/**', rules: { 'jest/expect-expect': 'off' } }, { files: ['**/*.stories.*'], rules: { diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..5ea62bf2933c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.yarn/releases/yarn-*.js linguist-generated=true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c23f1075713c..79a552730113 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,6 @@ /addons/a11y/ @jbovenschen @codebyalex /addons/actions/ @rhalff /addons/backgrounds/ @ndelangen -/addons/centered/ @kazupon /addons/events/ @z4o4z @ndelangen /addons/graphql/ @mnmtanish /addons/info/ @theinterned @z4o4z @UsulPro @dangreenisrael @@ -18,9 +17,7 @@ /addons/viewport/ @saponifi3d /app/angular/ @alterx @igor-dv -/app/polymer/ @ndelangen @naipath @leonrodenburg /app/react/ @xavcz @shilman @thomasbertet -/app/react-native/ @rmevans9 @Gongreg @tmeasday /app/vue/ @thomasbertet @kazupon /app/svelte/ @plumpNation @@ -30,7 +27,6 @@ /examples/cra-kitchen-sink/ @ndelangen @UsulPro /examples/cra-ts-kitchen-sink/ @mucsi96 /examples/official-storybook/ @UsulPro -/examples/polymer-cli/ @naipath @igor-dv /examples/vue-kitchen-sink/ @igor-dv @alexandrebodin /examples/svelte-kitchen-sink/ @plumpNation diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml index 4a4e5cf97216..79cae78e118e 100644 --- a/.github/autolabeler.yml +++ b/.github/autolabeler.yml @@ -1,7 +1,6 @@ 'addon: a11y': ["addons/a11y/**"] 'addon: actions': ["addons/actions/**"] 'addon: backgrounds': ["addons/backgrounds/**"] -'addon: centered': ["addons/centered/**"] 'addon: events ': ["addons/events/**"] 'addon: graphql ': ["addons/graphql/**"] 'addon: info': ["addons/info/**"] @@ -13,9 +12,8 @@ 'addon: storyshots': ["addons/storyshots/**"] 'addon: viewport': ["addons/viewport/**"] 'app: angular': ["app/angular/**"] -'app: polymer ': ["app/polymer/**"] 'app: preact': ["app/preact/**"] -'app: react-native': ["app/react-native/**"] +'app: rax': ["app/rax/**"] 'app: react': ["app/react/**"] 'app: vue': ["app/vue/**"] 'app: svelte': ["app/svelte/**"] diff --git a/.github/automention.yml b/.github/automention.yml index 4e3688c40d4e..4ca3b6a6256f 100644 --- a/.github/automention.yml +++ b/.github/automention.yml @@ -2,18 +2,15 @@ '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', 'gongreg'] -'app: svelte': ['rixo', 'cam-stitt', 'plumpNation'] +'app: rax': ['SoloJiang'] +'app: svelte': ['rixo', 'plumpNation'] 'app: vue': ['backbone87', 'elevatebart', 'pksunkara', 'Aaron-Pool', 'pocka'] 'app: web-components': ['daKmoR'] 'api: addons': ['ndelangen'] 'addon: a11y': ['CodeByAlex', 'Armanio', 'jsomsanith'] -'addon: contexts': ['leoyli'] -'addon: docs': ['shilman', 'elevatebart', 'jeroenreumkens'] -'addon: info': ['shilman', 'elevatebart'] +'addon: toolbars': ['shilman'] +'addon: docs': ['shilman', 'patricklafrance'] 'addon: knobs': ['leoyli', 'Armanio'] 'addon: storysource': ['igor-dv', 'libetl'] typescript: ['kroeder', 'gaetanmaisse', 'ndelangen', 'emilio-martinez'] diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml index db599cf0f080..3b5a154a4c6f 100644 --- a/.github/workflows/tests-cli.yml +++ b/.github/workflows/tests-cli.yml @@ -1,6 +1,6 @@ name: CLI tests -on: +on: push # push: # disabled for now: @@ -17,22 +17,44 @@ jobs: - uses: actions/setup-node@v1 with: node-version: '10.x' - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v1 with: path: node_modules - key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} + key: build-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.OS }}-build-${{ env.cache-name }}- - ${{ runner.OS }}-build- - ${{ runner.OS }}- + build-v2-${{ env.cache-name }}- + build-v2- - name: install, bootstrap run: | yarn bootstrap --core - name: cli run: | yarn test --cli + cli-yarn-2: + name: CLI Fixtures with Yarn 2 + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v1 + with: + node-version: '10.x' + - uses: actions/checkout@v2 + - name: Cache node modules + uses: actions/cache@v1 + with: + path: node_modules + key: build-v2-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + build-v2-${{ env.cache-name }}- + build-v2- + - name: install, bootstrap + run: | + yarn bootstrap --core + - name: cli with Yarn 2 + run: | + cd lib/cli + yarn test-yarn-2 latest-cra: name: Latest CRA runs-on: ubuntu-latest @@ -40,10 +62,10 @@ jobs: - uses: actions/setup-node@v1 with: node-version: '10.x' - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: install, bootstrap run: | yarn bootstrap --core - name: latest-cra run: | - yarn test-latest-cra \ No newline at end of file + yarn test-latest-cra diff --git a/.github/workflows/tests-puppeteer.yml b/.github/workflows/tests-puppeteer.yml deleted file mode 100644 index e7590c6e644d..000000000000 --- a/.github/workflows/tests-puppeteer.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Puppeteer & A11y tests - -on: [push] - -jobs: - build: - - name: Puppeteer & A11y tests - runs-on: ubuntu-latest - steps: - - uses: actions/setup-node@v1 - with: - node-version: '10.x' - - uses: actions/checkout@v1 - - name: Cache node modules - uses: actions/cache@v1 - with: - path: node_modules - key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.OS }}-build-${{ env.cache-name }}- - ${{ runner.OS }}-build- - ${{ runner.OS }}- - - name: install, bootstrap - run: | - yarn bootstrap --core - - name: build storybook - run: | - yarn --cwd examples/official-storybook build-storybook - - name: test - run: | - yarn test --puppeteer diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml index f3eb332e5d51..f32b2f80343b 100644 --- a/.github/workflows/tests-unit.yml +++ b/.github/workflows/tests-unit.yml @@ -4,23 +4,21 @@ on: [push] jobs: build: - - name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} - runs-on: ${{ matrix.os }} + name: Core Unit Tests + runs-on: ubuntu-latest steps: - uses: actions/setup-node@v1 with: node-version: '10.x' - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v1 with: path: node_modules - key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} + key: build-v2-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.OS }}-build-${{ env.cache-name }}- - ${{ runner.OS }}-build- - ${{ runner.OS }}- + build-v2-${{ env.cache-name }}- + build-v2- - name: install, bootstrap run: | yarn bootstrap --core diff --git a/.gitignore b/.gitignore index ffe460ac8a41..834e9ba0455b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,15 +2,13 @@ node_modules *.log .idea *.iml -.vscode/* -!.vscode/launch.json *.sw* npm-shrinkwrap.json dist +ts3.5 .tern-port *.DS_Store .cache -junit.xml coverage/ *.lerna_backup build @@ -26,12 +24,11 @@ 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 /false -storybook-out /addons/docs/common/config-* built-storybooks cypress/videos cypress/screenshots +examples/ember-cli/ember-output +.verdaccio-cache +tsconfig.tsbuildinfo diff --git a/.prettierrc b/.prettierrc index 6c3f240ceef7..d033feea7ad5 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,6 @@ "tabWidth": 2, "bracketSpacing": true, "trailingComma": "es5", - "singleQuote": true + "singleQuote": true, + "arrowParens": "always" } diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt deleted file mode 100644 index bd6ad004d7b4..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt +++ /dev/null @@ -1,81 +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_Bootstrap : BuildType({ - uuid = "9f9177e7-9ec9-4e2e-aabb-d304fd667712" - id = "OpenSourceProjects_Storybook_Bootstrap" - name = "Bootstrap" - - artifactRules = """ - addons/*/dist/** => dist.zip/addons - addons/storyshots/*/dist/** => dist.zip/addons/storyshots - app/*/dist/** => dist.zip/app - dev-kits/*/dist/** => dist.zip/dev-kits - lib/*/dist/** => dist.zip/lib - lib/core/dll/** => dist.zip/lib/core/dll - """.trimIndent() - - vcs { - root(OpenSourceProjects_Storybook.vcsRoots.OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster) - } - - steps { - script { - name = "Bootstrap" - scriptContent = """ - #!/bin/sh - - set -e -x - - yarn - yarn bootstrap --core - """.trimIndent() - dockerImage = "node:%docker.node.version%" - } - } - - triggers { - vcs { - quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT - triggerRules = "-:comment=^TeamCity change:**" - branchFilter = """ - +:pull/* - +:release/* - +:master - +:next - +:snyk-fix-* - """.trimIndent() - enabled = false - } - retryBuild { - delaySeconds = 60 - enabled = false - } - } - - features { - commitStatusPublisher { - publisher = github { - githubUrl = "https://api.github.com" - authType = personalToken { - token = "credentialsJSON:5ffe2d7e-531e-4f6f-b1fc-a41bfea26eaa" - } - } - param("github_oauth_user", "Hypnosphi") - } - } - - requirements { - doesNotContain("env.OS", "Windows") - } - - cleanup { - artifacts(days = 1) - } -}) diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1bda59b5-d08d-4fd8-b317-953e7d79d881.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1bda59b5-d08d-4fd8-b317-953e7d79d881.kts deleted file mode 100644 index 6d597e5164e2..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1bda59b5-d08d-4fd8-b317-953e7d79d881.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 = '1bda59b5-d08d-4fd8-b317-953e7d79d881' (id = 'OpenSourceProjects_Storybook_Docs') -accordingly, and delete the patch script. -*/ -changeBuildType("1bda59b5-d08d-4fd8-b317-953e7d79d881") { - check(paused == false) { - "Unexpected paused: '$paused'" - } - paused = true -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1ea2b5bd-28f6-44f5-8ab3-6c659ce8fbd6.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1ea2b5bd-28f6-44f5-8ab3-6c659ce8fbd6.kts deleted file mode 100644 index 4b5795b0d563..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/1ea2b5bd-28f6-44f5-8ab3-6c659ce8fbd6.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts deleted file mode 100644 index aef79086061a..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/2b9c73e2-0a6e-47ca-95ae-729cac42be2b.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a737.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a737.kts deleted file mode 100644 index 5b232f2b6803..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a737.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a738.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a738.kts deleted file mode 100644 index 927328468594..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/42cfbb9a-f35b-4f96-afae-0b508927a738.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-angular.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-angular.kts deleted file mode 100644 index a9c7f3490508..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-angular.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-chromatic.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-chromatic.kts deleted file mode 100644 index 8d0deb7df109..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-chromatic.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra.kts deleted file mode 100644 index 4c072da0e310..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_react15.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_react15.kts deleted file mode 100644 index 0ea4f527f639..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_react15.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_ts.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_ts.kts deleted file mode 100644 index a508334eb457..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-cra_ts.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-ember.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-ember.kts deleted file mode 100644 index ab0514e623c5..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-ember.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-html.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-html.kts deleted file mode 100644 index 039eed95efe6..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-html.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-marko.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-marko.kts deleted file mode 100644 index 754da521c8f3..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-marko.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-mithril.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-mithril.kts deleted file mode 100644 index bd0913a8a04d..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-mithril.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-polymer.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-polymer.kts deleted file mode 100644 index 7ed9e87651fe..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-polymer.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-preact.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-preact.kts deleted file mode 100644 index 7f169a3828fd..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-preact.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-riot.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-riot.kts deleted file mode 100644 index c484eca68e59..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-riot.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-svelte.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-svelte.kts deleted file mode 100644 index 221d31ccf7b1..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-svelte.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-vue.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-vue.kts deleted file mode 100644 index 9c9fc3530876..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6-vue.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6.kts deleted file mode 100644 index 1fce2cf575d8..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/8cc5f747-4ca7-4f0d-940d-b0c422f501a6.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667711.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667711.kts deleted file mode 100644 index 7b41f2e260a0..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667711.kts +++ /dev/null @@ -1,22 +0,0 @@ -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-d304fd667711' (id = 'OpenSourceProjects_Storybook_Test') -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") - } - } -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667712.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667712.kts deleted file mode 100644 index 6a63ab84d5c4..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/9f9177e7-9ec9-4e2e-aabb-d304fd667712.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/d4320bd8-6094-4dd6-9bed-e13d6f0d12e2.kts b/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/d4320bd8-6094-4dd6-9bed-e13d6f0d12e2.kts deleted file mode 100644 index 7e2fb0801929..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/buildTypes/d4320bd8-6094-4dd6-9bed-e13d6f0d12e2.kts +++ /dev/null @@ -1,16 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts b/.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts deleted file mode 100644 index a357d89ba726..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts +++ /dev/null @@ -1,17 +0,0 @@ -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 -} diff --git a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt b/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt deleted file mode 100644 index ee7951d8b573..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster.kt +++ /dev/null @@ -1,20 +0,0 @@ -package OpenSourceProjects_Storybook.vcsRoots - -import jetbrains.buildServer.configs.kotlin.v2017_2.* -import jetbrains.buildServer.configs.kotlin.v2017_2.vcs.GitVcsRoot - -object OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster : GitVcsRoot({ - uuid = "cec03c4b-d52c-42a0-8e9e-53bde85d6b33" - id = "OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster" - name = "Main root" - url = "git@github.com:storybookjs/storybook.git" - branch = "refs/heads/next" - branchSpec = """ - +:refs/(pull/*)/head - +:refs/heads/* - """.trimIndent() - authMethod = uploadedKey { - userName = "git" - uploadedKey = "Storybook bot" - } -}) diff --git a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1.kt b/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1.kt deleted file mode 100644 index 7daa8d93aa1f..000000000000 --- a/.teamcity/OpenSourceProjects_Storybook/vcsRoots/OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1.kt +++ /dev/null @@ -1,16 +0,0 @@ -package OpenSourceProjects_Storybook.vcsRoots - -import jetbrains.buildServer.configs.kotlin.v2017_2.* -import jetbrains.buildServer.configs.kotlin.v2017_2.vcs.GitVcsRoot - -object OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1 : GitVcsRoot({ - uuid = "5cacf90a-381a-4c73-9aa3-57f6439b545e" - id = "OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster1" - name = "https://github.com/storybookjs/storybook#refs/heads/master (1)" - url = "git@github.com:storybookjs/storybook.git" - branch = "refs/heads/next" - authMethod = uploadedKey { - userName = "git" - uploadedKey = "Storybook bot" - } -}) diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml new file mode 100644 index 000000000000..b98e212b6d2d --- /dev/null +++ b/.teamcity/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + Hosted_Root Config DSL Script + Hosted_Root + Hosted_Root_dsl + 1.0-SNAPSHOT + + + org.jetbrains.teamcity + configs-dsl-kotlin-parent + 1.0-SNAPSHOT + + + + + jetbrains-all + https://download.jetbrains.com/teamcity-repository + + true + + + + teamcity-server + https://storybook.beta.teamcity.com/app/dsl-plugins-repository + + true + + + + + + + JetBrains + https://download.jetbrains.com/teamcity-repository + + + + + ${basedir} + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + + compile + process-sources + + compile + + + + test-compile + process-test-sources + + test-compile + + + + + + org.jetbrains.teamcity + teamcity-configs-maven-plugin + ${teamcity.dsl.version} + + kotlin + target/generated-configs + + + + + + + + org.jetbrains.teamcity + configs-dsl-kotlin + ${teamcity.dsl.version} + compile + + + org.jetbrains.teamcity + configs-dsl-kotlin-plugins + 1.0-SNAPSHOT + pom + compile + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + compile + + + org.jetbrains.kotlin + kotlin-script-runtime + ${kotlin.version} + compile + + + + 2019.2.2-SNAPSHOT + + \ No newline at end of file diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts new file mode 100644 index 000000000000..2826c1006979 --- /dev/null +++ b/.teamcity/settings.kts @@ -0,0 +1,614 @@ +import jetbrains.buildServer.configs.kotlin.v2019_2.* +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.PullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests +import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.swabra +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep +import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script +import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric +import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange +import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.buildReportTab +import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubConnection +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.VcsTrigger +import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs + +/* +The settings script is an entry point for defining a TeamCity +project hierarchy. The script should contain a single call to the +project() function with a Project instance or an init function as +an argument. + +VcsRoots, BuildTypes, Templates, and subprojects can be +registered inside the project using the vcsRoot(), buildType(), +template(), and subProject() methods respectively. + +To debug settings scripts in command-line, run the + + mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate + +command and attach your debugger to the port 8000. + +To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View +-> Tool Windows -> Maven Projects), find the generate task node +(Plugins -> teamcity-configs -> teamcity-configs:generate), the +'Debug' option is available in the context menu for the task. +*/ + +version = "2019.2" + +project { + template(Common) + defaultTemplate = Common + + buildType(TestWorkflow) + + buildType(Build) + buildType(E2E) + buildType(SmokeTests) + buildType(Frontpage) + buildType(Docs) + buildType(Lint) + buildType(Test) + buildType(Coverage) + + subProject(ExamplesProject) + + buildTypesOrderIds = arrayListOf( + RelativeId("TestWorkflow"), + RelativeId("Build"), + RelativeId("E2E"), + RelativeId("SmokeTests"), + RelativeId("Frontpage"), + RelativeId("Docs"), + RelativeId("Lint"), + RelativeId("Test"), + RelativeId("Coverage") + ) + + + features { + githubConnection { + id = "PROJECT_EXT_6" + displayName = "GitHub.com" + clientId = "800d730c725f771d6d2a" + clientSecret = "credentialsJSON:d1a5af15-1200-46c6-b0f1-f35bd466d909" + } + buildReportTab { + id = "PROJECT_EXT_8" + title = "Official" + startPage = "built-storybooks.tar.gz!official-storybook/index.html" + } + } +} + +object Common: Template({ + name = "Common" + + vcs { + root(DslContext.settingsRoot) + checkoutMode = CheckoutMode.ON_AGENT + checkoutDir = "storybook/%teamcity.build.branch%" + } + + features { + commitStatusPublisher { + id = "Commit status publisher" + publisher = github { + githubUrl = "https://api.github.com" + authType = personalToken { + token = "credentialsJSON:5273320e-14be-4317-951e-a54c4dcca35d" + } + } + param("github_oauth_user", "Hypnosphi") + } + swabra { + id = "swabra" + verbose = true + paths = """ + -:.cache + -:node_modules + -:**/node_modules + """.trimIndent() + } + pullRequests { + id = "Pull requests" + provider = github { + authType = vcsRoot() + filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY + } + } + } +}) + +object Build : BuildType({ + name = "Build" + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + yarn repo-dirty-check + yarn bootstrap --core + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + artifactRules = """ + +:**/dist/** => dist.tar.gz + +:**/dll/** => dist.tar.gz + -:**/node_modules/** => dist.tar.gz + """.trimIndent() +}) + +object ExamplesProject : Project({ + name = "Examples" + + template(ExamplesTemplate) + + buildType(Examples1) + buildType(Examples2) + buildType(Examples3) + buildType(Examples4) + buildType(Examples5) + buildType(AggregateExamples) +}) + +object ExamplesTemplate : Template({ + name = "Examples Template" + + dependencies { + dependency(Build) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "dist.tar.gz!** => ." + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + rm -rf built-storybooks + mkdir -p built-storybooks + + yarn build-storybooks + """.trimIndent() + dockerImage = "buildkite/puppeteer" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + artifactRules = "built-storybooks => built-storybooks.tar.gz" + + params { + param("env.CIRCLE_NODE_TOTAL", "5") + } +}) + +object Examples1 : BuildType({ + name = "Examples 1" + templates = listOf(ExamplesTemplate) + + params { + param("env.CIRCLE_NODE_INDEX", "0") + } + + disableSettings("Commit status publisher") +}) + +object Examples2 : BuildType({ + name = "Examples 2" + templates = listOf(ExamplesTemplate) + + params { + param("env.CIRCLE_NODE_INDEX", "1") + } + + disableSettings("Commit status publisher") +}) + +object Examples3 : BuildType({ + name = "Examples 3" + templates = listOf(ExamplesTemplate) + + params { + param("env.CIRCLE_NODE_INDEX", "2") + } + + disableSettings("Commit status publisher") +}) + +object Examples4 : BuildType({ + name = "Examples 4" + templates = listOf(ExamplesTemplate) + + params { + param("env.CIRCLE_NODE_INDEX", "3") + } + + disableSettings("Commit status publisher") +}) + +object Examples5 : BuildType({ + name = "Examples 5" + templates = listOf(ExamplesTemplate) + + params { + param("env.CIRCLE_NODE_INDEX", "4") + } + + disableSettings("Commit status publisher") +}) + +object AggregateExamples : BuildType({ + name = "Aggregate Examples" + + dependencies { + dependency(Examples1) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + dependency(Examples2) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + dependency(Examples3) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + dependency(Examples4) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + dependency(Examples5) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + } + + artifactRules = "built-storybooks => built-storybooks.tar.gz" +}) + +object E2E : BuildType({ + name = "E2E" + + dependencies { + dependency(AggregateExamples) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "built-storybooks.tar.gz!** => built-storybooks" + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + yarn cypress install + yarn serve-storybooks & + yarn await-serve-storybooks + yarn cypress run --reporter teamcity || : + yarn ts-node --transpile-only cypress/report-teamcity-metadata.ts || : + """.trimIndent() + dockerImage = "cypress/base:10.18.1" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + artifactRules = """ + cypress/screenshots => screenshots.tar.gz + cypress/videos => videos.tar.gz + """.trimIndent() + + failureConditions { + failOnMetricChange { + metric = BuildFailureOnMetric.MetricType.TEST_COUNT + units = BuildFailureOnMetric.MetricUnit.DEFAULT_UNIT + comparison = BuildFailureOnMetric.MetricComparison.LESS + compareTo = value() + } + } +}) + +object SmokeTests : BuildType({ + name = "Smoke Tests" + + dependencies { + dependency(Build) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "dist.tar.gz!** => ." + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + + cd examples/cra-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../cra-ts-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../vue-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../svelte-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../angular-cli + yarn storybook --smoke-test --quiet + + cd ../ember-cli + yarn storybook --smoke-test --quiet + + cd ../marko-cli + yarn storybook --smoke-test --quiet + + cd ../official-storybook + yarn storybook --smoke-test --quiet + + cd ../mithril-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../riot-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../preact-kitchen-sink + yarn storybook --smoke-test --quiet + + cd ../cra-react15 + yarn storybook --smoke-test --quiet + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } +}) + +object Frontpage : BuildType({ + name = "Frontpage" + type = Type.DEPLOYMENT + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn bootstrap --install + node ./scripts/build-frontpage.js + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + triggers { + vcs { + quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT + triggerRules = "-:.teamcity/**" + branchFilter = "+:master" + } + } +}) + +object Docs : BuildType({ + name = "Docs" + type = Type.DEPLOYMENT + + steps { + script { + workingDir = "docs" + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + yarn build + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + triggers { + vcs { + quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT + triggerRules = "-:.teamcity/**" + branchFilter = """ + +: + +:next + +:master + +:pull/* + """.trimIndent() + } + } +}) + +object Lint : BuildType({ + name = "Lint" + + dependencies { + dependency(Build) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "dist.tar.gz!** => ." + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + + # TODO remove after merging + mkdir temp-eslint-teamcity + cd temp-eslint-teamcity + yarn init -y + yarn add -D eslint-teamcity + cd .. + + yarn lint:js --format ./temp-eslint-teamcity/node_modules/eslint-teamcity/index.js . + yarn lint:md . + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + failureConditions { + failOnMetricChange { + metric = BuildFailureOnMetric.MetricType.INSPECTION_ERROR_COUNT + threshold = 0 + units = BuildFailureOnMetric.MetricUnit.DEFAULT_UNIT + comparison = BuildFailureOnMetric.MetricComparison.MORE + compareTo = value() + } + } +}) + +object Test : BuildType({ + name = "Test" + + dependencies { + dependency(Build) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "dist.tar.gz!** => ." + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + + # TODO remove after merging + mkdir temp-jest-teamcity + cd temp-jest-teamcity + yarn init -y + yarn add -D jest-teamcity + cd .. + + yarn jest --coverage -w 2 --reporters=${'$'}PWD/temp-jest-teamcity/node_modules/jest-teamcity + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } + + artifactRules = "coverage => coverage.tar.gz" +}) + +object Coverage : BuildType({ + name = "Coverage" + + dependencies { + dependency(Test) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + } + artifacts { + artifactRules = "coverage.tar.gz!** => coverage" + } + } + } + + steps { + script { + scriptContent = """ + #!/bin/bash + set -e -x + + yarn install + yarn coverage + """.trimIndent() + dockerImage = "node:10" + dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux + } + } +}) + +object TestWorkflow : BuildType({ + name = "Test Workflow" + type = Type.COMPOSITE + maxRunningBuilds = 2 + + dependencies { + snapshot(E2E) {} + snapshot(SmokeTests) {} + snapshot(Lint) {} + snapshot(Coverage) {} + } + + triggers { + vcs { + quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_DEFAULT + triggerRules = "-:.teamcity/**" + branchFilter = """ + +: + +:next + +:master + +:pull/* + """.trimIndent() + } + } +}) diff --git a/.vscode/launch.json b/.vscode/launch.json index 50ef06713aca..daa7997bc064 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,22 @@ "skipFiles": [ "/**" ] - }, + }, { + "type": "node", + "request": "launch", + "name": "cli html", + "cwd": "${workspaceFolder}/lib/cli/stories", + "runtimeArgs": [ + "--inspect-brk", + "${workspaceFolder}/lib/cli/bin/index.js", + "init", + "--type", + "html" + ], + "port": 9229, + "skipFiles": [ + "/**" + ] + } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..66d2c072fcdb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deepscan.enable": true +} \ No newline at end of file diff --git a/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index a87bcfb68bc5..cdad86428e7a 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -1,25 +1,32 @@ ## Addon / Framework Support Table -| | [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) |+|+*|+|+|+|+|+|+|+|+|+|+| -|[backgrounds](addons/backgrounds) |+|*|+|+|+|+|+|+|+|+|+|+| -|[centered](addons/centered) |+| |+|+| |+|+| |+| |+|+| -|[contexts](addons/contexts) |+| |+| | | | | | | | |+| -|[events](addons/events) |+| |+|+|+|+|+|+| | |+|+| -|[design assets](addons/design-assets) |+| |+|+|+|+|+|+|+|+|+|+| -|[graphql](addons/graphql) |+| | | | | | | | | | | | -|[google-analytics](addons/google-analytics) |+|+|+|+|+|+|+|+|+|+|+|+| -|[info](addons/info) |+| | | | | | | | | | | | -|[jest](addons/jest) |+|+|+|+|+|+|+|+|+|+|+|+| -|[knobs](addons/knobs) |+|+*|+|+|+|+|+|+|+|+|+|+| -|[links](addons/links) |+|+|+|+|+|+|+| |+|+|+|+| -|[notes](addons/notes) |+|+*|+|+|+|+|+| |+|+|+|+| -|[options](addons/options) |+|+|+|+|+|+|+| |+|+|+|+| -|[cssresources](addons/cssresources) |+| |+|+|+|+|+|+|+|+|+|+| -|[storyshots](addons/storyshots) |+|+|+|+| | |+| |+|+| |+| -|[storysource](addons/storysource) |+| |+|+|+|+|+|+|+|+|+|+| -|[viewport](addons/viewport) |+| |+|+|+|+|+|+|+|+|+|+| +| | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Mithril](app/mithril) | [HTML](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) | +| ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- | +| [a11y](addons/a11y) | + | | + | + | + | + | + | + | + | + | + | + | +| [actions](addons/actions) | + | +\* | + | + | + | + | + | + | + | + | + | + | +| [backgrounds](addons/backgrounds) | + | \* | + | + | + | + | + | + | + | + | + | + | +| [cssresources](addons/cssresources) | + | | + | + | + | + | + | + | + | + | + | + | +| [design assets](addons/design-assets) | + | | + | + | + | + | + | + | + | + | + | + | +| [docs](addons/docs) | + | | + | + | + | + | + | + | + | + | + | + | +| [events](addons/events) | + | | + | + | + | + | + | | | + | + | + | +| [google-analytics](addons/google-analytics) | + | + | + | + | + | + | + | + | + | + | + | + | +| [graphql](addons/graphql) | + | | | | | | | | | | | | +| [jest](addons/jest) | + | + | + | + | + | + | + | + | + | + | + | + | +| [knobs](addons/knobs) | + | +\* | + | + | + | + | + | + | + | + | + | + | +| [links](addons/links) | + | + | + | + | + | + | | + | + | + | + | + | +| [options](addons/options) | + | + | + | + | + | + | | + | + | + | + | + | +| [query params](addons/queryparams) | + | | + | + | + | + | + | + | + | + | + | + | +| [storyshots](addons/storyshots) | + | + | + | + | | + | | + | + | | + | + | +| [storysource](addons/storysource) | + | | + | + | + | + | + | + | + | + | + | + | +| [viewport](addons/viewport) | + | | + | + | + | + | + | + | + | + | + | + | -`*` - React Native on device addon (addons/onDevice-\) +`*` - React Native on device addon (addons/onDevice-\) + +## Deprecated Addons + +| | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Mithril](app/mithril) | [HTML](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) | +| ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- | +| [info](https://github.com/storybookjs/storybook/tree/master/addons/info) | + | | | | | | | | | | | | +| [notes](https://github.com/storybookjs/storybook/tree/master/addons/notes) | + | +\* | + | + | + | + | | + | + | + | + | + | + +`*` - React Native on device addon (addons/onDevice-\) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e34b8bb52cf..ed9e7f29f00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,1325 @@ +## 6.0.0-beta.21 (June 4, 2020) + +### Breaking Changes + +- Preact: Update Preact version ([#10978](https://github.com/storybookjs/storybook/pull/10978)) + +### Features + +- Addon-docs: Angular ArgTypes for pipes, injectables, classes ([#11016](https://github.com/storybookjs/storybook/pull/11016)) +- TypeScript: Add warning for setup issues and fix Babel config ([#10998](https://github.com/storybookjs/storybook/pull/10998)) +- Core: Add logLevel preset property to filter logging ([#10370](https://github.com/storybookjs/storybook/pull/10370)) + +### Bug Fixes + +- Addon-controls: Fix initialization logic; remove react-select ([#11024](https://github.com/storybookjs/storybook/pull/11024)) +- CLI: Fix `sb init` in Yarn workspace environment ([#10985](https://github.com/storybookjs/storybook/pull/10985)) + +### Maintenance + +- React: Remove argsStory helper function ([#11036](https://github.com/storybookjs/storybook/pull/11036)) +- Addon-controls: Remove residual options-type controls ([#11015](https://github.com/storybookjs/storybook/pull/11015)) + +## 6.0.0-beta.20 (June 1, 2020) + +### Bug Fixes + +- Addon-controls: Fix `options` control types ([#11003](https://github.com/storybookjs/storybook/pull/11003)) +- Addon-controls: Fix no-control handling ([#11001](https://github.com/storybookjs/storybook/pull/11001)) +- Addon-docs: Fix function argType inference in react-docgen-typescript ([#10997](https://github.com/storybookjs/storybook/pull/10997)) + +### Maintenance + +- Addon-controls/a11y: Fix PARAM_KEY export for consistency ([#10988](https://github.com/storybookjs/storybook/pull/10988)) + +## 6.0.0-beta.19 (May 30, 2020) + +### Features + +- Addon-controls: Add warning to controls tab on no-args story ([#10986](https://github.com/storybookjs/storybook/pull/10986)) + +### Bug Fixes + +- Addon-docs: Handle JSON.parse exception for Angular union types ([#10984](https://github.com/storybookjs/storybook/pull/10984)) + +## 6.0.0-beta.18 (May 29, 2020) + +### Bug Fixes + +- Core: Fix HMR for navigation sidebar in UI ([#10981](https://github.com/storybookjs/storybook/pull/10981)) +- Core: Fix `register.tsx` as manager code in preset heuristic ([#10980](https://github.com/storybookjs/storybook/pull/10980)) +- Core: Send global args with set stories ([#10910](https://github.com/storybookjs/storybook/pull/10910)) +- Core: Log swallowed errors when requiring stories ([#10974](https://github.com/storybookjs/storybook/pull/10974)) +- Core: Support valid globs ([#10926](https://github.com/storybookjs/storybook/pull/10926)) + +## 6.0.0-beta.17 (May 28, 2020) + +### Features + +- Addon-controls: Angular support ([#10946](https://github.com/storybookjs/storybook/pull/10946)) +- Addon-controls: Web-components support ([#10953](https://github.com/storybookjs/storybook/pull/10953)) + +## 6.0.0-beta.16 (May 28, 2020) + +### Bug Fixes + +- Core: Add missing babel plugin ([#10941](https://github.com/storybookjs/storybook/pull/10941)) + +### Maintenance + +- CI: Stabilize E2E tests ([#10888](https://github.com/storybookjs/storybook/pull/10888)) + +## 6.0.0-beta.15 (May 27, 2020) + +### Features + +- Addon-Controls: Next-generation knobs ([#10834](https://github.com/storybookjs/storybook/pull/10834)) + +### Bug Fixes + +- Core: Avoid re-render on HMR of other stories ([#10908](https://github.com/storybookjs/storybook/pull/10908)) +- Core: Fix auth for refs ([#10845](https://github.com/storybookjs/storybook/pull/10845)) + +### Dependency Upgrades + +- Bump react-syntax-highlighter from 11.0.2 to 12.2.1 ([#10919](https://github.com/storybookjs/storybook/pull/10919)) + +## 6.0.0-beta.14 (May 25, 2020) + +### Breaking Changes + +- CSF: Hoist story annotation object ([#10907](https://github.com/storybookjs/storybook/pull/10907)) +- Vue: Remove babel-preset-vue ([#10909](https://github.com/storybookjs/storybook/pull/10909)) + +### Features + +- Angular: Support `workspace.json` in nx workspace ([#10881](https://github.com/storybookjs/storybook/pull/10881)) + +### Bug Fixes + +- Addon-docs: Fix single item width in Preview block ([#10877](https://github.com/storybookjs/storybook/pull/10877)) +- UI: Center toolbar icon buttons ([#10897](https://github.com/storybookjs/storybook/pull/10897)) +- Core: Fix double rendering on startup ([#10892](https://github.com/storybookjs/storybook/pull/10892)) + +### Maintenance + +- Core: Use dedicated loader for es6 modules ([#10783](https://github.com/storybookjs/storybook/pull/10783)) +- Core: Fix yarn test command on windows ([#10904](https://github.com/storybookjs/storybook/pull/10904)) + +## 5.3.19 (May 24, 2020) + +### Bug Fixes + +- UI: Fix search stories ([#10539](https://github.com/storybookjs/storybook/pull/10539)) + +### Security + +- Upgrade markdown-to-jsx to 6.11.4 ([#10873](https://github.com/storybookjs/storybook/pull/10873)) + +## 6.0.0-beta.13 (May 23, 2020) + +### Bug Fixes + +- Core: Fix ts/tsx resolution in the manager ([#10886](https://github.com/storybookjs/storybook/pull/10886)) +- Core: Fix typo in projectRoot node_modules detection ([#10848](https://github.com/storybookjs/storybook/pull/10848)) +- Addon-docs: Fix story inline rendering ([#10875](https://github.com/storybookjs/storybook/pull/10875)) +- Core: Fix CRA filter for built-in webpack settings ([#10861](https://github.com/storybookjs/storybook/pull/10861)) +- Addon-docs: Fix react forwardRefs with destructured props ([#10864](https://github.com/storybookjs/storybook/pull/10864)) + +### Maintenance + +- React: Upgrade preset-create-react-app in examples ([#10867](https://github.com/storybookjs/storybook/pull/10867)) +- Core: Close server when e2e test failed ([#10868](https://github.com/storybookjs/storybook/pull/10868)) + +### Dependency Upgrades + +- Upgrade markdown-to-jsx to 6.11.4 ([#10873](https://github.com/storybookjs/storybook/pull/10873)) + +## 6.0.0-beta.12 (May 21, 2020) + +### Breaking Changes + +- Core: Zero-config TypeScript loading ([#10813](https://github.com/storybookjs/storybook/pull/10813)) + +## 6.0.0-beta.11 (May 21, 2020) + +Failed publish + +## 6.0.0-beta.10 (May 21, 2020) + +Failed publish + +## 6.0.0-beta.9 (May 21, 2020) + +### Bug Fixes + +- UI: Avoid full refresh when on some tab changes ([#10838](https://github.com/storybookjs/storybook/pull/10838)) +- Composition: Fix refs not authenticating ([#10819](https://github.com/storybookjs/storybook/pull/10819)) +- Core: Fix global args initial state for addon-toolbars ([#10833](https://github.com/storybookjs/storybook/pull/10833)) +- Addon-a11y: Add deprecated withA11y ([#10814](https://github.com/storybookjs/storybook/pull/10814)) +- Core: Transpile minimum node_modules ([#10725](https://github.com/storybookjs/storybook/pull/10725)) +- UI: Change default view to Canvas on mobile ([#10818](https://github.com/storybookjs/storybook/pull/10818)) +- Docs: Improve Preview zoom handling ([#10801](https://github.com/storybookjs/storybook/pull/10801)) + +### Maintenance + +- CI: example overhaul clean ([#10702](https://github.com/storybookjs/storybook/pull/10702)) +- CLI: Migrate CLI to TypeScript ([#10802](https://github.com/storybookjs/storybook/pull/10802)) + +### Dependency Upgrades + +- Upgrade and add some missing dependencies in core, router, api ([#10825](https://github.com/storybookjs/storybook/pull/10825)) + +## 6.0.0-beta.8 (May 17, 2020) + +### Features + +- Addon-toolbars: Show tool icons for all viewModes ([#10810](https://github.com/storybookjs/storybook/pull/10810)) + +### Bug Fixes + +- Addon-docs: Eval argTypes default value ([#10812](https://github.com/storybookjs/storybook/pull/10812)) + +### Maintenance + +- Scripts: parallel execution on build package scripts ([#10808](https://github.com/storybookjs/storybook/pull/10808)) + +## 6.0.0-beta.7 (May 15, 2020) + +### Breaking changes + +- Cleanup: Remove support for babel-loader < 8 ([#10781](https://github.com/storybookjs/storybook/pull/10781)) + +### Features + +- Composition: Zero-config composition from dependencies ([#10753](https://github.com/storybookjs/storybook/pull/10753)) + +### Bug Fixes + +- Core: Detect local addons for windows machine ([#10786](https://github.com/storybookjs/storybook/pull/10786)) +- Composition: Rename `mapper` to `storyMapper` and fix loading bugs ([#10780](https://github.com/storybookjs/storybook/pull/10780)) + +### Maintenance + +- CLI: HTML stories homogenization ([#10705](https://github.com/storybookjs/storybook/pull/10705)) +- CLI: web-components stories homogenization ([#10703](https://github.com/storybookjs/storybook/pull/10703)) + +### Dependency Upgrades + +- Update jest-preset-angular to 8.2.0 ([#10778](https://github.com/storybookjs/storybook/pull/10778)) + +## 6.0.0-beta.6 (May 12, 2020) + +### Breaking Changes + +- Essentials: Update configuration heuristics for main.js ([#10737](https://github.com/storybookjs/storybook/pull/10737)) + +### Features + +- Essentials: Add addon-actions ([#10748](https://github.com/storybookjs/storybook/pull/10748)) +- Essentials: Add addon-docs ([#10729](https://github.com/storybookjs/storybook/pull/10729)) + +### Bug Fixes + +- UI: Reset layout properties when switching stories ([#10643](https://github.com/storybookjs/storybook/pull/10643)) + +### Maintenance + +- CLI: react stories homogenization ([#10711](https://github.com/storybookjs/storybook/pull/10711)) +- CLI: vue stories homogenization ([#10708](https://github.com/storybookjs/storybook/pull/10708)) +- CLI: webpack react stories homogenization ([#10709](https://github.com/storybookjs/storybook/pull/10709)) +- CLI: svelte stories homogenization ([#10704](https://github.com/storybookjs/storybook/pull/10704)) +- CLI: react-scripts stories homogenization ([#10710](https://github.com/storybookjs/storybook/pull/10710)) +- CLI: mithril stories homogenization ([#10707](https://github.com/storybookjs/storybook/pull/10707)) +- CLI: rax stories homogenization ([#10706](https://github.com/storybookjs/storybook/pull/10706)) +- CLI: riot stories homogenization ([#10715](https://github.com/storybookjs/storybook/pull/10715)) +- CLI: ember stories homogenization ([#10713](https://github.com/storybookjs/storybook/pull/10713)) +- CLI: preact stories homogenization ([#10712](https://github.com/storybookjs/storybook/pull/10712)) +- CLI: sfc_vue stories homogenization ([#10714](https://github.com/storybookjs/storybook/pull/10714)) + +### Dependency Upgrades + +- Revert "Change reference for jest-preset-angular/build/setupJest as per migration guide" ([#10727](https://github.com/storybookjs/storybook/pull/10727)) + +## 6.0.0-beta.5 (May 11, 2020) + +### Bug Fixes + +- Core: Fix error handling on load ([#10659](https://github.com/storybookjs/storybook/pull/10659)) + +### Maintenance + +- Storyshots: Change reference for jest-preset-angular/build/setupJest ([#10699](https://github.com/storybookjs/storybook/pull/10699)) +- CLI: Remove CRA fixtures from Yarn 2 tests run ([#10720](https://github.com/storybookjs/storybook/pull/10720)) +- Fix: Set private package on Aurelia example ([#10688](https://github.com/storybookjs/storybook/pull/10688)) + +## 6.0.0-beta.4 (May 8, 2020) + +### Features + +- React: Add `argsStory` convenience function ([#10685](https://github.com/storybookjs/storybook/pull/10685)) + +### Dependency Upgrades + +- Build: Upgrade jest to 26 ([#10669](https://github.com/storybookjs/storybook/pull/10669)) + +## 6.0.0-beta.3 (May 7, 2020) + +### Breaking Changes + +- Addon-backgrounds: Simplified parameters API ([#10634](https://github.com/storybookjs/storybook/pull/10634)) + +### Bug Fixes + +- Core: Fix `globalArgs` initialization from global parameters ([#10566](https://github.com/storybookjs/storybook/pull/10566)) +- Core: Fix DLL context for IE11 ([#106444]https://github.com/storybookjs/storybook/pull/10644)) + +### Dependency Upgrades + +- Addon-storyshots: Upgrade to jest 26 ([#10642](https://github.com/storybookjs/storybook/pull/10642)) +- Bump terser-webpack-plugin from 2.3.6 to 3.0.0 ([#10650](https://github.com/storybookjs/storybook/pull/10650)) + +## 6.0.0-beta.2 (May 4, 2020) + +### Bug Fixes + +- Addon-docs: Fix broken props logic for no-args stories ([#10633](https://github.com/storybookjs/storybook/pull/10633)) +- Addon-docs: Fix custom source manual override ([#10632](https://github.com/storybookjs/storybook/pull/10632)) +- Addon-docs: Fix MDX stories with multiple children ([#9531](https://github.com/storybookjs/storybook/pull/9531)) +- Addon-docs: Fix object array in Props ([#10621](https://github.com/storybookjs/storybook/pull/10621)) +- Actions: Fix import of `uuid` ([#10625](https://github.com/storybookjs/storybook/pull/10625)) + +### Maintenance + +- Core: Fix Args test to not use different code path ([#10607](https://github.com/storybookjs/storybook/pull/10607)) + +## 6.0.0-beta.1 (May 2, 2020) + +### Features + +- CLI: Add automatic detection for svelte ([#10623](https://github.com/storybookjs/storybook/pull/10623)) + +### Bug Fixes + +- Addon-docs: Fix no-props logic in Source block ([#10619](https://github.com/storybookjs/storybook/pull/10619)) +- Props: Fix subcomponents ([#10608](https://github.com/storybookjs/storybook/pull/10608)) + +### Maintenance + +- Yarn 2: Fix dependencies issues for compatibility ([#10613](https://github.com/storybookjs/storybook/pull/10613)) +- CLI: Fix cli when working with Yarn 2 and Node 10 ([#10550](https://github.com/storybookjs/storybook/pull/10550)) + +## 6.0.0-beta.0 (April 29, 2020) + +Storybook 6.0 is in beta. 🎉🎉🎉 + +Hundreds of improvements and fixes, including: + +- **Args** - Dynamic story data with automatic prop controls and actions. +- **Composition** - Compose storybooks for better documentation, performance, and multi-framework support. +- **Server** - Enabling Storybook for Rails and other server-side components. +- **Yarn 2** - Supporting next generation package management. + +Track the release in the Github: [Storybook 6.0 Release 🏆](https://github.com/storybookjs/storybook/issues/9311) + +## 6.0.0-alpha.46 (April 29, 2020) + +### Breaking Changes + +- Core: Normalize parameters in store/channel ([#10373](https://github.com/storybookjs/storybook/pull/10373)) +- React: Remove deprecated CRA preset ([#10571](https://github.com/storybookjs/storybook/pull/10571)) + +### Features + +- Addon-docs: Props controls for Vue ([#10559](https://github.com/storybookjs/storybook/pull/10559)) + +### Bug Fixes + +- Addon-docs: Add subcomponents prop to Meta block ([#10573](https://github.com/storybookjs/storybook/pull/10573)) + +## 6.0.0-alpha.45 (April 28, 2020) + +## Breaking changes + +- Core: Pass args first to stories by default ([#10452](https://github.com/storybookjs/storybook/pull/10452)) + +## 6.0.0-alpha.44 (April 27, 2020) + +### Features + +- CLI: Automatically detect typescript in `sb init` ([#10547](https://github.com/storybookjs/storybook/pull/10547)) + +### Bug Fixes + +- UI: Fix `viewMode` parameter handling ([#10292](https://github.com/storybookjs/storybook/pull/10292)) + +## 6.0.0-alpha.43 (April 24, 2020) + +### Features + +- Addon-a11y: Use channel to highlight elements in preview ([#10456](https://github.com/storybookjs/storybook/pull/10456)) +- Storyshots: Support react hooks ([#10529](https://github.com/storybookjs/storybook/pull/10529)) + +### Bug Fixes + +- Core: Transform for/of in dlls for IE11 compatibility ([#10471](https://github.com/storybookjs/storybook/pull/10471)) + +### Maintenance + +- Addon-contexts: Move to deprecated-addons repo ([#10479](https://github.com/storybookjs/storybook/pull/10479)) + +## 6.0.0-alpha.42 (April 23, 2020) + +### Bug Fixes + +- Build: Fix misc warnings that trip up Chromatic ([#10521](https://github.com/storybookjs/storybook/pull/10521)) +- Composition: Update UI for refs ([#10504](https://github.com/storybookjs/storybook/pull/10504)) + +### Maintenance + +- Addon-docs: Rename `formatSource` to `transformSource` ([#10503](https://github.com/storybookjs/storybook/pull/10503)) + +## 6.0.0-alpha.41 (April 21, 2020) + +### Features + +- Addon-docs: Reset styles in Preview component ([#10274](https://github.com/storybookjs/storybook/pull/10274)) + +### Bug Fixes + +- Addon-docs: Port Vue to ArgsTable ([#10482](https://github.com/storybookjs/storybook/pull/10482)) +- Addon-docs: Fix Props controls to point to primary story ([#10480](https://github.com/storybookjs/storybook/pull/10480)) +- Core: Fix addon tab in react-native-server ([#10468](https://github.com/storybookjs/storybook/pull/10468)) + +### Dependency Upgrades + +- Misc upgrades ([#10460](https://github.com/storybookjs/storybook/pull/10460)) + +## 6.0.0-alpha.40 (April 20, 2020) + +### Bug Fixes + +- Addon-docs: Fix controls column display logic ([#10473](https://github.com/storybookjs/storybook/pull/10473)) + +## 6.0.0-alpha.39 (April 18, 2020) + +### Breaking Changes + +- Addon-docs: Inline stories in Vue by default ([#10463](https://github.com/storybookjs/storybook/pull/10463)) + +### Features + +- Addon-docs: Provide better props include/exclude features ([#10464](https://github.com/storybookjs/storybook/pull/10464)) +- UI: Improve loading state ([#10444](https://github.com/storybookjs/storybook/pull/10444)) + +### Bug Fixes + +- UI: Fix bad shortcutpage layout ([#10445](https://github.com/storybookjs/storybook/pull/10445)) + +## 6.0.0-alpha.38 (April 18, 2020) + +Failed publish + +## 6.0.0-alpha.37 (April 17, 2020) + +### Features + +- Addon-actions: Make arg auto-generation more aggressive ([#10451](https://github.com/storybookjs/storybook/pull/10451)) + +### Maintenance + +- Examples: Format stringified parameters ([#10435](https://github.com/storybookjs/storybook/pull/10435)) + +### Dependency Upgrades + +- Bump recast from 0.16.2 to 0.19.0 ([#10415](https://github.com/storybookjs/storybook/pull/10415)) + +## 6.0.0-alpha.36 (April 16, 2020) + +### Bug Fixes + +- Server: Fix serialization of knobs params back to server ([#10391](https://github.com/storybookjs/storybook/pull/10391)) +- Core: Serve correctly hashed static files with the Cache-Control header ([#10390](https://github.com/storybookjs/storybook/pull/10390)) +- Addon-a11y: Fix default a11y parameters ([#10439](https://github.com/storybookjs/storybook/pull/10439)) +- Core: Fix event source handling ([#10416](https://github.com/storybookjs/storybook/pull/10416)) + +### Maintenance + +- Addon-docs: Add blocks typings ([#10441](https://github.com/storybookjs/storybook/pull/10441)) + +## 6.0.0-alpha.35 (April 16, 2020) + +### Bug Fixes + +- Core: Fix static build with DLL ([#10377](https://github.com/storybookjs/storybook/pull/10377)) +- Addon-Docs: Fix Args table generation for story with no component ([#10436](https://github.com/storybookjs/storybook/pull/10436)) + +### Maintenance + +- Yarn 2: Fix compatibility with `.storybook/preview.js` file ([#10342](https://github.com/storybookjs/storybook/pull/10342)) +- Official-storybook: Fix passArgsFirst problems ([#10432](https://github.com/storybookjs/storybook/pull/10432)) + +## 6.0.0-alpha.34 (April 15, 2020) + +### Breaking Changes + +- Addon-A11y: Remove decorator in favor of parameter configuration ([#10381](https://github.com/storybookjs/storybook/pull/10381)) + +### Features + +- Addon-docs: Add controls to ArgsTable ([#10354](https://github.com/storybookjs/storybook/pull/10354)) +- CLI: Reuse existing chromium tab if possible ([#10329](https://github.com/storybookjs/storybook/pull/10329)) + +### Bug Fixes + +- Core: Fix main.js `stories` regex to glob conversion ([#10400](https://github.com/storybookjs/storybook/pull/10400)) +- Composition: Fix ref getSourceType for URL paths with index.html ([#10421](https://github.com/storybookjs/storybook/pull/10421)) +- Core: Add .cjs files for main.js config ([#10358](https://github.com/storybookjs/storybook/pull/10358)) + +### Dependency Upgrades + +- Bump @types/react-select from 2.0.19 to 3.0.11 ([#10262](https://github.com/storybookjs/storybook/pull/10262)) +- Bump strip-json-comments from 3.0.1 to 3.1.0 ([#10334](https://github.com/storybookjs/storybook/pull/10334)) +- Bump axe version to 3.5.3 ([#10375](https://github.com/storybookjs/storybook/pull/10375)) +- Bump markdown-to-jsx from 6.11.0 to 6.11.1 ([#10331](https://github.com/storybookjs/storybook/pull/10331)) +- Bump semver from 7.1.3 to 7.2.2 ([#10385](https://github.com/storybookjs/storybook/pull/10385)) + +## 6.0.0-alpha.33 (April 14, 2020) + +### Breaking prerelease + +- Core: Rename ParameterEnhancer to ArgsEnhancer ([#10398](https://github.com/storybookjs/storybook/pull/10398)) + +### Bug Fixes + +- Core: Fix `webpackFinal` being called twice ([#10402](https://github.com/storybookjs/storybook/pull/10402)) +- Core: Fix legacy redirect ([#10404](https://github.com/storybookjs/storybook/pull/10404)) + +### Maintenance + +- CLI: Update fixtures used for CLI tests ([#10396](https://github.com/storybookjs/storybook/pull/10396)) +- Build: Update bootstrap to install optional deps on CI ([#10408](https://github.com/storybookjs/storybook/pull/10408)) +- Addon-docs: Format source at render time ([#10383](https://github.com/storybookjs/storybook/pull/10383)) + +## 6.0.0-alpha.32 (April 11, 2020) + +### Features + +- CSF: Warn when there are no exported stories ([#10357](https://github.com/storybookjs/storybook/pull/10357)) + +### Bug Fixes + +- Marko: Always destroy old component when switching stories ([#10345](https://github.com/storybookjs/storybook/pull/10345)) + +### Maintenance + +- Dev: Build script for package development ([#10343](https://github.com/storybookjs/storybook/pull/10343)) + +## 6.0.0-alpha.31 (April 7, 2020) + +### Bug Fixes + +- Core: Fix ie11 compatibility ([#10281](https://github.com/storybookjs/storybook/pull/10281)) +- Core: Add .cjs & .mjs to interpret-files ([#10288](https://github.com/storybookjs/storybook/pull/10288)) +- Core: Fix source-map strategy for production ([#10290](https://github.com/storybookjs/storybook/pull/10290)) +- Addon-knobs: Allow `text` and `number` to take undefined values ([#10101](https://github.com/storybookjs/storybook/pull/10101)) + +### Maintenance + +- Core: Warn about deprecated config files ([#10097](https://github.com/storybookjs/storybook/pull/10097)) +- Yarn 2: rework imports in webpack preview virtual module to fix compatibility ([#10305](https://github.com/storybookjs/storybook/pull/10305)) +- Addon-centered: Move to deprecated-addons ([#10300](https://github.com/storybookjs/storybook/pull/10300)) + +## 5.3.18 (March 31, 2020) + +### Bug Fixes + +- Core: Fix manager assets to be routed in express ([#9646](https://github.com/storybookjs/storybook/pull/9646)) +- Storyshots: Fix MDX transform ([#10223](https://github.com/storybookjs/storybook/pull/10223)) +- Addon-docs: Restore IE11 compat on Windows by transpiling acorn-jsx ([#9790](https://github.com/storybookjs/storybook/pull/9790)) +- Addon-docs: Ensure visibility of links within prop descriptions ([#10210](https://github.com/storybookjs/storybook/pull/10210)) + +## 6.0.0-alpha.30 (March 31, 2020) + +### Breaking Changes + +- Misc: remove deprecations for 6.0.0 ([#10216](https://github.com/storybookjs/storybook/pull/10216)) +- DocsPage: Remove slots for 6.0 ([#10259](https://github.com/storybookjs/storybook/pull/10259)) +- Addon-actions: Add preset and configure with parameters ([#9933](https://github.com/storybookjs/storybook/pull/9933)) + +### Features + +- MDX: Add args/argTypes/component/subcomponents support ([#10258](https://github.com/storybookjs/storybook/pull/10258)) +- Addon-docs: Add linear gradient support to ColorPalette block ([#10237](https://github.com/storybookjs/storybook/pull/10237)) + +### Bug Fixes + +- Addon-a11y: Performance fix ([#10219](https://github.com/storybookjs/storybook/pull/10219)) +- API: Fix local addon handling ([#10254](https://github.com/storybookjs/storybook/pull/10254)) +- Core: Fix URL load failure due to missing base ([#10228](https://github.com/storybookjs/storybook/pull/10228)) +- Storyshots: Fix MDX transform ([#10223](https://github.com/storybookjs/storybook/pull/10223)) + +### Maintenance + +- Build: Add puppeteer libs so teamcity can build examples ([#10235](https://github.com/storybookjs/storybook/pull/10235)) + +### Dependency Upgrades + +- Misc upgrades ([#10236](https://github.com/storybookjs/storybook/pull/10236)) + +## 6.0.0-alpha.29 (March 26, 2020) + +### Features + +- Core: Composition - load remote storybooks ([#9210](https://github.com/storybookjs/storybook/pull/9210)) +- CLI: extract-storybook bin ([#10146](https://github.com/storybookjs/storybook/pull/10146)) + +### Bug Fixes + +- Addon-docs: Ensure visibility of links within prop descriptions ([#10210](https://github.com/storybookjs/storybook/pull/10210)) + +### Maintenance + +- Core: Remove useStoryState ([#10187](https://github.com/storybookjs/storybook/pull/10187)) +- Addon-jest: Title case panel name ([#10161](https://github.com/storybookjs/storybook/pull/10161)) + +### Dependency Upgrades + +- Bump semver from 6.3.0 to 7.1.3 ([#9864](https://github.com/storybookjs/storybook/pull/9864)) +- Bump @types/jest from 25.1.3 to 25.1.4 ([#10133](https://github.com/storybookjs/storybook/pull/10133)) + +## 6.0.0-alpha.28 (March 23, 2020) + +### Features + +- UI: Form/textarea maxHeight : 400 ([#9860](https://github.com/storybookjs/storybook/pull/9860)) + +### Bug Fixes + +- Addon-docs: Make source resilient to bad story ID's ([#10184](https://github.com/storybookjs/storybook/pull/10184)) +- Core: Don't persist theme to localStorage ([#9076](https://github.com/storybookjs/storybook/pull/9076)) +- Core: Fix to load Storybook in IE11 ([#9942](https://github.com/storybookjs/storybook/pull/9942)) + +### Maintenance + +- Server: Simplify server addons ([#9931](https://github.com/storybookjs/storybook/pull/9931)) +- Core: FIX error of load order when using configure in preview|config.js ([#10159](https://github.com/storybookjs/storybook/pull/10159)) +- Build: Experiment to make CircleCI tests a faster and more stable ([#9969](https://github.com/storybookjs/storybook/pull/9969)) +- Vue: Fix webpack config when excute 'yarn workspace vue-example dev' ([#9704](https://github.com/storybookjs/storybook/pull/9704)) +- Core: Re-enable failing args tests ([#10126](https://github.com/storybookjs/storybook/pull/10126)) +- Build: Add script & parallelization for running chromatic on examples ([#10125](https://github.com/storybookjs/storybook/pull/10125)) + +### Dependency Upgrades + +- Addon-a11y: Move react to peer dependency ([#9957](https://github.com/storybookjs/storybook/pull/9957)) + +## 5.3.17 (March 14, 2020) + +### Bug Fixes + +- Components: Change react-syntax-highlighter from esm to cjs ([#9780](https://github.com/storybookjs/storybook/pull/9780)) + +## 5.3.16 (March 14, 2020) + +Failed NPM publish + +## 5.3.15 (March 14, 2020) + +### Bug Fixes + +- Core: Disables html-webpack-plugin's option to remove script tag types ([#10042](https://github.com/storybookjs/storybook/pull/10042)) +- Addon-actions: Style ActionLogger to preserve whitespace ([#10046](https://github.com/storybookjs/storybook/pull/10046)) + +### Maintenance + +- CI: Fix GitHub unit test workflow ([#9971](https://github.com/storybookjs/storybook/pull/9971)) + +### Dependency Upgrades + +- Security: Remove usage of a vulnerable version of serialize-javascript ([#10071](https://github.com/storybookjs/storybook/pull/10071)) + +## 6.0.0-alpha.27 (March 13, 2020) + +### Features + +- Addon-toolbars: Global args support in the toolbar ([#10028](https://github.com/storybookjs/storybook/pull/10028)) +- Addon-actions: Add Storybook Args support ([#10029](https://github.com/storybookjs/storybook/pull/10029)) +- Core: Add globalArgs/globalArgTypes `preview.js` exports ([#10123](https://github.com/storybookjs/storybook/pull/10123)) + +## 6.0.0-alpha.26 (March 12, 2020) + +### Breaking Changes + +- Remove deprecated decorators and loaders ([#9951](https://github.com/storybookjs/storybook/pull/9951)) + +### Features + +- Core: Improve support for main.ts/preview.ts files ([#10099](https://github.com/storybookjs/storybook/pull/10099)) +- Addon-docs: Theme with `docs.theme` parameter ([#10114](https://github.com/storybookjs/storybook/pull/10114)) +- Addon-docs: Svelte example ([#7673](https://github.com/storybookjs/storybook/pull/7673)) + +### Maintenance + +- CSF: Promote args/argTypes to first-class metadata ([#10117](https://github.com/storybookjs/storybook/pull/10117)) + +## 6.0.0-alpha.25 (March 11, 2020) + +NOTE: `6.0.0-alpha.24` broken due to bad merge. Sorry! + +### Bug Fixes + +- Core: Fix initialization of global args ([#10106](https://github.com/storybookjs/storybook/pull/10106)) + +## 6.0.0-alpha.24 (March 11, 2020) + +### Features + +- Addon-docs: formatSource snippet customization function ([#10089](https://github.com/storybookjs/storybook/pull/10089)) +- Core: Add global args feature ([#10015](https://github.com/storybookjs/storybook/pull/10015)) +- UI: Migrate from simplebar to overlaybars ([#9375](https://github.com/storybookjs/storybook/pull/9375)) + +### Bug Fixes + +- Core: Fix StoryInput parameters typings ([#10013](https://github.com/storybookjs/storybook/pull/10013)) +- Changed import of react-syntax-highlighter from esm to cjs ([#9292](https://github.com/storybookjs/storybook/pull/9292)) + +### Maintenance + +- Buidld: Setup TeamCity Cloud ([#9875](https://github.com/storybookjs/storybook/pull/9875)) +- Tech/improvements ([#10096](https://github.com/storybookjs/storybook/pull/10096)) +- Core: Move event handlers into module init ([#10085](https://github.com/storybookjs/storybook/pull/10085)) + +### Dependency Upgrades + +- Bump axe-core from 3.5.1 to 3.5.2 ([#10090](https://github.com/storybookjs/storybook/pull/10090)) + +## 6.0.0-alpha.23 (March 11, 2020) + +Failed publish + +## 6.0.0-alpha.22 (March 10, 2020) + +### Breaking Changes + +- MDX: Compile to improved source-loader format ([#10084](https://github.com/storybookjs/storybook/pull/10084)) + +### Features + +- Core: Add args feature ([#10014](https://github.com/storybookjs/storybook/pull/10014)) + +### Maintenance + +- Tech/improvements ([#10083](https://github.com/storybookjs/storybook/pull/10083)) +- Few minor improvements extracted from the inception feature PR ([#10072](https://github.com/storybookjs/storybook/pull/10072)) +- Tech/improvements ([#10070](https://github.com/storybookjs/storybook/pull/10070)) + +### Dependency Upgrades + +- Yarn 2: Add missing dependencies ([#10012](https://github.com/storybookjs/storybook/pull/10012)) +- Security: Remove usage of a vulnerable version of serialize-javascript ([#10071](https://github.com/storybookjs/storybook/pull/10071)) + +## 6.0.0-alpha.21 (March 5, 2020) + +### Breaking Changes + +- Core: Overhaul start.js and event emitting/listening ([#9914](https://github.com/storybookjs/storybook/pull/9914)) + +### Features + +- CLI: Support js / jsx / ts / tsx stories in React CSF template ([#10003](https://github.com/storybookjs/storybook/pull/10003)) +- Cra-kitchen-sink : Add Named Color Palette Example(MDX) ([#9709](https://github.com/storybookjs/storybook/pull/9709)) +- Addon-Queryparams: Add addon preset ([#9949](https://github.com/storybookjs/storybook/pull/9949)) + +### Bug Fixes + +- Addon-actions: Style ActionLogger to preserve whitespace ([#10046](https://github.com/storybookjs/storybook/pull/10046)) +- Core: Disables html-webpack-plugin's option to remove script tag types ([#10042](https://github.com/storybookjs/storybook/pull/10042)) + +### Maintenance + +- Tech: Misc improvements ([#10052](https://github.com/storybookjs/storybook/pull/10052)) +- Tech: Misc improvements extracted from composition ([#10040](https://github.com/storybookjs/storybook/pull/10040)) +- CI: change parallelism ([#10041](https://github.com/storybookjs/storybook/pull/10041)) +- Storybook-official: try moving options to `manager.js` ([#9323](https://github.com/storybookjs/storybook/pull/9323)) +- Misc: Add missing dependencies ([#9965](https://github.com/storybookjs/storybook/pull/9965)) +- CI: Fix GitHub unit test workflow ([#9971](https://github.com/storybookjs/storybook/pull/9971)) + +### Dependency Upgrades + +- Upgrade reach router ([#10016](https://github.com/storybookjs/storybook/pull/10016)) + +## 6.0.0-alpha.20 (February 27, 2020) + +### Bug Fixes + +- Core: Fix `configFilename` containing backticks ([#9960](https://github.com/storybookjs/storybook/pull/9960)) + +### Maintenance + +- Core: Add stories to demonstrate `layout` ([#9940](https://github.com/storybookjs/storybook/pull/9940)) + +## 5.3.14 (February 25, 2020) + +### Bug Fixes + +- Centered: remove `typesVersions` attribute ([#9907](https://github.com/storybookjs/storybook/pull/9907)) +- Props: Fix typescript unspecified default value ([#9873](https://github.com/storybookjs/storybook/pull/9873)) +- Core: Use telejson for websockets channel ([#9867](https://github.com/storybookjs/storybook/pull/9867)) +- Storyshots: Fix support for jsx/tsx config files ([#9834](https://github.com/storybookjs/storybook/pull/9834)) +- MDX: Fix custom classes getting stripped ([#8897](https://github.com/storybookjs/storybook/pull/8897)) +- Typescript: Add downlevel dts for 3.5 ([#9847](https://github.com/storybookjs/storybook/pull/9847)) + +## 6.0.0-alpha.19 (February 24, 2020) + +### Features + +- Addon-links: Add preset ([#9932](https://github.com/storybookjs/storybook/pull/9932)) + +### Bug Fixes + +- Addon-docs: Restore IE11 compat on Windows by transpiling acorn-jsx ([#9790](https://github.com/storybookjs/storybook/pull/9790)) + +## 6.0.0-alpha.18 (February 22, 2020) + +### Features + +- Addon-knobs: Add `disableForceUpdate` option ([#9447](https://github.com/storybookjs/storybook/pull/9447)) + +## 6.0.0-alpha.17 (February 21, 2020) + +### Bug Fixes + +- Props: Fix typescript unspecified default value ([#9873](https://github.com/storybookjs/storybook/pull/9873)) +- Centered: remove `typesVersions` attribute ([#9907](https://github.com/storybookjs/storybook/pull/9907)) + +### Maintenance + +- Misc: Add missing dependencies or peerDependencies ([#9916](https://github.com/storybookjs/storybook/pull/9916)) + +## 6.0.0-alpha.16 (February 21, 2020) + +Failed NPM publish + +## 6.0.0-alpha.15 (February 20, 2020) + +### Breaking Changes + +- Refactor Client API: pull metadata handling code into the store. ([#9877](https://github.com/storybookjs/storybook/pull/9877)) + +### Features + +- Core: Add skip dispose option to ClientApi ([#9868](https://github.com/storybookjs/storybook/pull/9868)) + +## 6.0.0-alpha.14 (February 19, 2020) + +### Features + +- CLI: Add Yarn 2 compatibility ([#9866](https://github.com/storybookjs/storybook/pull/9866)) + +### Bug Fixes + +- Typescript: Add downlevel dts for TS3.5 support ([#9902](https://github.com/storybookjs/storybook/pull/9902)) +- CLI: capture unknown arguments the native way ([#9888](https://github.com/storybookjs/storybook/pull/9888)) +- Core: Use telejson for websockets channel ([#9867](https://github.com/storybookjs/storybook/pull/9867)) + +### Maintenance + +- Build: Upgrade to latest version of eslint config ([#9882](https://github.com/storybookjs/storybook/pull/9882)) +- Typescript: Misc improvements ([#9879](https://github.com/storybookjs/storybook/pull/9879)) +- Misc: Project root cleanup ([#9880](https://github.com/storybookjs/storybook/pull/9880)) + +### Dependency Upgrades + +- Bump webpack-cli from 3.3.10 to 3.3.11 ([#9895](https://github.com/storybookjs/storybook/pull/9895)) +- Migrate to leven ([#9881](https://github.com/storybookjs/storybook/pull/9881)) + +## 6.0.0-alpha.13 (February 15, 2020) + +### Bug Fixes + +- CLI: fix React Scripts csf-ts story templates ([#9863](https://github.com/storybookjs/storybook/pull/9863)) +- Addon-viewports: Fix Galaxy S9's viewport size ([#9797](https://github.com/storybookjs/storybook/pull/9797)) +- Storyshots: Fix support for jsx/tsx config files ([#9834](https://github.com/storybookjs/storybook/pull/9834)) + +### Maintenance + +- Addon-docs: Snapshot testing and bug reporting for props tables ([#9838](https://github.com/storybookjs/storybook/pull/9838)) +- Typescript: Remove prop types in lib/components ([#9747](https://github.com/storybookjs/storybook/pull/9747)) +- Typescript: Better api consumer ([#9861](https://github.com/storybookjs/storybook/pull/9861)) + +### Dependency Upgrades + +- Bump marko from 4.18.42 to 4.18.45 ([#9839](https://github.com/storybookjs/storybook/pull/9839)) + +## 6.0.0-alpha.12 (February 14, 2020) + +### Maintenance + +- Typescript: Improve @storybook/ui types ([#9820](https://github.com/storybookjs/storybook/pull/9820)) +- Misc: Fix deepscan issues ([#9843](https://github.com/storybookjs/storybook/pull/9843)) ([#9842](https://github.com/storybookjs/storybook/pull/9842)) + +## 6.0.0-alpha.11 (February 13, 2020) + +### Breaking Changes + +- Core: Remove legacy data from Story Store ([#9810](https://github.com/storybookjs/storybook/pull/9810)) + +### Bug Fixes + +- Addon-docs: Preserve HTML element classes in MDX ([#8897](https://github.com/storybookjs/storybook/pull/8897)) + +### Maintenance + +- CLI: transpile `@storybook/cli` to CJS ([#9807](https://github.com/storybookjs/storybook/pull/9807)) + +## 5.3.13 (February 12, 2020) + +### Bug Fixes + +- React: Remove `MiniCssExtractPlugin` for CRA ([#9759](https://github.com/storybookjs/storybook/pull/9759)) + +### Maintenance + +- Build: Fix DLL generation race condition ([#9770](https://github.com/storybookjs/storybook/pull/9770)) + +## 6.0.0-alpha.10 (February 11, 2020) + +### Maintenance + +- Typescript: Migrate @storybook/ui ([#9791](https://github.com/storybookjs/storybook/pull/9791)) + +## 6.0.0-alpha.9 (February 9, 2020) + +### Features + +- Addon-docs: Add preset options for vue-docgen-api ([#9699](https://github.com/storybookjs/storybook/pull/9699)) +- UI: Add initialActive option parameter ([#9141](https://github.com/storybookjs/storybook/pull/9141)) + +### Bug Fixes + +- Components: Import react-syntax-highlighter/create-element from cjs ([#9795](https://github.com/storybookjs/storybook/pull/9795)) + +### Maintenance + +- Examples: Change main.js to main.ts to show it's possible ([#9775](https://github.com/storybookjs/storybook/pull/9775)) + +## 6.0.0-alpha.8 (February 8, 2020) + +### Maintenance + +- Replace lodash named imports with specific imports ([#9787](https://github.com/storybookjs/storybook/pull/9787)) + +## 6.0.0-alpha.7 (February 7, 2020) + +### Bug Fixes + +- Core: Support custom addons using JSX ([#9648](https://github.com/storybookjs/storybook/pull/9648)) +- Components: Change react-syntax-highlighter from esm to cjs ([#9780](https://github.com/storybookjs/storybook/pull/9780)) +- Core: Fix manager assets to be routed in express ([#9646](https://github.com/storybookjs/storybook/pull/9646)) + +### Maintenance + +- Examples: Remove addon-notes remnants ([#9782](https://github.com/storybookjs/storybook/pull/9782)) +- Build: Fix DLL generation race condition ([#9770](https://github.com/storybookjs/storybook/pull/9770)) + +## 6.0.0-alpha.6 (February 5, 2020) + +### Bug Fixes + +- Core: Fix dev server error - back out bad change ([#9753](https://github.com/storybookjs/storybook/pull/9753)) +- CLI: Fix file path for the Button story ([#9325](https://github.com/storybookjs/storybook/pull/9325)) + +## 5.3.12 (February 5, 2020) + +### Bug Fixes + +- Core: Fix dev server error - back out bad change ([#9753](https://github.com/storybookjs/storybook/pull/9753)) + +## 5.3.11 (February 4, 2020) + +### Bug Fixes + +- Svelte: Fix Svelte 3 slots for decorators ([#9724](https://github.com/storybookjs/storybook/pull/9724)) +- CLI: Fix file path for Button story ([#9325](https://github.com/storybookjs/storybook/pull/9325)) +- Angular: Emit decorator metadata by default ([#9701](https://github.com/storybookjs/storybook/pull/9701)) +- Storyshots: Fix config via main.ts ([#9577](https://github.com/storybookjs/storybook/pull/9577)) + +## 6.0.0-alpha.5 (February 4, 2020) + +### Features + +- Core: Add Yarn 2 compatibility ([#9667](https://github.com/storybookjs/storybook/pull/9667)) +- Addon-a11y: Add preset ([#9697](https://github.com/storybookjs/storybook/pull/9697)) +- Server: Initial support for @storybook/server ([#9722](https://github.com/storybookjs/storybook/pull/9722)) + +### Bug Fixes + +- Svelte: Fix Svelte 3 slots for decorators ([#9724](https://github.com/storybookjs/storybook/pull/9724)) + +### Maintenance + +- Cra-ts-kitchen-sink: Fix stories glob pattern ([#9706](https://github.com/storybookjs/storybook/pull/9706)) + +## 6.0.0-alpha.4 (February 3, 2020) + +### Bug Fixes + +- Angular: Emit decorator metadata by default ([#9701](https://github.com/storybookjs/storybook/pull/9701)) +- Addon-centered: Fix clash with addon-docs for react ([#8388](https://github.com/storybookjs/storybook/pull/8388)) + +### Maintenance + +- Add angular 8 and 9 cli fixtures ([#8769](https://github.com/storybookjs/storybook/pull/8769)) + +### Dependency Upgrades + +- Misc upgrades ([#9688](https://github.com/storybookjs/storybook/pull/9688)) + +## 5.3.10 (February 2, 2020) + +### Bug Fixes + +- Core: Upgrade `min-css-extract-plugin` to fix SASS loading ([#9652](https://github.com/storybookjs/storybook/pull/9652)) +- CRA: Fix jsconfig support ([#9324](https://github.com/storybookjs/storybook/pull/9324)) +- Web-components: Fix default value for docs prop table ([#9655](https://github.com/storybookjs/storybook/pull/9655)) +- Web-components: Fix types to play nicely with lit-element ([#9557](https://github.com/storybookjs/storybook/pull/9557)) +- UI: Add support for className prop on Form.Field ([#9665](https://github.com/storybookjs/storybook/pull/9665)) +- Addon-storyshots: Remove excess slashes from jest transform warning ([#9616](https://github.com/storybookjs/storybook/pull/9616)) + +### Maintenance + +- Ember: Migrate to new "import { hbs } from 'ember-cli-htmlbars'" ([#9633](https://github.com/storybookjs/storybook/pull/9633)) +- Build: Netlify for examples again ([#9585](https://github.com/storybookjs/storybook/pull/9585)) +- Publish: Remove docs to reduce package size ([#9612](https://github.com/storybookjs/storybook/pull/9612)) + +## 6.0.0-alpha.3 (February 2, 2020) + +### Bug Fixes + +- CRA: Fix jsconfig support ([#9324](https://github.com/storybookjs/storybook/pull/9324)) +- UI: Check if docsOnly is set to hide the addon panels ([#9687](https://github.com/storybookjs/storybook/pull/9687)) + +### Maintenance + +- Addon-notes, addon-info: Move to deprecated-addons repo ([#9673](https://github.com/storybookjs/storybook/pull/9673)) + +## 6.0.0-alpha.2 (January 30, 2020) + +### Features + +- UI: Configure tabs title, visibility, order and disable ([#9095](https://github.com/storybookjs/storybook/pull/9095)) +- Addon-cssresources: Add hideCode option ([#9627](https://github.com/storybookjs/storybook/pull/9627)) +- UI: Add `viewMode` parameter to control story nav UI ([#9090](https://github.com/storybookjs/storybook/pull/9090)) + +### Bug Fixes + +- Web-components: Fix default value for prop table docs ([#9655](https://github.com/storybookjs/storybook/pull/9655)) +- Web-components: Make TypeScript types play nicely with lit-element ([#9557](https://github.com/storybookjs/storybook/pull/9557)) +- UI: Fix tabs to scroll horizontally ([#9383](https://github.com/storybookjs/storybook/pull/9383)) +- UI: Add support for className prop on Form.Field ([#9665](https://github.com/storybookjs/storybook/pull/9665)) +- Core: Upgrade `min-css-extract-plugin` to fix SASS loading ([#9652](https://github.com/storybookjs/storybook/pull/9652)) +- Adon-docs: Fix ColorPalette styling ([#9643](https://github.com/storybookjs/storybook/pull/9643)) +- Addon-storyshots: Remove excess slashes from jest transform warning ([#9616](https://github.com/storybookjs/storybook/pull/9616)) + +### Maintenance + +- Source-loader: Overhaul to remove decorators, support user-configurable source ([#9547](https://github.com/storybookjs/storybook/pull/9547)) +- Build: Use Netlify for examples again ([#9585](https://github.com/storybookjs/storybook/pull/9585)) +- Ember: Migrate to new "import { hbs } from 'ember-cli-htmlbars'" ([#9633](https://github.com/storybookjs/storybook/pull/9633)) +- Publish: Remove docs to reduce package size ([#9612](https://github.com/storybookjs/storybook/pull/9612)) + +## 5.3.9 (January 24, 2020) + +### Bug Fixes + +- Addon-docs: Revert breaking source indentation fix ([#9609](https://github.com/storybookjs/storybook/pull/9609)) + +## 6.0.0-alpha.1 (January 23, 2020) + +### Features + +- Core: Enable HMR logging in browser console ([#9535](https://github.com/storybookjs/storybook/pull/9535)) + +### Bug Fixes + +- Addon-knobs: Fix broken link to repo in empty panel ([#9530](https://github.com/storybookjs/storybook/pull/9530)) +- Typescript: Export IStory in `@storybook/angular` ([#9097](https://github.com/storybookjs/storybook/pull/9097)) + +### Maintenance + +- React-native: Extract to its own repo ([#9599](https://github.com/storybookjs/storybook/pull/9599)) +- Polymer: Extract to its own repo ([#9596](https://github.com/storybookjs/storybook/pull/9596)) +- Build: Fix some dependencies & ts problems ([#9603](https://github.com/storybookjs/storybook/pull/9603)) + +## 5.3.8 (January 22, 2020) + +### Bug Fixes + +- Addon-docs: Fix TS false default value in prop table ([#9560](https://github.com/storybookjs/storybook/pull/9560)) +- Addon-knobs: Fix broken repo link in empty panel ([#9530](https://github.com/storybookjs/storybook/pull/9530)) +- Typescript: Export IStory in `@storybook/angular` ([#9097](https://github.com/storybookjs/storybook/pull/9097)) +- Fixed Angular button example story ([#9540](https://github.com/storybookjs/storybook/pull/9540)) +- Clean usage of `@types/webpack-env` dep in all packages ([#9536](https://github.com/storybookjs/storybook/pull/9536)) + +## 6.0.0-alpha.0 (January 21, 2020) + +### Features + +- API: Add useSharedState, useStoryState ([#9566](https://github.com/storybookjs/storybook/pull/9566)) +- Addon-docs: Named colors with ColorPalette ([#9453](https://github.com/storybookjs/storybook/pull/9453)) +- Core: Add preview layouts ([#9229](https://github.com/storybookjs/storybook/pull/9229)) +- Marionette: Add marionette support ([#7981](https://github.com/storybookjs/storybook/pull/7981)) +- Addon-a11y: Support manual run ([#8883](https://github.com/storybookjs/storybook/pull/8883)) +- Addon-cssresources: Disable SyntaxHighlighter for long code ([#9360](https://github.com/storybookjs/storybook/pull/9360)) +- Core: Improve monorepo support ([#8822](https://github.com/storybookjs/storybook/pull/8822)) + +### Bug Fixes + +- Addon-docs: Fix TS false default value in prop table ([#9560](https://github.com/storybookjs/storybook/pull/9560)) +- Addon-docs: Remove hard-coded lineHeight in Typeset block ([#9567](https://github.com/storybookjs/storybook/pull/9567)) +- Fixed Angular button example story ([#9540](https://github.com/storybookjs/storybook/pull/9540)) +- Core: Fix generated entry to import at top of file ([#9398](https://github.com/storybookjs/storybook/pull/9398)) +- Preact: Fix story function typescript type ([#9123](https://github.com/storybookjs/storybook/pull/9123)) +- UI: Make canvas link a link ([#9257](https://github.com/storybookjs/storybook/pull/9257)) + +### Maintenance + +- Build: the build-storybooks script ([#9569](https://github.com/storybookjs/storybook/pull/9569)) +- CLI: Improve Rax template ([#9574](https://github.com/storybookjs/storybook/pull/9574)) +- Typescript: Migrate polymer ([#9565](https://github.com/storybookjs/storybook/pull/9565)) +- Typescript: Migrate ember ([#9020](https://github.com/storybookjs/storybook/pull/9020)) +- Next 6.0.0 ([#9212](https://github.com/storybookjs/storybook/pull/9212)) +- REMOVE subscription_store ([#9228](https://github.com/storybookjs/storybook/pull/9228)) + +### Dependency Upgrades + +- Update husky to v4 ([#9509](https://github.com/storybookjs/storybook/pull/9509)) +- Bumped react-dev-utils dependency to v10. ([#9579](https://github.com/storybookjs/storybook/pull/9579)) +- Bump babel-plugin-macros from 2.7.1 to 2.8.0 ([#9236](https://github.com/storybookjs/storybook/pull/9236)) +- Bump babel-plugin-emotion from 10.0.23 to 10.0.27 ([#9239](https://github.com/storybookjs/storybook/pull/9239)) +- Bump @babel/runtime from 7.7.4 to 7.7.7 ([#9277](https://github.com/storybookjs/storybook/pull/9277)) +- Bump corejs-upgrade-webpack-plugin from 2.2.0 to 3.0.1 ([#9427](https://github.com/storybookjs/storybook/pull/9427)) +- Bump terser-webpack-plugin from 2.2.1 to 2.3.2 ([#9386](https://github.com/storybookjs/storybook/pull/9386)) + +## 5.3.7 (January 20, 2020) + +### Bug Fixes + +- Node-logger: Move `@types/npmlog` to dependencies ([#9538](https://github.com/storybookjs/storybook/pull/9538)) +- Core: Fix legacy story URLs ([#9545](https://github.com/storybookjs/storybook/pull/9545)) +- Addon-docs: Convert default prop value to string ([#9525](https://github.com/storybookjs/storybook/pull/9525)) +- Addon-docs: Preserve Source indentation by default ([#9513](https://github.com/storybookjs/storybook/pull/9513)) + +## 5.3.6 (January 17, 2020) + +### Bug Fixes + +- Source-loader: Bypass if file has no exports ([#9505](https://github.com/storybookjs/storybook/pull/9505)) +- Core: Fix default sorting of docs-only stories ([#9504](https://github.com/storybookjs/storybook/pull/9504)) + +## 5.3.5 (January 17, 2020) + +### Bug Fixes + +- Core: Fix typo for loading addon-notes/register-panel ([#9497](https://github.com/storybookjs/storybook/pull/9497)) +- Source-loader: Add imports to top of file ([#9492](https://github.com/storybookjs/storybook/pull/9492)) + +## 5.3.4 (January 16, 2020) + +### Bug Fixes + +- Core: Fix presets register panel ([#9486](https://github.com/storybookjs/storybook/pull/9486)) +- Core: Fix addon/preset detection for local addons ([#9485](https://github.com/storybookjs/storybook/pull/9485)) +- Core: Fix default story sort ([#9482](https://github.com/storybookjs/storybook/pull/9482)) + +## 5.3.3 (January 14, 2020) + +### Bug Fixes + +- UI: Fix edge case where only one legacy separator is defined ([#9425](https://github.com/storybookjs/storybook/pull/9425)) +- Core: Preserve kind load order on HMR when no sortFn is provided ([#9424](https://github.com/storybookjs/storybook/pull/9424)) +- Angular: Fix missing architect properties ([#9390](https://github.com/storybookjs/storybook/pull/9390)) +- Addon-knobs: Fix null knob values in select ([#9416](https://github.com/storybookjs/storybook/pull/9416)) +- Source-loader: Disable linting altogether ([#9417](https://github.com/storybookjs/storybook/pull/9417)) + +## 5.3.2 (January 13, 2020) + +### Bug Fixes + +- Source-loader: Disable eslint entirely for generated code ([#9410](https://github.com/storybookjs/storybook/pull/9410)) + +## 5.3.1 (January 12, 2020) + +### Bug Fixes + +- Core: Fix generated entry to import at top of file ([#9398](https://github.com/storybookjs/storybook/pull/9398)) + +## 5.3.0 (January 11, 2020) + +Storybook 5.3 is here! + +- 📝 [Custom documentation in MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) +- 🎨 [Multi-framework SB Docs (React, Vue, Angular, WC, Ember)](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) +- 📦 [Web-components framework support](https://dev.to/open-wc/storybook-for-web-components-on-steroids-4h29) +- 🔼 [Main.js declarative configuration](https://medium.com/storybookjs/declarative-storybook-configuration-49912f77b78) + + 5.3 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.3.0-alpha.*`, `5.3.0-beta.*`, and `5.3.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier. + +## 5.3.0-rc.14 (January 11, 2020) + +- Merge `master` into `next` for 5.3.0 release ([#9388](https://github.com/storybookjs/storybook/pull/9388)) + +## 5.3.0-rc.13 (January 11, 2020) + +### Bug Fixes + +- Addon-docs: Fix link CORS errors using channel navigate event ([#9381](https://github.com/storybookjs/storybook/pull/9381)) +- CLI: Fix `sb init` to use spawn.sync if creating package.json ([#9359](https://github.com/storybookjs/storybook/pull/9359)) + +### Maintenance + +- Official-storybook: Prop table example for multiple named exports ([#9364](https://github.com/storybookjs/storybook/pull/9364)) +- Addon-docs / web-components: Rename 'props' to 'properties' in props table ([#9362](https://github.com/storybookjs/storybook/pull/9362)) + +### Dependency Upgrades + +- Upgrade @types/webpack-env and @types/node to fix conflicting types ([#9365](https://github.com/storybookjs/storybook/pull/9365)) + +## 5.3.0-rc.12 (January 8, 2020) + +### Bug Fixes + +- Nav UI: Nodes are components only if they contain ALL leaf nodes ([#9356](https://github.com/storybookjs/storybook/pull/9356)) +- Core: Fix HMR for global decorators in main.js config ([#9354](https://github.com/storybookjs/storybook/pull/9354)) +- Presets: Fix register.js addons entry ([#9347](https://github.com/storybookjs/storybook/pull/9347)) +- React: Check CRA is installed before showing warning ([#9346](https://github.com/storybookjs/storybook/pull/9346)) + +## 5.3.0-rc.11 (January 7, 2020) + +### Bug Fixes + +- Addon-Docs: Handle leaf/non-leaf mixture in docs-mode navigation ([#9321](https://github.com/storybookjs/storybook/pull/9321)) + +### Dependency Upgrades + +- Axe storyshots: move to original @wordpress/jest-puppeteer-axe package ([#9337](https://github.com/storybookjs/storybook/pull/9337)) + +## 5.3.0-rc.10 (January 6, 2020) + +### Bug Fixes + +- Revert "Source-loader: Disable no-implicit-any linting" ([#9333](https://github.com/storybookjs/storybook/pull/9333)) +- Addon-docs: Fix scroll behavior on page navigation ([#9331](https://github.com/storybookjs/storybook/pull/9331)) + +## 5.3.0-rc.9 (January 4, 2020) + +### Features + +- CSF: Use `__namedExportsOrder` array in loader if provided ([#9315](https://github.com/storybookjs/storybook/pull/9315)) + +### Bug Fixes + +- Router: Add storyNameFromExport to avoid breaking changes ([#9320](https://github.com/storybookjs/storybook/pull/9320)) + +## 5.3.0-rc.8 (January 3, 2020) + +### Bug Fixes + +- Addon-docs: Tweak props table paragraph spacing ([#9307](https://github.com/storybookjs/storybook/pull/9307)) + +### Maintenance + +- Add minimal typescript component to official-storybook ([#9308](https://github.com/storybookjs/storybook/pull/9308)) + +### Dependency Upgrades + +- React: Upgrade babel-plugin-react-docgen to 4.0.0 ([#9303](https://github.com/storybookjs/storybook/pull/9303)) + +## 5.3.0-rc.7 (January 2, 2020) + +### Bug Fixes + +- Core: Fix babel.js to disable simplify ([#9280](https://github.com/storybookjs/storybook/pull/9280)) +- Storyshots-Puppeteer: Don't infer story ID from its name ([#9291](https://github.com/storybookjs/storybook/pull/9291)) + +## 5.3.0-rc.6 (December 31, 2019) + +This is significant change to `main.js` aka tri-config, dramatically simplifying how addons and presets are registered. See the maintenannce PR for details. + +### Maintenance + +- Main.js: Combine presets/registers in `addons` field ([#9246](https://github.com/storybookjs/storybook/pull/9246)) + +## 5.3.0-rc.5 (December 31, 2019) + +### Bug Fixes + +- Addon-docs: Hide stories block when there are no stories ([#9271](https://github.com/storybookjs/storybook/pull/9271)) +- Source-loader: Disable no-implicit-any linting ([#9272](https://github.com/storybookjs/storybook/pull/9272)) + +## 5.3.0-rc.4 (December 28, 2019) + +### Bug Fixes + +- Addon-docs: Fix MDX story rendering with dynamic component titles ([#9248](https://github.com/storybookjs/storybook/pull/9248)) + +### Maintenance + +- Ignore testfixtures directory in storybook publish ([#9244](https://github.com/storybookjs/storybook/pull/9244)) + +## 5.3.0-rc.3 (December 26, 2019) + +### Bug Fixes + +- Addon-docs: Include ember files in addon-docs publish ([#9230](https://github.com/storybookjs/storybook/pull/9230)) + +### Maintenance + +- Standalone CSF example ([#9223](https://github.com/storybookjs/storybook/pull/9223)) + +### Dependency Upgrades + +- Addon-info: Upgrade marksy for security ([#9234](https://github.com/storybookjs/storybook/pull/9234)) + +## 5.3.0-rc.2 (December 26, 2019) + +Failed NPM publish + +## 5.3.0-rc.1 (December 23, 2019) + +### Bug Fixes + +- Angular: Add default value to the budgets property ([#9207](https://github.com/storybookjs/storybook/pull/9207)) +- DocsPage: Fix title with new path separator scheme ([#9204](https://github.com/storybookjs/storybook/pull/9204)) + +### Maintenance + +- CLI: Make template `stories` glob more permissive ([#9224](https://github.com/storybookjs/storybook/pull/9224)) + ## 5.3.0-rc.0 (December 19, 2019) ### Features -* CSF: Use `__orderedExports` in loader if provided ([#9181](https://github.com/storybookjs/storybook/pull/9181)) +- CSF: Use `__orderedExports` in loader if provided ([#9181](https://github.com/storybookjs/storybook/pull/9181)) ### Bug Fixes -* Addon-a11y: Fix selected blindness color filter ([#9179](https://github.com/storybookjs/storybook/pull/9179)) +- Addon-a11y: Fix selected blindness color filter ([#9179](https://github.com/storybookjs/storybook/pull/9179)) ### Maintenance -* Addon-essentials: Remove actions, links, knobs ([#9184](https://github.com/storybookjs/storybook/pull/9184)) +- Addon-essentials: Remove actions, links, knobs ([#9184](https://github.com/storybookjs/storybook/pull/9184)) ## 5.3.0-beta.31 (December 16, 2019) ### Features -* React: Add support for CRA without overrides ([#9157](https://github.com/storybookjs/storybook/pull/9157)) -* Addon-docs: Add fontFamily prop to Typeset component ([#9158](https://github.com/storybookjs/storybook/pull/9158)) +- React: Add support for CRA without overrides ([#9157](https://github.com/storybookjs/storybook/pull/9157)) +- Addon-docs: Add fontFamily prop to Typeset component ([#9158](https://github.com/storybookjs/storybook/pull/9158)) ### Bug Fixes -* Core: Emit store render event synchronously if we can ([#9087](https://github.com/storybookjs/storybook/pull/9087)) +- Core: Emit store render event synchronously if we can ([#9087](https://github.com/storybookjs/storybook/pull/9087)) ## 5.3.0-beta.30 (December 16, 2019) @@ -47,11 +1345,11 @@ Failed NPM publish ### Bug Fixes -* Addon-docs: Hide addons on docs-only stories ([#9125](https://github.com/storybookjs/storybook/pull/9125)) +- Addon-docs: Hide addons on docs-only stories ([#9125](https://github.com/storybookjs/storybook/pull/9125)) ### Dependency Upgrades -* Upgrade vue-docgen-loader to 1.3.0-beta.0 ([#9155](https://github.com/storybookjs/storybook/pull/9155)) +- Upgrade vue-docgen-loader to 1.3.0-beta.0 ([#9155](https://github.com/storybookjs/storybook/pull/9155)) ## 5.3.0-beta.24 (December 15, 2019) @@ -61,82 +1359,82 @@ Failed NPM publish ### Features -* Addon-docs: Render components as leaves in `--docs` mode ([#7700](https://github.com/storybookjs/storybook/pull/7700)) +- Addon-docs: Render components as leaves in `--docs` mode ([#7700](https://github.com/storybookjs/storybook/pull/7700)) ### Bug Fixes -* Addon-viewport: Allow viewports config to be optional ([#9137](https://github.com/storybookjs/storybook/pull/9137)) +- Addon-viewport: Allow viewports config to be optional ([#9137](https://github.com/storybookjs/storybook/pull/9137)) ## 5.3.0-beta.22 (December 12, 2019) ### Bug Fixes -* React: Fix CRA preset check ([#9142](https://github.com/storybookjs/storybook/pull/9142)) +- React: Fix CRA preset check ([#9142](https://github.com/storybookjs/storybook/pull/9142)) ### Maintenance -* Build: Change CI to chromatic on all examples ([#9114](https://github.com/storybookjs/storybook/pull/9114)) -* Web-components: Clean up example `custom-elements.json` and expose `defaultValue` ([#9107](https://github.com/storybookjs/storybook/pull/9107)) +- Build: Change CI to chromatic on all examples ([#9114](https://github.com/storybookjs/storybook/pull/9114)) +- Web-components: Clean up example `custom-elements.json` and expose `defaultValue` ([#9107](https://github.com/storybookjs/storybook/pull/9107)) ### Dependency Upgrades -* Restore main jscodeshift package ([#9140](https://github.com/storybookjs/storybook/pull/9140)) +- Restore main jscodeshift package ([#9140](https://github.com/storybookjs/storybook/pull/9140)) ## 5.3.0-beta.21 (December 11, 2019) ### Features -* CLI: Add Yarn workspaces support for init command ([#9104](https://github.com/storybookjs/storybook/pull/9104)) +- CLI: Add Yarn workspaces support for init command ([#9104](https://github.com/storybookjs/storybook/pull/9104)) ### Bug Fixes -* Addon-docs: Update MDX compiler to fix knobs ([#9118](https://github.com/storybookjs/storybook/pull/9118)) -* CLI: Add web-components to sb init ([#9106](https://github.com/storybookjs/storybook/pull/9106)) +- Addon-docs: Update MDX compiler to fix knobs ([#9118](https://github.com/storybookjs/storybook/pull/9118)) +- CLI: Add web-components to sb init ([#9106](https://github.com/storybookjs/storybook/pull/9106)) ### Maintenance -* UI: Remove css usage ([#9003](https://github.com/storybookjs/storybook/pull/9003)) +- UI: Remove css usage ([#9003](https://github.com/storybookjs/storybook/pull/9003)) ## 5.3.0-beta.20 (December 9, 2019) ### Features -* Addon-essentials: Remove docs from essentials ([#9093](https://github.com/storybookjs/storybook/pull/9093)) +- Addon-essentials: Remove docs from essentials ([#9093](https://github.com/storybookjs/storybook/pull/9093)) ### Bug Fixes -* Source-loader: Handle includeStories/excludeStories in CSF ([#9100](https://github.com/storybookjs/storybook/pull/9100)) -* Source-loader: Support function declaration story exports ([#9092](https://github.com/storybookjs/storybook/pull/9092)) +- Source-loader: Handle includeStories/excludeStories in CSF ([#9100](https://github.com/storybookjs/storybook/pull/9100)) +- Source-loader: Support function declaration story exports ([#9092](https://github.com/storybookjs/storybook/pull/9092)) ### Maintenance -* CSF: Refactor router utils into CSF library ([#9099](https://github.com/storybookjs/storybook/pull/9099)) +- CSF: Refactor router utils into CSF library ([#9099](https://github.com/storybookjs/storybook/pull/9099)) ## 5.3.0-beta.19 (December 7, 2019) ### Features -* Addon-essentials ([#9019](https://github.com/storybookjs/storybook/pull/9019)) +- Addon-essentials ([#9019](https://github.com/storybookjs/storybook/pull/9019)) ### Bug Fixes -* Addon-docs: Fix prop table default value for web-components ([#9086](https://github.com/storybookjs/storybook/pull/9086)) +- Addon-docs: Fix prop table default value for web-components ([#9086](https://github.com/storybookjs/storybook/pull/9086)) ## 5.3.0-beta.18 (December 6, 2019) ### Features -* CLI: Change generators to Triconfig ([#9075](https://github.com/storybookjs/storybook/pull/9075)) -* Addon-docs: Add Props for Ember ([#9067](https://github.com/storybookjs/storybook/pull/9067)) -* MDX: Handle quotes / template literals in title ([#9069](https://github.com/storybookjs/storybook/pull/9069)) +- CLI: Change generators to Triconfig ([#9075](https://github.com/storybookjs/storybook/pull/9075)) +- Addon-docs: Add Props for Ember ([#9067](https://github.com/storybookjs/storybook/pull/9067)) +- MDX: Handle quotes / template literals in title ([#9069](https://github.com/storybookjs/storybook/pull/9069)) ### Bug Fixes -* Addon-docs: MDX Octicon anchors should not be tabbable ([#9063](https://github.com/storybookjs/storybook/pull/9063)) +- Addon-docs: MDX Octicon anchors should not be tabbable ([#9063](https://github.com/storybookjs/storybook/pull/9063)) ### Dependency Upgrades -* Addon-docs: Upgrade vue-docgen-loader ([#9082](https://github.com/storybookjs/storybook/pull/9082)) +- Addon-docs: Upgrade vue-docgen-loader ([#9082](https://github.com/storybookjs/storybook/pull/9082)) ## 5.3.0-beta.17 (December 6, 2019) @@ -146,145 +1444,145 @@ NPM publish failed ### Features -* Addon-docs: DocsPage Heading and Subheading anchor links ([#9060](https://github.com/storybookjs/storybook/pull/9060)) +- Addon-docs: DocsPage Heading and Subheading anchor links ([#9060](https://github.com/storybookjs/storybook/pull/9060)) ### Bug Fixes -* Core: Fix `api.selectStory` for component permalinks ([#9054](https://github.com/storybookjs/storybook/pull/9054)) -* Storyshots: Escape Windows fileNames ([#9061](https://github.com/storybookjs/storybook/pull/9061)) +- Core: Fix `api.selectStory` for component permalinks ([#9054](https://github.com/storybookjs/storybook/pull/9054)) +- Storyshots: Escape Windows fileNames ([#9061](https://github.com/storybookjs/storybook/pull/9061)) ### Dependency Upgrades -* Addon-docs: Upgrade vue-docgen-api ([#9066](https://github.com/storybookjs/storybook/pull/9066)) +- Addon-docs: Upgrade vue-docgen-api ([#9066](https://github.com/storybookjs/storybook/pull/9066)) ## 5.3.0-beta.15 (December 4, 2019) ### Features -* Addon-docs: MDX Linking ([#9051](https://github.com/storybookjs/storybook/pull/9051)) +- Addon-docs: MDX Linking ([#9051](https://github.com/storybookjs/storybook/pull/9051)) ## 5.2.8 (December 2, 2019) ### Bug Fixes -* UI: Fix layout of Preview container ([#8628](https://github.com/storybookjs/storybook/pull/8628)) -* Core: Use `stable` package to ensure story sorting is stable ([#8795](https://github.com/storybookjs/storybook/pull/8795)) -* Source-loader: Warn if applied to non-stories file ([#8773](https://github.com/storybookjs/storybook/pull/8773)) +- UI: Fix layout of Preview container ([#8628](https://github.com/storybookjs/storybook/pull/8628)) +- Core: Use `stable` package to ensure story sorting is stable ([#8795](https://github.com/storybookjs/storybook/pull/8795)) +- Source-loader: Warn if applied to non-stories file ([#8773](https://github.com/storybookjs/storybook/pull/8773)) ## 5.3.0-beta.14 (December 2, 2019) ### Features -* Addon-docs: Increase Props summary and func length ([#8998](https://github.com/storybookjs/storybook/pull/8998)) +- Addon-docs: Increase Props summary and func length ([#8998](https://github.com/storybookjs/storybook/pull/8998)) ### Bug Fixes -* Addon-docs: Restore IE11 compat by transpiling acorn-jsx ([#9021](https://github.com/storybookjs/storybook/pull/9021)) -* Source-loader: Handle template strings in CSF title ([#8995](https://github.com/storybookjs/storybook/pull/8995)) -* CLI: Fix various storiesof-to-csf cases based on chromatic stories upgrade ([#9013](https://github.com/storybookjs/storybook/pull/9013)) +- Addon-docs: Restore IE11 compat by transpiling acorn-jsx ([#9021](https://github.com/storybookjs/storybook/pull/9021)) +- Source-loader: Handle template strings in CSF title ([#8995](https://github.com/storybookjs/storybook/pull/8995)) +- CLI: Fix various storiesof-to-csf cases based on chromatic stories upgrade ([#9013](https://github.com/storybookjs/storybook/pull/9013)) ## 5.2.7 (November 30, 2019) ### Bug Fixes -* Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001)) -* Addon-viewports: Fix missing TypeScript types ([#8848](https://github.com/storybookjs/storybook/pull/8848)) -* Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779)) -* UI: Store layout state in sessionStorage ([#8786](https://github.com/storybookjs/storybook/pull/8786)) -* UI: Fix MobileLayout component error on master ([#8941](https://github.com/storybookjs/storybook/pull/8941)) -* Addon-analytics: Fix 'path is required in .pageview()' ([#8468](https://github.com/storybookjs/storybook/pull/8468)) +- Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001)) +- Addon-viewports: Fix missing TypeScript types ([#8848](https://github.com/storybookjs/storybook/pull/8848)) +- Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779)) +- UI: Store layout state in sessionStorage ([#8786](https://github.com/storybookjs/storybook/pull/8786)) +- UI: Fix MobileLayout component error on master ([#8941](https://github.com/storybookjs/storybook/pull/8941)) +- Addon-analytics: Fix 'path is required in .pageview()' ([#8468](https://github.com/storybookjs/storybook/pull/8468)) ## 5.3.0-beta.13 (November 30, 2019) ### Bug Fixes -* Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001)) +- Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001)) ### Maintenance -* CLI: Code cleanup ([#9004](https://github.com/storybookjs/storybook/pull/9004)) +- CLI: Code cleanup ([#9004](https://github.com/storybookjs/storybook/pull/9004)) ## 5.3.0-beta.12 (November 29, 2019) ### Features -* Storyshots: Support a11y tests, generic tests ([#8934](https://github.com/storybookjs/storybook/pull/8934)) +- Storyshots: Support a11y tests, generic tests ([#8934](https://github.com/storybookjs/storybook/pull/8934)) ### Maintenance -* Dev: Add vscode launch.json for debugging ([#8993](https://github.com/storybookjs/storybook/pull/8993)) -* UI: viewMode proptypes changed to any string ([#8994](https://github.com/storybookjs/storybook/pull/8994)) -* Addon-docs: Remove deprecated framework-specific docs presets ([#8985](https://github.com/storybookjs/storybook/pull/8985)) +- Dev: Add vscode launch.json for debugging ([#8993](https://github.com/storybookjs/storybook/pull/8993)) +- UI: viewMode proptypes changed to any string ([#8994](https://github.com/storybookjs/storybook/pull/8994)) +- Addon-docs: Remove deprecated framework-specific docs presets ([#8985](https://github.com/storybookjs/storybook/pull/8985)) ### Dependency Upgrades -* Addon-docs: Upgrade MDX dependencies ([#8991](https://github.com/storybookjs/storybook/pull/8991)) +- Addon-docs: Upgrade MDX dependencies ([#8991](https://github.com/storybookjs/storybook/pull/8991)) ## 5.3.0-beta.11 (November 28, 2019) ### Features -* UI: Escape hatch CSS on for "active" tablist buttons ([#8989](https://github.com/storybookjs/storybook/pull/8989)) -* Addon-docs: Added dark theme option to source component ([#8732](https://github.com/storybookjs/storybook/pull/8732)) -* Triconfig: Configure UI options overhaul ([#8871](https://github.com/storybookjs/storybook/pull/8871)) +- UI: Escape hatch CSS on for "active" tablist buttons ([#8989](https://github.com/storybookjs/storybook/pull/8989)) +- Addon-docs: Added dark theme option to source component ([#8732](https://github.com/storybookjs/storybook/pull/8732)) +- Triconfig: Configure UI options overhaul ([#8871](https://github.com/storybookjs/storybook/pull/8871)) ### Bug Fixes -* Addon-docs: Fix vertical alignment of props expandable ([#8953](https://github.com/storybookjs/storybook/pull/8953)) -* Addon-links: Fix return type of linkTo and examples ([#8975](https://github.com/storybookjs/storybook/pull/8975)) +- Addon-docs: Fix vertical alignment of props expandable ([#8953](https://github.com/storybookjs/storybook/pull/8953)) +- Addon-links: Fix return type of linkTo and examples ([#8975](https://github.com/storybookjs/storybook/pull/8975)) ## 5.3.0-beta.10 (November 27, 2019) ### Features -* MDX: Allow user to override `docs.container` parameter ([#8968](https://github.com/storybookjs/storybook/pull/8968)) -* Addon-docs: Increase docs content wrapper max-width to 1000px ([#8970](https://github.com/storybookjs/storybook/pull/8970)) -* Addon-docs: Prop table support for Angular directives ([#8922](https://github.com/storybookjs/storybook/pull/8922)) -* Addon-docs: Increase width of props table type column ([#8950](https://github.com/storybookjs/storybook/pull/8950)) +- MDX: Allow user to override `docs.container` parameter ([#8968](https://github.com/storybookjs/storybook/pull/8968)) +- Addon-docs: Increase docs content wrapper max-width to 1000px ([#8970](https://github.com/storybookjs/storybook/pull/8970)) +- Addon-docs: Prop table support for Angular directives ([#8922](https://github.com/storybookjs/storybook/pull/8922)) +- Addon-docs: Increase width of props table type column ([#8950](https://github.com/storybookjs/storybook/pull/8950)) ### Bug Fixes -* Addon-docs: Fix `Preview` theming escape hatch ([#8969](https://github.com/storybookjs/storybook/pull/8969)) -* Core: Don't try to require .ts files from dist ([#8971](https://github.com/storybookjs/storybook/pull/8971)) -* Core: Use logger in base-webpack.config.js ([#8966](https://github.com/storybookjs/storybook/pull/8966)) +- Addon-docs: Fix `Preview` theming escape hatch ([#8969](https://github.com/storybookjs/storybook/pull/8969)) +- Core: Don't try to require .ts files from dist ([#8971](https://github.com/storybookjs/storybook/pull/8971)) +- Core: Use logger in base-webpack.config.js ([#8966](https://github.com/storybookjs/storybook/pull/8966)) ### Maintenance -* Examples: Add "debug" script for storybook-official ([#8973](https://github.com/storybookjs/storybook/pull/8973)) -* Build: Upgrade to node 10 on netlify ([#8967](https://github.com/storybookjs/storybook/pull/8967)) -* Core/triconfig everywhere: migrate examples ([#8942](https://github.com/storybookjs/storybook/pull/8942)) +- Examples: Add "debug" script for storybook-official ([#8973](https://github.com/storybookjs/storybook/pull/8973)) +- Build: Upgrade to node 10 on netlify ([#8967](https://github.com/storybookjs/storybook/pull/8967)) +- Core/triconfig everywhere: migrate examples ([#8942](https://github.com/storybookjs/storybook/pull/8942)) ## 5.3.0-beta.9 (November 26, 2019) ### Features -* Storyshots: Remove abandoned storyshots when run with `-u` flag ([#8889](https://github.com/storybookjs/storybook/pull/8889)) +- Storyshots: Remove abandoned storyshots when run with `-u` flag ([#8889](https://github.com/storybookjs/storybook/pull/8889)) ### Bug Fixes -* Addon-docs: Support subcomponents as a top-level default export ([#8931](https://github.com/storybookjs/storybook/pull/8931)) +- Addon-docs: Support subcomponents as a top-level default export ([#8931](https://github.com/storybookjs/storybook/pull/8931)) ### Dependency Upgrades -* Core: Add missing dependencies ([#8945](https://github.com/storybookjs/storybook/pull/8945)) +- Core: Add missing dependencies ([#8945](https://github.com/storybookjs/storybook/pull/8945)) ## 5.3.0-beta.8 (November 26, 2019) ### Features -* Storyshots-puppeteer: Add afterScreenshot handler ([#8092](https://github.com/storybookjs/storybook/pull/8092)) +- Storyshots-puppeteer: Add afterScreenshot handler ([#8092](https://github.com/storybookjs/storybook/pull/8092)) ### Bug Fixes -* Core: Upgrade telejson to fix cross-origin frame error ([#8940](https://github.com/storybookjs/storybook/pull/8940)) +- Core: Upgrade telejson to fix cross-origin frame error ([#8940](https://github.com/storybookjs/storybook/pull/8940)) ### Maintenance -* Build: Fix image snapshots setup in official-storybook ([#8932](https://github.com/storybookjs/storybook/pull/8932)) +- Build: Fix image snapshots setup in official-storybook ([#8932](https://github.com/storybookjs/storybook/pull/8932)) ### Dependency Upgrades -* Core: Add @babel/core peer dependency to @storybook/core ([#8933](https://github.com/storybookjs/storybook/pull/8933)) +- Core: Add @babel/core peer dependency to @storybook/core ([#8933](https://github.com/storybookjs/storybook/pull/8933)) ## 5.3.0-beta.7 (November 26, 2019) @@ -294,21 +1592,21 @@ Failed npm publish ### Features -* Presets: dynamic preset injection ([#8921](https://github.com/storybookjs/storybook/pull/8921)) +- Presets: dynamic preset injection ([#8921](https://github.com/storybookjs/storybook/pull/8921)) ### Bug Fixes -* Revert "feat: use `puppeteer-core` instead of `puppeteer`" ([#8925](https://github.com/storybookjs/storybook/pull/8925)) -* Addon-docs: Fix props detail tooltip to prevent cutting end of content ([#8923](https://github.com/storybookjs/storybook/pull/8923)) +- Revert "feat: use `puppeteer-core` instead of `puppeteer`" ([#8925](https://github.com/storybookjs/storybook/pull/8925)) +- Addon-docs: Fix props detail tooltip to prevent cutting end of content ([#8923](https://github.com/storybookjs/storybook/pull/8923)) ### Maintenance -* Addon-docs: Base code to improve the props table for TS ([#8905](https://github.com/storybookjs/storybook/pull/8905)) -* Build: Fix now deploy ([#8929](https://github.com/storybookjs/storybook/pull/8929)) +- Addon-docs: Base code to improve the props table for TS ([#8905](https://github.com/storybookjs/storybook/pull/8905)) +- Build: Fix now deploy ([#8929](https://github.com/storybookjs/storybook/pull/8929)) ### Dependency Upgrades -* Miscellaneous upgrades ([#8912](https://github.com/storybookjs/storybook/pull/8912)) +- Miscellaneous upgrades ([#8912](https://github.com/storybookjs/storybook/pull/8912)) ## 5.3.0-beta.5 (November 23, 2019) @@ -322,47 +1620,47 @@ Failed npm publish ### Features -* Addon-docs: Rich props table UI ([#8887](https://github.com/storybookjs/storybook/pull/8887)) -* Addon-docs: Improve basic support for Flow props ([#8890](https://github.com/storybookjs/storybook/pull/8890)) -* CLI: Avoid id changes after `storiesof-to-csf` migration ([#8856](https://github.com/storybookjs/storybook/pull/8856)) +- Addon-docs: Rich props table UI ([#8887](https://github.com/storybookjs/storybook/pull/8887)) +- Addon-docs: Improve basic support for Flow props ([#8890](https://github.com/storybookjs/storybook/pull/8890)) +- CLI: Avoid id changes after `storiesof-to-csf` migration ([#8856](https://github.com/storybookjs/storybook/pull/8856)) ### Bug Fixes -* Addon-docs: Fix props table for sections props ([#8904](https://github.com/storybookjs/storybook/pull/8904)) -* Addon-docs: Fix Description block when no component provided ([#8902](https://github.com/storybookjs/storybook/pull/8902)) -* Angular: Fix project without `architect.build` option ([#6737](https://github.com/storybookjs/storybook/pull/6737)) +- Addon-docs: Fix props table for sections props ([#8904](https://github.com/storybookjs/storybook/pull/8904)) +- Addon-docs: Fix Description block when no component provided ([#8902](https://github.com/storybookjs/storybook/pull/8902)) +- Angular: Fix project without `architect.build` option ([#6737](https://github.com/storybookjs/storybook/pull/6737)) ### Maintenance -* Addon-docs: Docgen lib maintenance ([#8896](https://github.com/storybookjs/storybook/pull/8896)) -* Examples: Fix stories glob in official-storybook ([#8888](https://github.com/storybookjs/storybook/pull/8888)) +- Addon-docs: Docgen lib maintenance ([#8896](https://github.com/storybookjs/storybook/pull/8896)) +- Examples: Fix stories glob in official-storybook ([#8888](https://github.com/storybookjs/storybook/pull/8888)) ## 5.3.0-beta.2 (November 19, 2019) ### Features -* Addon-docs: Customizable DocPage doc blocks ([#8855](https://github.com/storybookjs/storybook/pull/8855)) +- Addon-docs: Customizable DocPage doc blocks ([#8855](https://github.com/storybookjs/storybook/pull/8855)) ### Bug Fixes -* Addon-docs: Add back Props "exclude" support ([#8868](https://github.com/storybookjs/storybook/pull/8868)) -* Addon-docs: Fix MDX component permalinking ([#8872](https://github.com/storybookjs/storybook/pull/8872)) -* Addon-docs: Fix regression to @ignore in Props ([#8867](https://github.com/storybookjs/storybook/pull/8867)) +- Addon-docs: Add back Props "exclude" support ([#8868](https://github.com/storybookjs/storybook/pull/8868)) +- Addon-docs: Fix MDX component permalinking ([#8872](https://github.com/storybookjs/storybook/pull/8872)) +- Addon-docs: Fix regression to @ignore in Props ([#8867](https://github.com/storybookjs/storybook/pull/8867)) ### Maintenance -* Addon-docs: Add tests for prop types default value ([#8869](https://github.com/storybookjs/storybook/pull/8869)) +- Addon-docs: Add tests for prop types default value ([#8869](https://github.com/storybookjs/storybook/pull/8869)) ## 5.3.0-beta.1 (November 18, 2019) ### Features -* Addon-google-analytics: Add gaOption config ([#8859](https://github.com/storybookjs/storybook/pull/8859)) +- Addon-google-analytics: Add gaOption config ([#8859](https://github.com/storybookjs/storybook/pull/8859)) ### Bug Fixes -* Addon-docs: Fix props table props sorting for PropTypes ([#8857](https://github.com/storybookjs/storybook/pull/8857)) -* Fix layout of Preview container ([#8628](https://github.com/storybookjs/storybook/pull/8628)) +- Addon-docs: Fix props table props sorting for PropTypes ([#8857](https://github.com/storybookjs/storybook/pull/8857)) +- Fix layout of Preview container ([#8628](https://github.com/storybookjs/storybook/pull/8628)) ## 5.3.0-beta.0 (November 16, 2019) @@ -379,294 +1677,294 @@ See the [latest changelog](https://github.com/storybookjs/storybook/blob/next/CH ### Features -* Triconfig: Declarative story definition in main.js ([#8748](https://github.com/storybookjs/storybook/pull/8748)) -* Storyshots: Triconfig support ([#8765](https://github.com/storybookjs/storybook/pull/8765)) +- Triconfig: Declarative story definition in main.js ([#8748](https://github.com/storybookjs/storybook/pull/8748)) +- Storyshots: Triconfig support ([#8765](https://github.com/storybookjs/storybook/pull/8765)) ### Bug Fixes -* Addon-viewports: Fix missing TypeScript types ([#8848](https://github.com/storybookjs/storybook/pull/8848)) +- Addon-viewports: Fix missing TypeScript types ([#8848](https://github.com/storybookjs/storybook/pull/8848)) ### Dependency Upgrades -* Dependency upgrades ([#8847](https://github.com/storybookjs/storybook/pull/8847)) +- Dependency upgrades ([#8847](https://github.com/storybookjs/storybook/pull/8847)) ## 5.3.0-alpha.46 (November 16, 2019) ### Features -* Core: allow uppercase path names in url query param ([#8516](https://github.com/storybookjs/storybook/pull/8516)) +- Core: allow uppercase path names in url query param ([#8516](https://github.com/storybookjs/storybook/pull/8516)) ### Bug Fixes -* Core: Fix null version check bug ([#8806](https://github.com/storybookjs/storybook/pull/8806)) -* Addon-notes: Fix anchor links ([#8132](https://github.com/storybookjs/storybook/pull/8132)) +- Core: Fix null version check bug ([#8806](https://github.com/storybookjs/storybook/pull/8806)) +- Addon-notes: Fix anchor links ([#8132](https://github.com/storybookjs/storybook/pull/8132)) ### Maintenance -* Refactor: remove useless variables from eslint config ([#8843](https://github.com/storybookjs/storybook/pull/8843)) +- Refactor: remove useless variables from eslint config ([#8843](https://github.com/storybookjs/storybook/pull/8843)) ### Dependency Upgrades -* Addon-docs: Replace `storybook-addon-vue-info` with `vue-docgen-loader` ([#8831](https://github.com/storybookjs/storybook/pull/8831)) +- Addon-docs: Replace `storybook-addon-vue-info` with `vue-docgen-loader` ([#8831](https://github.com/storybookjs/storybook/pull/8831)) ## 5.3.0-alpha.45 (November 14, 2019) ### Breaking Changes -* CSF: Deprecate displayName parameter ([#8775](https://github.com/storybookjs/storybook/pull/8775)) +- CSF: Deprecate displayName parameter ([#8775](https://github.com/storybookjs/storybook/pull/8775)) ### Features -* Addon-docs: Rich prop tables ([#8826](https://github.com/storybookjs/storybook/pull/8826)) -* Core: Simplified hierarchy separators ([#8796](https://github.com/storybookjs/storybook/pull/8796)) -* CLI: Upgrade hierarchy separator codemod + examples ([#8818](https://github.com/storybookjs/storybook/pull/8818)) -* CLI: Addon postinstall hooks ([#8700](https://github.com/storybookjs/storybook/pull/8700)) -* CSF/MDX: Add component id for permalinks ([#8808](https://github.com/storybookjs/storybook/pull/8808)) -* Addon-knobs: Add object[] support for select ([#7957](https://github.com/storybookjs/storybook/pull/7957)) +- Addon-docs: Rich prop tables ([#8826](https://github.com/storybookjs/storybook/pull/8826)) +- Core: Simplified hierarchy separators ([#8796](https://github.com/storybookjs/storybook/pull/8796)) +- CLI: Upgrade hierarchy separator codemod + examples ([#8818](https://github.com/storybookjs/storybook/pull/8818)) +- CLI: Addon postinstall hooks ([#8700](https://github.com/storybookjs/storybook/pull/8700)) +- CSF/MDX: Add component id for permalinks ([#8808](https://github.com/storybookjs/storybook/pull/8808)) +- Addon-knobs: Add object[] support for select ([#7957](https://github.com/storybookjs/storybook/pull/7957)) ### Bug Fixes -* Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779)) +- Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779)) ## 5.3.0-alpha.44 (November 13, 2019) ### Features -* React-native: Add theming to ondevice-addons ([#8738](https://github.com/storybookjs/storybook/pull/8738)) +- React-native: Add theming to ondevice-addons ([#8738](https://github.com/storybookjs/storybook/pull/8738)) ### Bug Fixes -* UI: Store layout state in sessionStorage ([#8786](https://github.com/storybookjs/storybook/pull/8786)) -* Core: Use `stable` package to ensure story sorting is stable ([#8795](https://github.com/storybookjs/storybook/pull/8795)) +- UI: Store layout state in sessionStorage ([#8786](https://github.com/storybookjs/storybook/pull/8786)) +- Core: Use `stable` package to ensure story sorting is stable ([#8795](https://github.com/storybookjs/storybook/pull/8795)) ### Maintenance -* Svelte: Migrate @storybook/svelte to Typescript ([#8770](https://github.com/storybookjs/storybook/pull/8770)) +- Svelte: Migrate @storybook/svelte to Typescript ([#8770](https://github.com/storybookjs/storybook/pull/8770)) ## 5.3.0-alpha.43 (November 11, 2019) ### Bug Fixes -* Source-loader: Warn if applied to non-stories file ([#8773](https://github.com/storybookjs/storybook/pull/8773)) +- Source-loader: Warn if applied to non-stories file ([#8773](https://github.com/storybookjs/storybook/pull/8773)) ### Maintenance -* Presets / Addon-docs: Cleanup framework-specific presets ([#8782](https://github.com/storybookjs/storybook/pull/8782)) -* Add @babel/runtime to workspace ([#8774](https://github.com/storybookjs/storybook/pull/8774)) +- Presets / Addon-docs: Cleanup framework-specific presets ([#8782](https://github.com/storybookjs/storybook/pull/8782)) +- Add @babel/runtime to workspace ([#8774](https://github.com/storybookjs/storybook/pull/8774)) ## 5.2.6 (November 9, 2019) ### Bug Fixes -* Addon-info: Remove jsnext:main ([#8764](https://github.com/storybookjs/storybook/pull/8764)) -* Addon-info: Fix "The prop 'children' is marked as required in 'Td'" ([#8745](https://github.com/storybookjs/storybook/pull/8745)) -* UI: Fix unmount components on Canvas/Docs tab switch ([#8625](https://github.com/storybookjs/storybook/pull/8625)) -* Addon-docs: Fix code style inside LI ([#8708](https://github.com/storybookjs/storybook/pull/8708)) -* Remove min-height CSS rule from DocsPage wrapper ([#8366](https://github.com/storybookjs/storybook/pull/8366)) -* Core: Revert webpack rebuild changes in node_modules ([#8657](https://github.com/storybookjs/storybook/pull/8657)) -* Addon-notes: Add key to render function ([#8633](https://github.com/storybookjs/storybook/pull/8633)) -* Addon-docs: Fix story scroll-to heuristics ([#8629](https://github.com/storybookjs/storybook/pull/8629)) -* React-native-server: Changed default port to number in CLI options ([#8584](https://github.com/storybookjs/storybook/pull/8584)) -* Increase TooltipLinkList max-height to accommodate more links ([#8545](https://github.com/storybookjs/storybook/pull/8545)) -* Prevent form submission as search is done while typing ([#8546](https://github.com/storybookjs/storybook/pull/8546)) -* Ondevice-knobs: Fix peer dep ([#8644](https://github.com/storybookjs/storybook/pull/8644)) +- Addon-info: Remove jsnext:main ([#8764](https://github.com/storybookjs/storybook/pull/8764)) +- Addon-info: Fix "The prop 'children' is marked as required in 'Td'" ([#8745](https://github.com/storybookjs/storybook/pull/8745)) +- UI: Fix unmount components on Canvas/Docs tab switch ([#8625](https://github.com/storybookjs/storybook/pull/8625)) +- Addon-docs: Fix code style inside LI ([#8708](https://github.com/storybookjs/storybook/pull/8708)) +- Remove min-height CSS rule from DocsPage wrapper ([#8366](https://github.com/storybookjs/storybook/pull/8366)) +- Core: Revert webpack rebuild changes in node_modules ([#8657](https://github.com/storybookjs/storybook/pull/8657)) +- Addon-notes: Add key to render function ([#8633](https://github.com/storybookjs/storybook/pull/8633)) +- Addon-docs: Fix story scroll-to heuristics ([#8629](https://github.com/storybookjs/storybook/pull/8629)) +- React-native-server: Changed default port to number in CLI options ([#8584](https://github.com/storybookjs/storybook/pull/8584)) +- Increase TooltipLinkList max-height to accommodate more links ([#8545](https://github.com/storybookjs/storybook/pull/8545)) +- Prevent form submission as search is done while typing ([#8546](https://github.com/storybookjs/storybook/pull/8546)) +- Ondevice-knobs: Fix peer dep ([#8644](https://github.com/storybookjs/storybook/pull/8644)) ## 5.3.0-alpha.42 (November 9, 2019) ### Bug Fixes -* Addon-info: Remove jsnext:main ([#8764](https://github.com/storybookjs/storybook/pull/8764)) -* Addon-info: Fix "The prop 'children' is marked as required in 'Td'" ([#8745](https://github.com/storybookjs/storybook/pull/8745)) +- Addon-info: Remove jsnext:main ([#8764](https://github.com/storybookjs/storybook/pull/8764)) +- Addon-info: Fix "The prop 'children' is marked as required in 'Td'" ([#8745](https://github.com/storybookjs/storybook/pull/8745)) ### Maintenance -* React-native: Update compilation target - it was compiled for old browsers ([#8698](https://github.com/storybookjs/storybook/pull/8698)) +- React-native: Update compilation target - it was compiled for old browsers ([#8698](https://github.com/storybookjs/storybook/pull/8698)) ## 5.3.0-alpha.41 (November 7, 2019) ### Features -* Addon-docs: Render array of shape properly in props table ([#8707](https://github.com/storybookjs/storybook/pull/8707)) -* Addon-docs: Render params description in props table, support @ignore ([#8702](https://github.com/storybookjs/storybook/pull/8702)) +- Addon-docs: Render array of shape properly in props table ([#8707](https://github.com/storybookjs/storybook/pull/8707)) +- Addon-docs: Render params description in props table, support @ignore ([#8702](https://github.com/storybookjs/storybook/pull/8702)) ### Bug Fixes -* Addon-docs: Fix "Cannot read property 'props'" ([#8731](https://github.com/storybookjs/storybook/pull/8731)) -* UI: Fix unmount components on Canvas/Docs tab switch ([#8625](https://github.com/storybookjs/storybook/pull/8625)) -* Angular: Fix loading baseConfig if no angular.json found ([#8727](https://github.com/storybookjs/storybook/pull/8727)) +- Addon-docs: Fix "Cannot read property 'props'" ([#8731](https://github.com/storybookjs/storybook/pull/8731)) +- UI: Fix unmount components on Canvas/Docs tab switch ([#8625](https://github.com/storybookjs/storybook/pull/8625)) +- Angular: Fix loading baseConfig if no angular.json found ([#8727](https://github.com/storybookjs/storybook/pull/8727)) ## 5.3.0-alpha.40 (November 6, 2019) ### Features -* TriConfig configuration refactor ([#8597](https://github.com/storybookjs/storybook/pull/8597)) -* Presets: Ability to combine presets into another preset ([#6828](https://github.com/storybookjs/storybook/pull/6828)) +- TriConfig configuration refactor ([#8597](https://github.com/storybookjs/storybook/pull/8597)) +- Presets: Ability to combine presets into another preset ([#6828](https://github.com/storybookjs/storybook/pull/6828)) ### Bug Fixes -* Theming: Fix invisible hr styles in typography ([#8659](https://github.com/storybookjs/storybook/pull/8659)) +- Theming: Fix invisible hr styles in typography ([#8659](https://github.com/storybookjs/storybook/pull/8659)) ### Maintenance -* Build: caching to github workflows ([#8724](https://github.com/storybookjs/storybook/pull/8724)) +- Build: caching to github workflows ([#8724](https://github.com/storybookjs/storybook/pull/8724)) ## 5.3.0-alpha.39 (November 5, 2019) ### Breaking Changes -* React-native: Require user-provided async storage ([#7801](https://github.com/storybookjs/storybook/pull/7801)) +- React-native: Require user-provided async storage ([#7801](https://github.com/storybookjs/storybook/pull/7801)) ### Features -* Addon-docs: Inline rendering for web-components as default ([#8705](https://github.com/storybookjs/storybook/pull/8705)) +- Addon-docs: Inline rendering for web-components as default ([#8705](https://github.com/storybookjs/storybook/pull/8705)) ### Bug Fixes -* Addon-docs: Fix recipe to use `notes` as component description ([#8718](https://github.com/storybookjs/storybook/pull/8718)) -* Addon-docs: Fix code style inside LI ([#8708](https://github.com/storybookjs/storybook/pull/8708)) -* React Native: Fix on-device-notes ([#8692](https://github.com/storybookjs/storybook/pull/8692)) -* React Native: On Device Knobs Groups Fix ([#8694](https://github.com/storybookjs/storybook/pull/8694)) -* React-native: Fix event listening for story navigation ([#8690](https://github.com/storybookjs/storybook/pull/8690)) -* React-Native: Add safe area wrapper for iphone x and later ([#8679](https://github.com/storybookjs/storybook/pull/8679)) +- Addon-docs: Fix recipe to use `notes` as component description ([#8718](https://github.com/storybookjs/storybook/pull/8718)) +- Addon-docs: Fix code style inside LI ([#8708](https://github.com/storybookjs/storybook/pull/8708)) +- React Native: Fix on-device-notes ([#8692](https://github.com/storybookjs/storybook/pull/8692)) +- React Native: On Device Knobs Groups Fix ([#8694](https://github.com/storybookjs/storybook/pull/8694)) +- React-native: Fix event listening for story navigation ([#8690](https://github.com/storybookjs/storybook/pull/8690)) +- React-Native: Add safe area wrapper for iphone x and later ([#8679](https://github.com/storybookjs/storybook/pull/8679)) ## 5.3.0-alpha.38 (November 3, 2019) ### Bug Fixes -* React-native: Patch rn-host-detect ([#8683](https://github.com/storybookjs/storybook/pull/8683)) -* React-native: Fix layout in RN61 so addons no longer initially displayed ([#8681](https://github.com/storybookjs/storybook/pull/8681)) +- React-native: Patch rn-host-detect ([#8683](https://github.com/storybookjs/storybook/pull/8683)) +- React-native: Fix layout in RN61 so addons no longer initially displayed ([#8681](https://github.com/storybookjs/storybook/pull/8681)) ### Maintenance -* React-native: Catch touch events when preview is minimized ([#8680](https://github.com/storybookjs/storybook/pull/8680)) -* React-native: Make menu bar have its own position instead of absolute ([#8678](https://github.com/storybookjs/storybook/pull/8678)) +- React-native: Catch touch events when preview is minimized ([#8680](https://github.com/storybookjs/storybook/pull/8680)) +- React-native: Make menu bar have its own position instead of absolute ([#8678](https://github.com/storybookjs/storybook/pull/8678)) ## 5.3.0-alpha.37 (November 2, 2019) ### Bug Fixes -* Source-loader: Fix Typescript support ([#8499](https://github.com/storybookjs/storybook/pull/8499)) +- Source-loader: Fix Typescript support ([#8499](https://github.com/storybookjs/storybook/pull/8499)) ## 5.3.0-alpha.36 (November 2, 2019) ### Features -* Addon-docs: Angular DocsPage props table ([#8621](https://github.com/storybookjs/storybook/pull/8621)) -* Addon-docs: Support jsdoc params to describe function signature ([#8660](https://github.com/storybookjs/storybook/pull/8660)) +- Addon-docs: Angular DocsPage props table ([#8621](https://github.com/storybookjs/storybook/pull/8621)) +- Addon-docs: Support jsdoc params to describe function signature ([#8660](https://github.com/storybookjs/storybook/pull/8660)) ### Bug Fixes -* Addon-docs: Remove min-height CSS rule from DocsPage wrapper ([#8366](https://github.com/storybookjs/storybook/pull/8366)) -* Addon-docs: Check component propTypes before unwrapping ([#8665](https://github.com/storybookjs/storybook/pull/8665)) +- Addon-docs: Remove min-height CSS rule from DocsPage wrapper ([#8366](https://github.com/storybookjs/storybook/pull/8366)) +- Addon-docs: Check component propTypes before unwrapping ([#8665](https://github.com/storybookjs/storybook/pull/8665)) ## 5.3.0-alpha.35 (November 1, 2019) ### Bug Fixes -* Core: Revert webpack rebuild changes in node_modules ([#8657](https://github.com/storybookjs/storybook/pull/8657)) -* Ondevice-knobs: Fix peer dep ([#8643](https://github.com/storybookjs/storybook/pull/8643)) +- Core: Revert webpack rebuild changes in node_modules ([#8657](https://github.com/storybookjs/storybook/pull/8657)) +- Ondevice-knobs: Fix peer dep ([#8643](https://github.com/storybookjs/storybook/pull/8643)) ## 5.3.0-alpha.34 (October 30, 2019) ### Bug Fixes -* Addon-notes: Add key to render function ([#8633](https://github.com/storybookjs/storybook/pull/8633)) -* Addon-docs: Fix story scroll-to heuristics ([#8629](https://github.com/storybookjs/storybook/pull/8629)) -* Addon-docs: Fix props table in no props case ([#8632](https://github.com/storybookjs/storybook/pull/8632)) -* Addon-docs/web-components: Add attributes to props table ([#8598](https://github.com/storybookjs/storybook/pull/8598)) -* CLI: Fix package.json update ([#8615](https://github.com/storybookjs/storybook/pull/8615)) -* React-native: Fix background style ([#8480](https://github.com/storybookjs/storybook/pull/8480)) +- Addon-notes: Add key to render function ([#8633](https://github.com/storybookjs/storybook/pull/8633)) +- Addon-docs: Fix story scroll-to heuristics ([#8629](https://github.com/storybookjs/storybook/pull/8629)) +- Addon-docs: Fix props table in no props case ([#8632](https://github.com/storybookjs/storybook/pull/8632)) +- Addon-docs/web-components: Add attributes to props table ([#8598](https://github.com/storybookjs/storybook/pull/8598)) +- CLI: Fix package.json update ([#8615](https://github.com/storybookjs/storybook/pull/8615)) +- React-native: Fix background style ([#8480](https://github.com/storybookjs/storybook/pull/8480)) ### Maintenance -* Channel: Clean up test ([#8627](https://github.com/storybookjs/storybook/pull/8627)) -* RAX: Fix example app ([#8617](https://github.com/storybookjs/storybook/pull/8617)) +- Channel: Clean up test ([#8627](https://github.com/storybookjs/storybook/pull/8627)) +- RAX: Fix example app ([#8617](https://github.com/storybookjs/storybook/pull/8617)) ### Dependency Upgrades -* Misc. dependency upgrades ([#8612](https://github.com/storybookjs/storybook/pull/8612)) +- Misc. dependency upgrades ([#8612](https://github.com/storybookjs/storybook/pull/8612)) ## 5.3.0-alpha.33 (October 28, 2019) ### Maintenance -* Web-components: Move custom-elements utils to app ([#8592](https://github.com/storybookjs/storybook/pull/8592)) +- Web-components: Move custom-elements utils to app ([#8592](https://github.com/storybookjs/storybook/pull/8592)) ## 5.3.0-alpha.32 (October 28, 2019) ### Features -* Addon-docs: Props / Description for web-components ([#8585](https://github.com/storybookjs/storybook/pull/8585)) -* Core: Global addDecorator/addParameters ([#8573](https://github.com/storybookjs/storybook/pull/8573)) -* Addon-docs: Generalize Description doc block ([#8590](https://github.com/storybookjs/storybook/pull/8590)) +- Addon-docs: Props / Description for web-components ([#8585](https://github.com/storybookjs/storybook/pull/8585)) +- Core: Global addDecorator/addParameters ([#8573](https://github.com/storybookjs/storybook/pull/8573)) +- Addon-docs: Generalize Description doc block ([#8590](https://github.com/storybookjs/storybook/pull/8590)) ### Bug Fixes -* React-native-server: Changed default port to number in CLI options ([#8584](https://github.com/storybookjs/storybook/pull/8584)) +- React-native-server: Changed default port to number in CLI options ([#8584](https://github.com/storybookjs/storybook/pull/8584)) ### Maintenance -* Typescript: Use native package types now available ([#8588](https://github.com/storybookjs/storybook/pull/8588)) -* Build: Fix now deploy maybe ([#8589](https://github.com/storybookjs/storybook/pull/8589)) +- Typescript: Use native package types now available ([#8588](https://github.com/storybookjs/storybook/pull/8588)) +- Build: Fix now deploy maybe ([#8589](https://github.com/storybookjs/storybook/pull/8589)) ## 5.3.0-alpha.31 (October 27, 2019) ### Dependency Upgrades -* Upgrade babel-plugin-react-docgen to 4.0.0-beta.1 ([#8581](https://github.com/storybookjs/storybook/pull/8581)) +- Upgrade babel-plugin-react-docgen to 4.0.0-beta.1 ([#8581](https://github.com/storybookjs/storybook/pull/8581)) ## 5.3.0-alpha.30 (October 27, 2019) ### Features -* Addon-docs: Modify Typeset doc block to accept units ([#8574](https://github.com/storybookjs/storybook/pull/8574)) +- Addon-docs: Modify Typeset doc block to accept units ([#8574](https://github.com/storybookjs/storybook/pull/8574)) ### Bug Fixes -* Web-components: Use framework id 'web-components' ([#8579](https://github.com/storybookjs/storybook/pull/8579)) +- Web-components: Use framework id 'web-components' ([#8579](https://github.com/storybookjs/storybook/pull/8579)) ### Maintenance -* WC-kitchen-sink: Add babel-loader dependency ([#8578](https://github.com/storybookjs/storybook/pull/8578)) +- WC-kitchen-sink: Add babel-loader dependency ([#8578](https://github.com/storybookjs/storybook/pull/8578)) ### Dependency Upgrades -* Upgrade babel-plugin-react-docgen to 3.2.0 ([#8580](https://github.com/storybookjs/storybook/pull/8580)) +- Upgrade babel-plugin-react-docgen to 3.2.0 ([#8580](https://github.com/storybookjs/storybook/pull/8580)) ## 5.3.0-alpha.29 (October 25, 2019) ### Features -* Addon-docs: Add props loader to vue preset ([#8567](https://github.com/storybookjs/storybook/pull/8567)) +- Addon-docs: Add props loader to vue preset ([#8567](https://github.com/storybookjs/storybook/pull/8567)) ### Dependency Upgrades -* Angular: bump zone.js version in peer dependencies ([#8558](https://github.com/storybookjs/storybook/pull/8558)) +- Angular: bump zone.js version in peer dependencies ([#8558](https://github.com/storybookjs/storybook/pull/8558)) ## 5.3.0-alpha.28 (October 25, 2019) ### Features -* Addon-docs: Publish web-components preset ([#8563](https://github.com/storybookjs/storybook/pull/8563)) -* Addon-docs: Vue slots/events props table + generalization ([#8489](https://github.com/storybookjs/storybook/pull/8489)) +- Addon-docs: Publish web-components preset ([#8563](https://github.com/storybookjs/storybook/pull/8563)) +- Addon-docs: Vue slots/events props table + generalization ([#8489](https://github.com/storybookjs/storybook/pull/8489)) ## 5.3.0-alpha.27 (October 24, 2019) ### Features -* Core: Allow linking to kind/component ID ([#7648](https://github.com/storybookjs/storybook/pull/7648)) -* Addon-docs: Jest MDX transform for storyshots ([#8189](https://github.com/storybookjs/storybook/pull/8189)) -* CLI: Add flag to disable version checks ([#8488](https://github.com/storybookjs/storybook/pull/8488)) -* Typescript: add types for react demo ([#8517](https://github.com/storybookjs/storybook/pull/8517)) +- Core: Allow linking to kind/component ID ([#7648](https://github.com/storybookjs/storybook/pull/7648)) +- Addon-docs: Jest MDX transform for storyshots ([#8189](https://github.com/storybookjs/storybook/pull/8189)) +- CLI: Add flag to disable version checks ([#8488](https://github.com/storybookjs/storybook/pull/8488)) +- Typescript: add types for react demo ([#8517](https://github.com/storybookjs/storybook/pull/8517)) ### Bug Fixes -* UI: Increase TooltipLinkList max-height to accommodate more links ([#8545](https://github.com/storybookjs/storybook/pull/8545)) -* UI: Prevent form submission as search is done while typing ([#8546](https://github.com/storybookjs/storybook/pull/8546)) +- UI: Increase TooltipLinkList max-height to accommodate more links ([#8545](https://github.com/storybookjs/storybook/pull/8545)) +- UI: Prevent form submission as search is done while typing ([#8546](https://github.com/storybookjs/storybook/pull/8546)) ### Dependency Upgrades -* Remove outdated and unused `jsx-to-string` library ([#8549](https://github.com/storybookjs/storybook/pull/8549)) +- Remove outdated and unused `jsx-to-string` library ([#8549](https://github.com/storybookjs/storybook/pull/8549)) ## 5.3.0-alpha.26 (October 23, 2019) @@ -676,150 +1974,150 @@ Fix web-components storybook dependencies ### Features -* Web-components: New storybook app ([#8400](https://github.com/storybookjs/storybook/pull/8400)) -* Core: Allow custom postcss config ([#8498](https://github.com/storybookjs/storybook/pull/8498)) +- Web-components: New storybook app ([#8400](https://github.com/storybookjs/storybook/pull/8400)) +- Core: Allow custom postcss config ([#8498](https://github.com/storybookjs/storybook/pull/8498)) ## 5.3.0-alpha.24 (October 22, 2019) ### Features -* Docs: Highlight ts and tsx syntax ([#8493](https://github.com/storybookjs/storybook/pull/8493)) +- Docs: Highlight ts and tsx syntax ([#8493](https://github.com/storybookjs/storybook/pull/8493)) ### Bug Fixes -* React-native-server: Convert port to number in CLI options ([#8491](https://github.com/storybookjs/storybook/pull/8491)) +- React-native-server: Convert port to number in CLI options ([#8491](https://github.com/storybookjs/storybook/pull/8491)) ### Maintenance -* Automate codemod tests as jest snapshots ([#8506](https://github.com/storybookjs/storybook/pull/8506)) +- Automate codemod tests as jest snapshots ([#8506](https://github.com/storybookjs/storybook/pull/8506)) ### Dependency Upgrades -* Add "@types/react-textarea-autosize" to dependencies ([#8503](https://github.com/storybookjs/storybook/pull/8503)) +- Add "@types/react-textarea-autosize" to dependencies ([#8503](https://github.com/storybookjs/storybook/pull/8503)) ## 5.2.5 (October 22, 2019) ### Bug Fixes -* UI: Fix React15 support ([#8454](https://github.com/storybookjs/storybook/pull/8454)) -* React-native-server: Convert port to number in CLI options ([#8491](https://github.com/storybookjs/storybook/pull/8491)) -* Addon-docs: Fix React.forwardedRef/memo props ([#8445](https://github.com/storybookjs/storybook/pull/8445)) -* MDX: Handle `` name starting with number ([#8469](https://github.com/storybookjs/storybook/pull/8469)) -* React: Fix custom themes breaking the welcome demo ([#8259](https://github.com/storybookjs/storybook/pull/8259)) -* Addon-jest: Make withTests type generic ([#8410](https://github.com/storybookjs/storybook/pull/8410)) +- UI: Fix React15 support ([#8454](https://github.com/storybookjs/storybook/pull/8454)) +- React-native-server: Convert port to number in CLI options ([#8491](https://github.com/storybookjs/storybook/pull/8491)) +- Addon-docs: Fix React.forwardedRef/memo props ([#8445](https://github.com/storybookjs/storybook/pull/8445)) +- MDX: Handle `` name starting with number ([#8469](https://github.com/storybookjs/storybook/pull/8469)) +- React: Fix custom themes breaking the welcome demo ([#8259](https://github.com/storybookjs/storybook/pull/8259)) +- Addon-jest: Make withTests type generic ([#8410](https://github.com/storybookjs/storybook/pull/8410)) ### Dependency Upgrades -* [Security] Bump lodash from 4.17.11 to 4.17.15 ([#8351](https://github.com/storybookjs/storybook/pull/8351)) +- [Security] Bump lodash from 4.17.11 to 4.17.15 ([#8351](https://github.com/storybookjs/storybook/pull/8351)) ### Dependency Upgrades -* Add "@types/react-textarea-autosize" to dependencies ([#8503](https://github.com/storybookjs/storybook/pull/8503)) +- Add "@types/react-textarea-autosize" to dependencies ([#8503](https://github.com/storybookjs/storybook/pull/8503)) ## 5.3.0-alpha.23 (October 19, 2019) ### Features -* Webpack: Enabled error details ([#8391](https://github.com/storybookjs/storybook/pull/8391)) +- Webpack: Enabled error details ([#8391](https://github.com/storybookjs/storybook/pull/8391)) ### Bug Fixes -* Addon-docs: Fix React.forwardedRef/memo props ([#8445](https://github.com/storybookjs/storybook/pull/8445)) -* MDX: Handle `` name starting with number ([#8469](https://github.com/storybookjs/storybook/pull/8469)) +- Addon-docs: Fix React.forwardedRef/memo props ([#8445](https://github.com/storybookjs/storybook/pull/8445)) +- MDX: Handle `` name starting with number ([#8469](https://github.com/storybookjs/storybook/pull/8469)) ### Maintenance -* ADD a step in circleci to upload storybook for angular ([#8477](https://github.com/storybookjs/storybook/pull/8477)) -* Core: Check module type in configure ([#8412](https://github.com/storybookjs/storybook/pull/8412)) -* Angular: Convert angular-cli stories to CSF ([#7668](https://github.com/storybookjs/storybook/pull/7668)) -* Typescript: Migrate @storybook/mithril ([#8320](https://github.com/storybookjs/storybook/pull/8320)) +- ADD a step in circleci to upload storybook for angular ([#8477](https://github.com/storybookjs/storybook/pull/8477)) +- Core: Check module type in configure ([#8412](https://github.com/storybookjs/storybook/pull/8412)) +- Angular: Convert angular-cli stories to CSF ([#7668](https://github.com/storybookjs/storybook/pull/7668)) +- Typescript: Migrate @storybook/mithril ([#8320](https://github.com/storybookjs/storybook/pull/8320)) ## 5.3.0-alpha.22 (October 18, 2019) ### Features -* CLI: Add component meta to framework templates ([#8462](https://github.com/storybookjs/storybook/pull/8462)) +- CLI: Add component meta to framework templates ([#8462](https://github.com/storybookjs/storybook/pull/8462)) ### Maintenance -* Build: Add cypress to Storybook CI ([#8397](https://github.com/storybookjs/storybook/pull/8397)) -* Incorrect minimal required node version ([#8427](https://github.com/storybookjs/storybook/pull/8427)) +- Build: Add cypress to Storybook CI ([#8397](https://github.com/storybookjs/storybook/pull/8397)) +- Incorrect minimal required node version ([#8427](https://github.com/storybookjs/storybook/pull/8427)) ## 5.3.0-alpha.21 (October 17, 2019) ### Features -* CLI: React-scripts TS template & test ([#8451](https://github.com/storybookjs/storybook/pull/8451)) -* Addon-storysource: Add preset ([#8437](https://github.com/storybookjs/storybook/pull/8437)) +- CLI: React-scripts TS template & test ([#8451](https://github.com/storybookjs/storybook/pull/8451)) +- Addon-storysource: Add preset ([#8437](https://github.com/storybookjs/storybook/pull/8437)) ### Bug Fixes -* UI: Fix React15 support ([#8454](https://github.com/storybookjs/storybook/pull/8454)) -* Addon-centered: Fix zoom issues for non-Firefox browsers ([#8442](https://github.com/storybookjs/storybook/pull/8442)) -* CLI: Add CRA preset to MDX template ([#8452](https://github.com/storybookjs/storybook/pull/8452)) -* CLI: Add preset-create-react-app to CRA presets template ([#8449](https://github.com/storybookjs/storybook/pull/8449)) -* CLI: Fix CRA-ts fixture package name ([#8457](https://github.com/storybookjs/storybook/pull/8457)) +- UI: Fix React15 support ([#8454](https://github.com/storybookjs/storybook/pull/8454)) +- Addon-centered: Fix zoom issues for non-Firefox browsers ([#8442](https://github.com/storybookjs/storybook/pull/8442)) +- CLI: Add CRA preset to MDX template ([#8452](https://github.com/storybookjs/storybook/pull/8452)) +- CLI: Add preset-create-react-app to CRA presets template ([#8449](https://github.com/storybookjs/storybook/pull/8449)) +- CLI: Fix CRA-ts fixture package name ([#8457](https://github.com/storybookjs/storybook/pull/8457)) ## 5.3.0-alpha.20 (October 15, 2019) ### Features -* Addon-docs: Support MDX source in Preview w/ no Story blocks ([#7966](https://github.com/storybookjs/storybook/pull/7966)) +- Addon-docs: Support MDX source in Preview w/ no Story blocks ([#7966](https://github.com/storybookjs/storybook/pull/7966)) ### Bug Fixes -* Addon-docs: Fix react inline stories ([#8419](https://github.com/storybookjs/storybook/pull/8419)) -* React: Fix custom themes breaking welcome demo ([#8259](https://github.com/storybookjs/storybook/pull/8259)) +- Addon-docs: Fix react inline stories ([#8419](https://github.com/storybookjs/storybook/pull/8419)) +- React: Fix custom themes breaking welcome demo ([#8259](https://github.com/storybookjs/storybook/pull/8259)) ## 5.3.0-alpha.19 (October 15, 2019) ### Features -* React: Move Create React App support to external preset ([#8416](https://github.com/storybookjs/storybook/pull/8416)) -* CLI: MDX template support ([#8396](https://github.com/storybookjs/storybook/pull/8396)) +- React: Move Create React App support to external preset ([#8416](https://github.com/storybookjs/storybook/pull/8416)) +- CLI: MDX template support ([#8396](https://github.com/storybookjs/storybook/pull/8396)) ### Bug Fixes -* Addon-jest: Make withTests type generic ([#8410](https://github.com/storybookjs/storybook/pull/8410)) -* Addon-docs: Don't error in React when there's no `prepareForInline` ([#8415](https://github.com/storybookjs/storybook/pull/8415)) +- Addon-jest: Make withTests type generic ([#8410](https://github.com/storybookjs/storybook/pull/8410)) +- Addon-docs: Don't error in React when there's no `prepareForInline` ([#8415](https://github.com/storybookjs/storybook/pull/8415)) ### Dependency Upgrades -* [Security] Bump lodash from 4.17.11 to 4.17.15 ([#8351](https://github.com/storybookjs/storybook/pull/8351)) +- [Security] Bump lodash from 4.17.11 to 4.17.15 ([#8351](https://github.com/storybookjs/storybook/pull/8351)) ## 5.3.0-alpha.18 (October 14, 2019) ### Bug Fixes -* CSF: Warn when CSF and `storiesOf` mixed in one file ([#8411](https://github.com/storybookjs/storybook/pull/8411)) -* Addon API: Clean preview hooks when removing a story ([#8408](https://github.com/storybookjs/storybook/pull/8408)) -* Addon-docs: Fix typo in default config ([#8403](https://github.com/storybookjs/storybook/pull/8403)) -* Angular: Fix angular2-template-loader / raw-loader version conflicts ([#8269](https://github.com/storybookjs/storybook/pull/8269)) -* CLI: Update button.svelte template ([#8369](https://github.com/storybookjs/storybook/pull/8369)) +- CSF: Warn when CSF and `storiesOf` mixed in one file ([#8411](https://github.com/storybookjs/storybook/pull/8411)) +- Addon API: Clean preview hooks when removing a story ([#8408](https://github.com/storybookjs/storybook/pull/8408)) +- Addon-docs: Fix typo in default config ([#8403](https://github.com/storybookjs/storybook/pull/8403)) +- Angular: Fix angular2-template-loader / raw-loader version conflicts ([#8269](https://github.com/storybookjs/storybook/pull/8269)) +- CLI: Update button.svelte template ([#8369](https://github.com/storybookjs/storybook/pull/8369)) ### Maintenance -* Official-storybook: Add a story with duplicate decorators ([#8407](https://github.com/storybookjs/storybook/pull/8407)) +- Official-storybook: Add a story with duplicate decorators ([#8407](https://github.com/storybookjs/storybook/pull/8407)) ## 5.2.4 (October 14, 2019) ### Bug Fixes -* Angular: Fix angular2-template-loader / raw-loader version conflicts ([#8269](https://github.com/storybookjs/storybook/pull/8269)) -* CSF: Warn when CSF and `storiesOf` mixed in one file ([#8411](https://github.com/storybookjs/storybook/pull/8411)) -* Addon API: Clean preview hooks when removing a story ([#8408](https://github.com/storybookjs/storybook/pull/8408)) -* Update button.svelte ([#8369](https://github.com/storybookjs/storybook/pull/8369)) -* CSF: Ignore __esModule export ([#8317](https://github.com/storybookjs/storybook/pull/8317)) +- Angular: Fix angular2-template-loader / raw-loader version conflicts ([#8269](https://github.com/storybookjs/storybook/pull/8269)) +- CSF: Warn when CSF and `storiesOf` mixed in one file ([#8411](https://github.com/storybookjs/storybook/pull/8411)) +- Addon API: Clean preview hooks when removing a story ([#8408](https://github.com/storybookjs/storybook/pull/8408)) +- Update button.svelte ([#8369](https://github.com/storybookjs/storybook/pull/8369)) +- CSF: Ignore \_\_esModule export ([#8317](https://github.com/storybookjs/storybook/pull/8317)) ### Maintenance -* Publish top-level .js and .d.ts files ([#8354](https://github.com/storybookjs/storybook/pull/8354)) +- Publish top-level .js and .d.ts files ([#8354](https://github.com/storybookjs/storybook/pull/8354)) ## 5.3.0-alpha.17 (October 10, 2019) ### Maintenance -* Publish top-level .js and .d.ts files ([#8354](https://github.com/storybookjs/storybook/pull/8354)) +- Publish top-level .js and .d.ts files ([#8354](https://github.com/storybookjs/storybook/pull/8354)) ## 5.3.0-alpha.16 (October 10, 2019) @@ -829,11 +2127,11 @@ Publish failed ### Features -* Addon-docs: support vue inline rendering ([#7929](https://github.com/storybookjs/storybook/pull/7929)) +- Addon-docs: support vue inline rendering ([#7929](https://github.com/storybookjs/storybook/pull/7929)) ### Maintenance -* Typescript: Migrate addon-storyshots ([#7674](https://github.com/storybookjs/storybook/pull/7674)) +- Typescript: Migrate addon-storyshots ([#7674](https://github.com/storybookjs/storybook/pull/7674)) ## 5.3.0-alpha.14 (October 8, 2019) @@ -843,92 +2141,92 @@ NPM publish failed ### Features -* MDX: Better ergonomics for documenting CSF ([#8312](https://github.com/storybookjs/storybook/pull/8312)) -* Addon-docs: Story parameter for disabling docs ([#8313](https://github.com/storybookjs/storybook/pull/8313)) +- MDX: Better ergonomics for documenting CSF ([#8312](https://github.com/storybookjs/storybook/pull/8312)) +- Addon-docs: Story parameter for disabling docs ([#8313](https://github.com/storybookjs/storybook/pull/8313)) ### Dependency Upgrades -* Remove redundant dependency on hoist-non-react-statics (#6349) ([#8310](https://github.com/storybookjs/storybook/pull/8310)) +- Remove redundant dependency on hoist-non-react-statics (#6349) ([#8310](https://github.com/storybookjs/storybook/pull/8310)) ## 5.3.0-alpha.12 (October 7, 2019) ### Features -* CSF: Allow multiple CSF with same title ([#8133](https://github.com/storybookjs/storybook/pull/8133)) +- CSF: Allow multiple CSF with same title ([#8133](https://github.com/storybookjs/storybook/pull/8133)) ### Bug Fixes -* CSF: Ignore __esModule export ([#8317](https://github.com/storybookjs/storybook/pull/8317)) -* React: Improve type of storyFn ([#8197](https://github.com/storybookjs/storybook/pull/8197)) +- CSF: Ignore \_\_esModule export ([#8317](https://github.com/storybookjs/storybook/pull/8317)) +- React: Improve type of storyFn ([#8197](https://github.com/storybookjs/storybook/pull/8197)) ## 5.2.3 (October 7, 2019) ### Bug Fixes -* Core: Fix lib/core whitelist ([#8182](https://github.com/storybookjs/storybook/pull/8182)) +- Core: Fix lib/core whitelist ([#8182](https://github.com/storybookjs/storybook/pull/8182)) ## 5.2.2 (October 7, 2019) ### Bug Fixes -* Storyshots: First-class CSF support ([#8000](https://github.com/storybookjs/storybook/pull/8000)) -* UI: Move addon dependencies to devDependencies ([#8206](https://github.com/storybookjs/storybook/pull/8206)) -* Addon-docs: CSS classes for escape-hatch theming wrapper/content ([#8061](https://github.com/storybookjs/storybook/pull/8061)) -* CLI: Fix variable collisions in storiesof-to-csf ([#8106](https://github.com/storybookjs/storybook/pull/8106)) -* Addon-knobs: Add missing type def #8105 ([#8118](https://github.com/storybookjs/storybook/pull/8118)) -* Dependencies: add @types/webpack-env to apps that depend on it ([#8119](https://github.com/storybookjs/storybook/pull/8119)) -* Core: Show exception rather than error on react error boundary ([#8100](https://github.com/storybookjs/storybook/pull/8100)) -* UI: Fix inline code styling for dark theme ([#8260](https://github.com/storybookjs/storybook/pull/8260)) -* Addon-ondevice-notes: Validate the state content ([#8261](https://github.com/storybookjs/storybook/pull/8261)) -* Telejson: New version with typings and bugfixes ([#8228](https://github.com/storybookjs/storybook/pull/8228)) -* React: Add DecoratorFn type to exports ([#8121](https://github.com/storybookjs/storybook/pull/8121)) -* Addon-knobs: Handle undefined array value ([#8006](https://github.com/storybookjs/storybook/pull/8006)) -* Preact: Allow JSX.Element story ([#8159](https://github.com/storybookjs/storybook/pull/8159)) -* Storyshots: Fix STORYBOOK_HOOKS_CONTEXT error ([#8163](https://github.com/storybookjs/storybook/pull/8163)) -* Update react-draggable to 4.0.3 ([#8145](https://github.com/storybookjs/storybook/pull/8145)) +- Storyshots: First-class CSF support ([#8000](https://github.com/storybookjs/storybook/pull/8000)) +- UI: Move addon dependencies to devDependencies ([#8206](https://github.com/storybookjs/storybook/pull/8206)) +- Addon-docs: CSS classes for escape-hatch theming wrapper/content ([#8061](https://github.com/storybookjs/storybook/pull/8061)) +- CLI: Fix variable collisions in storiesof-to-csf ([#8106](https://github.com/storybookjs/storybook/pull/8106)) +- Addon-knobs: Add missing type def #8105 ([#8118](https://github.com/storybookjs/storybook/pull/8118)) +- Dependencies: add @types/webpack-env to apps that depend on it ([#8119](https://github.com/storybookjs/storybook/pull/8119)) +- Core: Show exception rather than error on react error boundary ([#8100](https://github.com/storybookjs/storybook/pull/8100)) +- UI: Fix inline code styling for dark theme ([#8260](https://github.com/storybookjs/storybook/pull/8260)) +- Addon-ondevice-notes: Validate the state content ([#8261](https://github.com/storybookjs/storybook/pull/8261)) +- Telejson: New version with typings and bugfixes ([#8228](https://github.com/storybookjs/storybook/pull/8228)) +- React: Add DecoratorFn type to exports ([#8121](https://github.com/storybookjs/storybook/pull/8121)) +- Addon-knobs: Handle undefined array value ([#8006](https://github.com/storybookjs/storybook/pull/8006)) +- Preact: Allow JSX.Element story ([#8159](https://github.com/storybookjs/storybook/pull/8159)) +- Storyshots: Fix STORYBOOK_HOOKS_CONTEXT error ([#8163](https://github.com/storybookjs/storybook/pull/8163)) +- Update react-draggable to 4.0.3 ([#8145](https://github.com/storybookjs/storybook/pull/8145)) ## 5.3.0-alpha.11 (October 6, 2019) ### Bug Fixes -* Storyshots: Update `read-pkg-up` usage to work with version `7` ([#8299](https://github.com/storybookjs/storybook/pull/8299)) +- Storyshots: Update `read-pkg-up` usage to work with version `7` ([#8299](https://github.com/storybookjs/storybook/pull/8299)) ### Maintenance -* Remove `weak` dependency Node 12 ([#8300](https://github.com/storybookjs/storybook/pull/8300)) +- Remove `weak` dependency Node 12 ([#8300](https://github.com/storybookjs/storybook/pull/8300)) ## 5.3.0-alpha.10 (October 5, 2019) ### Bug Fixes -* Storyshots: First-class CSF support ([#8000](https://github.com/storybookjs/storybook/pull/8000)) +- Storyshots: First-class CSF support ([#8000](https://github.com/storybookjs/storybook/pull/8000)) ### Maintenance -* Build: Add CLI test in github actions ([#8064](https://github.com/storybookjs/storybook/pull/8064)) +- Build: Add CLI test in github actions ([#8064](https://github.com/storybookjs/storybook/pull/8064)) ## 5.3.0-alpha.9 (October 4, 2019) ### Features -* API: Add method to set manager config ([#8232](https://github.com/storybookjs/storybook/pull/8232)) +- API: Add method to set manager config ([#8232](https://github.com/storybookjs/storybook/pull/8232)) ### Bug Fixes -* Core: Show exception rather than error on react error boundary ([#8100](https://github.com/storybookjs/storybook/pull/8100)) -* Addon-knobs: Fix issues caused by rerenders ([#8287](https://github.com/storybookjs/storybook/pull/8287)) +- Core: Show exception rather than error on react error boundary ([#8100](https://github.com/storybookjs/storybook/pull/8100)) +- Addon-knobs: Fix issues caused by rerenders ([#8287](https://github.com/storybookjs/storybook/pull/8287)) ### Maintenance -* Fix local `yarn lint` ([#8289](https://github.com/storybookjs/storybook/pull/8289)) -* Fix button knob story ([#8282](https://github.com/storybookjs/storybook/pull/8282)) -* Official examples: apply babel-preset-minify only in production mode ([#8283](https://github.com/storybookjs/storybook/pull/8283)) -* Build: Upgrade chromatic to test ([#8246](https://github.com/storybookjs/storybook/pull/8246)) +- Fix local `yarn lint` ([#8289](https://github.com/storybookjs/storybook/pull/8289)) +- Fix button knob story ([#8282](https://github.com/storybookjs/storybook/pull/8282)) +- Official examples: apply babel-preset-minify only in production mode ([#8283](https://github.com/storybookjs/storybook/pull/8283)) +- Build: Upgrade chromatic to test ([#8246](https://github.com/storybookjs/storybook/pull/8246)) ### Dependency Upgrades -* Upgrade regenerator-runtime to 0.13 in all packages ([#8258](https://github.com/storybookjs/storybook/pull/8258)) -* Misc upgrades ([#8280](https://github.com/storybookjs/storybook/pull/8280)) +- Upgrade regenerator-runtime to 0.13 in all packages ([#8258](https://github.com/storybookjs/storybook/pull/8258)) +- Misc upgrades ([#8280](https://github.com/storybookjs/storybook/pull/8280)) ## 5.3.0-alpha.9 (October 4, 2019) @@ -938,103 +2236,103 @@ NPM publish failed ### Features -* UI: Preferred color scheme awareness ([#8271](https://github.com/storybookjs/storybook/pull/8271)) +- UI: Preferred color scheme awareness ([#8271](https://github.com/storybookjs/storybook/pull/8271)) ### Bug Fixes -* UI: Fix inline code styling for dark theme ([#8260](https://github.com/storybookjs/storybook/pull/8260)) +- UI: Fix inline code styling for dark theme ([#8260](https://github.com/storybookjs/storybook/pull/8260)) ### Maintenance -* Regenerate lockfile ([#8263](https://github.com/storybookjs/storybook/pull/8263)) +- Regenerate lockfile ([#8263](https://github.com/storybookjs/storybook/pull/8263)) ## 5.3.0-alpha.6 (October 1, 2019) ### Bug Fixes -* Addon-ondevice-notes: Validate the state content ([#8261](https://github.com/storybookjs/storybook/pull/8261)) -* API: Fix require cycles in addons hooks ([#8236](https://github.com/storybookjs/storybook/pull/8236)) -* API: Fix missing `channel.off` ([#8234](https://github.com/storybookjs/storybook/pull/8234)) -* Telejson: New version with typings and bugfixes ([#8228](https://github.com/storybookjs/storybook/pull/8228)) -* Addon-links: Update linkTo type to accept function ([#8117](https://github.com/storybookjs/storybook/pull/8117)) -* React: Add DecoratorFn type to exports ([#8121](https://github.com/storybookjs/storybook/pull/8121)) +- Addon-ondevice-notes: Validate the state content ([#8261](https://github.com/storybookjs/storybook/pull/8261)) +- API: Fix require cycles in addons hooks ([#8236](https://github.com/storybookjs/storybook/pull/8236)) +- API: Fix missing `channel.off` ([#8234](https://github.com/storybookjs/storybook/pull/8234)) +- Telejson: New version with typings and bugfixes ([#8228](https://github.com/storybookjs/storybook/pull/8228)) +- Addon-links: Update linkTo type to accept function ([#8117](https://github.com/storybookjs/storybook/pull/8117)) +- React: Add DecoratorFn type to exports ([#8121](https://github.com/storybookjs/storybook/pull/8121)) ### Maintenance -* Update angular example to v8 ([#7747](https://github.com/storybookjs/storybook/pull/7747)) -* Hitting some flakiness in now deploys, this might help ([#8200](https://github.com/storybookjs/storybook/pull/8200)) -* Remov hooks from extracted StoryItem ([#8256](https://github.com/storybookjs/storybook/pull/8256)) +- Update angular example to v8 ([#7747](https://github.com/storybookjs/storybook/pull/7747)) +- Hitting some flakiness in now deploys, this might help ([#8200](https://github.com/storybookjs/storybook/pull/8200)) +- Remov hooks from extracted StoryItem ([#8256](https://github.com/storybookjs/storybook/pull/8256)) ### Dependency Upgrades -* Bump react-native-swipe-gestures to 1.0.4 ([#8235](https://github.com/storybookjs/storybook/pull/8235)) +- Bump react-native-swipe-gestures to 1.0.4 ([#8235](https://github.com/storybookjs/storybook/pull/8235)) ## 5.3.0-alpha.5 (September 27, 2019) ### Maintenance -* UI: Improve code indentation ([#8218](https://github.com/storybookjs/storybook/pull/8218)) -* Use the extracted linting configs ([#8213](https://github.com/storybookjs/storybook/pull/8213)) +- UI: Improve code indentation ([#8218](https://github.com/storybookjs/storybook/pull/8218)) +- Use the extracted linting configs ([#8213](https://github.com/storybookjs/storybook/pull/8213)) ## 5.3.0-alpha.4 (September 26, 2019) ### Bug Fixes -* UI: Move addon dependencies to devDependencies ([#8206](https://github.com/storybookjs/storybook/pull/8206)) -* Addon-knobs: Handle undefined array value ([#8006](https://github.com/storybookjs/storybook/pull/8006)) +- UI: Move addon dependencies to devDependencies ([#8206](https://github.com/storybookjs/storybook/pull/8206)) +- Addon-knobs: Handle undefined array value ([#8006](https://github.com/storybookjs/storybook/pull/8006)) ### Maintenance -* Build: Upgrade from node8 to node10 ([#8207](https://github.com/storybookjs/storybook/pull/8207)) +- Build: Upgrade from node8 to node10 ([#8207](https://github.com/storybookjs/storybook/pull/8207)) ## 5.3.0-alpha.3 (September 25, 2019) ### Bug Fixes -* Dependencies: add @types/webpack-env to apps that depend on it ([#8119](https://github.com/storybookjs/storybook/pull/8119)) -* UI: Removes default CSS margins on viewport ([#7742](https://github.com/storybookjs/storybook/pull/7742)) +- Dependencies: add @types/webpack-env to apps that depend on it ([#8119](https://github.com/storybookjs/storybook/pull/8119)) +- UI: Removes default CSS margins on viewport ([#7742](https://github.com/storybookjs/storybook/pull/7742)) ## 5.3.0-alpha.2 (September 24, 2019) ### Bug Fixes -* Core: Fix lib/core files ([#8182](https://github.com/storybookjs/storybook/pull/8182)) +- Core: Fix lib/core files ([#8182](https://github.com/storybookjs/storybook/pull/8182)) ## 5.3.0-alpha.1 (September 23, 2019) ### Bug Fixes -* Preact: Allow JSX.Element story ([#8159](https://github.com/storybookjs/storybook/pull/8159)) -* Addon-docs: CSS classes for escape-hatch theming wrapper/content ([#8061](https://github.com/storybookjs/storybook/pull/8061)) -* CLI: Fix variable collisions in storiesof-to-csf ([#8106](https://github.com/storybookjs/storybook/pull/8106)) -* Storyshots: Fix STORYBOOK_HOOKS_CONTEXT error ([#8163](https://github.com/storybookjs/storybook/pull/8163)) -* Addon-contexts: Remove peer dependencies ([#7675](https://github.com/storybookjs/storybook/pull/7675)) -* Addon-knobs: Add missing type def ([#8118](https://github.com/storybookjs/storybook/pull/8118)) +- Preact: Allow JSX.Element story ([#8159](https://github.com/storybookjs/storybook/pull/8159)) +- Addon-docs: CSS classes for escape-hatch theming wrapper/content ([#8061](https://github.com/storybookjs/storybook/pull/8061)) +- CLI: Fix variable collisions in storiesof-to-csf ([#8106](https://github.com/storybookjs/storybook/pull/8106)) +- Storyshots: Fix STORYBOOK_HOOKS_CONTEXT error ([#8163](https://github.com/storybookjs/storybook/pull/8163)) +- Addon-contexts: Remove peer dependencies ([#7675](https://github.com/storybookjs/storybook/pull/7675)) +- Addon-knobs: Add missing type def ([#8118](https://github.com/storybookjs/storybook/pull/8118)) ### Dependency Upgrades -* Update react-draggable to 4.0.3 ([#8145](https://github.com/storybookjs/storybook/pull/8145)) +- Update react-draggable to 4.0.3 ([#8145](https://github.com/storybookjs/storybook/pull/8145)) ## 5.2.1 (September 17, 2019) ### Bug Fixes -* Core: Fix error handling ([#8097](https://github.com/storybookjs/storybook/pull/8097)) +- Core: Fix error handling ([#8097](https://github.com/storybookjs/storybook/pull/8097)) ## 5.3.0-alpha.0 (September 16, 2019) ### Features -* UI: Debounce sidebar search filter ([#8032](https://github.com/storybookjs/storybook/pull/8032)) +- UI: Debounce sidebar search filter ([#8032](https://github.com/storybookjs/storybook/pull/8032)) ### Bug Fixes -* Core: Fix error handling ([#8097](https://github.com/storybookjs/storybook/pull/8097)) +- Core: Fix error handling ([#8097](https://github.com/storybookjs/storybook/pull/8097)) ### Maintenance -* CLI: Add wrapper packages: sb & storybook ([#8034](https://github.com/storybookjs/storybook/pull/8034)) -* Build: Update now config ([#8049](https://github.com/storybookjs/storybook/pull/8049)) +- CLI: Add wrapper packages: sb & storybook ([#8034](https://github.com/storybookjs/storybook/pull/8034)) +- Build: Update now config ([#8049](https://github.com/storybookjs/storybook/pull/8049)) ## 5.2.0 (September 13, 2019) @@ -1045,40 +2343,40 @@ Storybook 5.2 is here! - 🖼 Design System: Best practice component development - 🧩 Addon API: Simplified w/ hooks -5.2 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.2.0-alpha.*`, `5.2.0-beta.*`, and `5.2.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier. + 5.2 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.2.0-alpha.*`, `5.2.0-beta.*`, and `5.2.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier. ## 5.2.0-rc.11 (September 10, 2019) ### Features -* DocsPage: show docs.storyDescription above story ([#8037](https://github.com/storybookjs/storybook/pull/8037)) +- DocsPage: show docs.storyDescription above story ([#8037](https://github.com/storybookjs/storybook/pull/8037)) ### Maintenance -* Migrate to new github actions ([#8045](https://github.com/storybookjs/storybook/pull/8045)) +- Migrate to new github actions ([#8045](https://github.com/storybookjs/storybook/pull/8045)) ## 5.2.0-rc.10 (September 9, 2019) ### Bug Fixes -* Angular: remove webpack-env from tsconfig types ([#8036](https://github.com/storybookjs/storybook/pull/8036)) +- Angular: remove webpack-env from tsconfig types ([#8036](https://github.com/storybookjs/storybook/pull/8036)) ### Documentation -* Guides: Update all guides to CSF ([#8030](https://github.com/storybookjs/storybook/pull/8030)) +- Guides: Update all guides to CSF ([#8030](https://github.com/storybookjs/storybook/pull/8030)) ## 5.2.0-rc.9 (September 7, 2019) ### Bug Fixes -* Addon-knobs: allow array values in select and options knobs ([#8027](https://github.com/storybookjs/storybook/pull/8027)) +- Addon-knobs: allow array values in select and options knobs ([#8027](https://github.com/storybookjs/storybook/pull/8027)) ## 5.2.0-rc.8 (September 6, 2019) ### Bug Fixes -* Addon-docs: Scroll story into view ([#7994](https://github.com/storybookjs/storybook/pull/7994)) -* Addon-docs: Fix LI styling for dark color theme ([#8015](https://github.com/storybookjs/storybook/pull/8015)) +- Addon-docs: Scroll story into view ([#7994](https://github.com/storybookjs/storybook/pull/7994)) +- Addon-docs: Fix LI styling for dark color theme ([#8015](https://github.com/storybookjs/storybook/pull/8015)) ## 5.2.0-rc.7 (September 6, 2019) @@ -1088,22 +2386,22 @@ Publish failed ### Bug Fixes -* Addon-docs: Doc blocks fixes for dark theme ([#7991](https://github.com/storybookjs/storybook/pull/7991)) -* API: Fix useEfect in inline Docs ([#7992](https://github.com/storybookjs/storybook/pull/7992)) -* UI: Fix enableShortcuts option ([#7990](https://github.com/storybookjs/storybook/pull/7990)) -* Addon-docs: Error handling for invalid Story id ([#7965](https://github.com/storybookjs/storybook/pull/7965)) +- Addon-docs: Doc blocks fixes for dark theme ([#7991](https://github.com/storybookjs/storybook/pull/7991)) +- API: Fix useEfect in inline Docs ([#7992](https://github.com/storybookjs/storybook/pull/7992)) +- UI: Fix enableShortcuts option ([#7990](https://github.com/storybookjs/storybook/pull/7990)) +- Addon-docs: Error handling for invalid Story id ([#7965](https://github.com/storybookjs/storybook/pull/7965)) ## 5.2.0-rc.5 (September 4, 2019) ### Bug Fixes -* CLI: Force welcome stories to show up first in load order ([#7979](https://github.com/storybookjs/storybook/pull/7979)) +- CLI: Force welcome stories to show up first in load order ([#7979](https://github.com/storybookjs/storybook/pull/7979)) ## 5.2.0-rc.4 (September 3, 2019) ### Bug Fixes -* Addon-docs: Fix MDX theme bleed with reset context ([#7974](https://github.com/storybookjs/storybook/pull/7974)) +- Addon-docs: Fix MDX theme bleed with reset context ([#7974](https://github.com/storybookjs/storybook/pull/7974)) ## 5.2.0-rc.3 (September 3, 2019) @@ -1113,20 +2411,20 @@ Failed publish ### Bug Fixes -* Addon-docs: Fix inline stories using react hooks ([#7946](https://github.com/storybookjs/storybook/pull/7946)) +- Addon-docs: Fix inline stories using react hooks ([#7946](https://github.com/storybookjs/storybook/pull/7946)) ## 5.2.0-rc.1 (August 31, 2019) ### Features -* Core: Add html lang attribute to iframe ([#7892](https://github.com/storybookjs/storybook/pull/7892)) +- Core: Add html lang attribute to iframe ([#7892](https://github.com/storybookjs/storybook/pull/7892)) ### Bug Fixes -* Source-loader: Fix CSF display name handling ([#7940](https://github.com/storybookjs/storybook/pull/7940)) -* React-native: Resolve deprecated methods ([#7908](https://github.com/storybookjs/storybook/pull/7908)) -* Addon-viewport: keep styles on rotation-change ([#7683](https://github.com/storybookjs/storybook/pull/7683)) -* Addon-viewport: Fix defaultViewport ([#7934](https://github.com/storybookjs/storybook/pull/7934)) +- Source-loader: Fix CSF display name handling ([#7940](https://github.com/storybookjs/storybook/pull/7940)) +- React-native: Resolve deprecated methods ([#7908](https://github.com/storybookjs/storybook/pull/7908)) +- Addon-viewport: keep styles on rotation-change ([#7683](https://github.com/storybookjs/storybook/pull/7683)) +- Addon-viewport: Fix defaultViewport ([#7934](https://github.com/storybookjs/storybook/pull/7934)) ## 5.2.0-rc.0 (August 30, 2019) @@ -1142,22 +2440,22 @@ Track progress on the [5.2 release issue](https://github.com/storybookjs/storybo ### Bug Fixes -* Addon-docs: Fix docs-only story ID suffix ([#7921](https://github.com/storybookjs/storybook/pull/7921)) +- Addon-docs: Fix docs-only story ID suffix ([#7921](https://github.com/storybookjs/storybook/pull/7921)) ### Maintenance -* Replace favicon with new Storybook icon. ([#7922](https://github.com/storybookjs/storybook/pull/7922)) +- Replace favicon with new Storybook icon. ([#7922](https://github.com/storybookjs/storybook/pull/7922)) ### Dependency Upgrades -* Bump react-draggable from 3.1.1 to 3.3.2 ([#7912](https://github.com/storybookjs/storybook/pull/7912)) +- Bump react-draggable from 3.1.1 to 3.3.2 ([#7912](https://github.com/storybookjs/storybook/pull/7912)) ## 5.2.0-beta.47 (August 30, 2019) ### Bug Fixes -* UI: Optimize treeview render/filter performance ([#7910](https://github.com/storybookjs/storybook/pull/7910)) -* Addon-docs: Fix DocsPage to respect displayName ([#7915](https://github.com/storybookjs/storybook/pull/7915)) +- UI: Optimize treeview render/filter performance ([#7910](https://github.com/storybookjs/storybook/pull/7910)) +- Addon-docs: Fix DocsPage to respect displayName ([#7915](https://github.com/storybookjs/storybook/pull/7915)) ## 5.2.0-beta.46 (August 29, 2019) @@ -1165,18 +2463,18 @@ CSF users: this reverts the `makeDisplayName` change introduced in `5.2.0-beta.4 ### Bug Fixes -* React: Fix missing props in the Welcome.tsx demo ([#7774](https://github.com/storybookjs/storybook/pull/7774)) +- React: Fix missing props in the Welcome.tsx demo ([#7774](https://github.com/storybookjs/storybook/pull/7774)) ### Maintenance -* CSF: Revert `makeDisplayName` & add stable `storyNameFromExport` ([#7901](https://github.com/storybookjs/storybook/pull/7901)) -* Addon-a11y: Upgrade axe to 3.3.2 which adds/mods rules ([#7888](https://github.com/storybookjs/storybook/pull/7888)) +- CSF: Revert `makeDisplayName` & add stable `storyNameFromExport` ([#7901](https://github.com/storybookjs/storybook/pull/7901)) +- Addon-a11y: Upgrade axe to 3.3.2 which adds/mods rules ([#7888](https://github.com/storybookjs/storybook/pull/7888)) ## 5.2.0-beta.45 (August 28, 2019) ### Bug Fixes -* Addon-docs: Fix MDX Story ID to match new CSF ([#7894](https://github.com/storybookjs/storybook/pull/7894)) +- Addon-docs: Fix MDX Story ID to match new CSF ([#7894](https://github.com/storybookjs/storybook/pull/7894)) ## 5.2.0-beta.44 (August 28, 2019) @@ -1187,12 +2485,12 @@ Publish failed CSF users: This is potentially a breaking change. If you want to opt-out of the new default display name calculation (`lodash.startCase`) you can add the following to your SB config: ```js -addParameters({ options: { makeDisplayName: key => key }}); +addParameters({ options: { makeDisplayName: (key) => key } }); ``` ### Features -* CSF: Transform CSF named exports w/ `makeDisplayName` ([#7878](https://github.com/storybookjs/storybook/pull/7878)) +- CSF: Transform CSF named exports w/ `makeDisplayName` ([#7878](https://github.com/storybookjs/storybook/pull/7878)) ## 5.2.0-beta.42 (August 28, 2019) @@ -1200,69 +2498,69 @@ Addon-docs users: This is a breaking change if you have been hacking the `docs` ### Maintenance -* Addon-docs: Make config API consistent with other addons ([#7874](https://github.com/storybookjs/storybook/pull/7874)) +- Addon-docs: Make config API consistent with other addons ([#7874](https://github.com/storybookjs/storybook/pull/7874)) ## 5.2.0-beta.41 (August 27, 2019) ### Features -* Addon-info: Add configurable component comparator ([#7409](https://github.com/storybookjs/storybook/pull/7409)) +- Addon-info: Add configurable component comparator ([#7409](https://github.com/storybookjs/storybook/pull/7409)) ### Bug Fixes -* Addon-viewports: Add back default viewports ([#7448](https://github.com/storybookjs/storybook/pull/7448)) -* Core: Pass a separate hooks context per story ([#7860](https://github.com/storybookjs/storybook/pull/7860)) -* UI: Fix TooltipLinkList not scrollable ([#7865](https://github.com/storybookjs/storybook/pull/7865)) +- Addon-viewports: Add back default viewports ([#7448](https://github.com/storybookjs/storybook/pull/7448)) +- Core: Pass a separate hooks context per story ([#7860](https://github.com/storybookjs/storybook/pull/7860)) +- UI: Fix TooltipLinkList not scrollable ([#7865](https://github.com/storybookjs/storybook/pull/7865)) ### Maintenance -* UI: Add ignore `first-child` selector warning flag ([#7861](https://github.com/storybookjs/storybook/pull/7861)) +- UI: Add ignore `first-child` selector warning flag ([#7861](https://github.com/storybookjs/storybook/pull/7861)) ## 5.2.0-beta.40 (August 23, 2019) ### Bug Fixes -* Addon-docs: Fix DocsPage primary story switching ([#7849](https://github.com/storybookjs/storybook/pull/7849)) -* Source-loader: Selectively ignore typescript errors in generated code ([#7845](https://github.com/storybookjs/storybook/pull/7845)) -* Addon-docs: Force hidden attribute on #root element ([#7841](https://github.com/storybookjs/storybook/pull/7841)) +- Addon-docs: Fix DocsPage primary story switching ([#7849](https://github.com/storybookjs/storybook/pull/7849)) +- Source-loader: Selectively ignore typescript errors in generated code ([#7845](https://github.com/storybookjs/storybook/pull/7845)) +- Addon-docs: Force hidden attribute on #root element ([#7841](https://github.com/storybookjs/storybook/pull/7841)) ### Maintenance -* Remove temporarily added --no-dll option on examples ([#7647](https://github.com/storybookjs/storybook/pull/7647)) +- Remove temporarily added --no-dll option on examples ([#7647](https://github.com/storybookjs/storybook/pull/7647)) ## 5.2.0-beta.39 (August 22, 2019) ### Bug Fixes -* React-Native: Fix tabs layout issue on simulator ([#7809](https://github.com/storybookjs/storybook/pull/7809)) -* Source-loader: Selectively ignore typescript errors in generated code ([#7831](https://github.com/storybookjs/storybook/pull/7831)) +- React-Native: Fix tabs layout issue on simulator ([#7809](https://github.com/storybookjs/storybook/pull/7809)) +- Source-loader: Selectively ignore typescript errors in generated code ([#7831](https://github.com/storybookjs/storybook/pull/7831)) ## 5.2.0-beta.38 (August 21, 2019) ### Bug Fixes -* FIX issue where the block of build info (including url) wouldn't show if passed --ci #7821 ([#7822](https://github.com/storybookjs/storybook/pull/7822)) +- FIX issue where the block of build info (including url) wouldn't show if passed --ci #7821 ([#7822](https://github.com/storybookjs/storybook/pull/7822)) ### Maintenance -* CLI: Remove babel-register in favor of esm ([#7823](https://github.com/storybookjs/storybook/pull/7823)) +- CLI: Remove babel-register in favor of esm ([#7823](https://github.com/storybookjs/storybook/pull/7823)) ## 5.2.0-beta.37 (August 20, 2019) ### Breaking Changes -* Addon-docs: Add docsContainer parameter ([#7814](https://github.com/storybookjs/storybook/pull/7814)) +- Addon-docs: Add docsContainer parameter ([#7814](https://github.com/storybookjs/storybook/pull/7814)) ## 5.2.0-beta.36 (August 20, 2019) ### Features -* Addon-docs: Improved "No docs" message ([#7785](https://github.com/storybookjs/storybook/pull/7785)) -* Core: Add .pdf support to file-loader ([#7651](https://github.com/storybookjs/storybook/pull/7651)) +- Addon-docs: Improved "No docs" message ([#7785](https://github.com/storybookjs/storybook/pull/7785)) +- Core: Add .pdf support to file-loader ([#7651](https://github.com/storybookjs/storybook/pull/7651)) ### Bug Fixes -* Preview hooks: trigger effects after story render ([#7791](https://github.com/storybookjs/storybook/pull/7791)) +- Preview hooks: trigger effects after story render ([#7791](https://github.com/storybookjs/storybook/pull/7791)) ## 5.2.0-beta.35 (August 20, 2019) @@ -1276,67 +2574,67 @@ Failed publish ### Features -* Addon-jest: Add pending & todo items ([#7793](https://github.com/storybookjs/storybook/pull/7793)) +- Addon-jest: Add pending & todo items ([#7793](https://github.com/storybookjs/storybook/pull/7793)) ### Bug Fixes -* Addon-docs: Fix unique key warning ([#7796](https://github.com/storybookjs/storybook/pull/7796)) +- Addon-docs: Fix unique key warning ([#7796](https://github.com/storybookjs/storybook/pull/7796)) ## 5.2.0-beta.32 (August 17, 2019) ### Bug Fixes -* Addon-docs: Fix docs-only story load ([#7787](https://github.com/storybookjs/storybook/pull/7787)) +- Addon-docs: Fix docs-only story load ([#7787](https://github.com/storybookjs/storybook/pull/7787)) ### Maintenance -* Addon-docs: Unwrap doc blocks stories ([#7788](https://github.com/storybookjs/storybook/pull/7788)) +- Addon-docs: Unwrap doc blocks stories ([#7788](https://github.com/storybookjs/storybook/pull/7788)) ## 5.2.0-beta.31 (August 16, 2019) ### Features -* Addon-docs: Add toolbar support to Preview ([#7778](https://github.com/storybookjs/storybook/pull/7778)) +- Addon-docs: Add toolbar support to Preview ([#7778](https://github.com/storybookjs/storybook/pull/7778)) ### Bug Fixes -* Addon-docs: Fix CSS bleed issue in doc blocks ([#7771](https://github.com/storybookjs/storybook/pull/7771)) +- Addon-docs: Fix CSS bleed issue in doc blocks ([#7771](https://github.com/storybookjs/storybook/pull/7771)) ## 5.2.0-beta.30 (August 14, 2019) ### Features -* CLI: Don't output startup information on smoke test ([#6949](https://github.com/storybookjs/storybook/pull/6949)) -* Ondevice-knobs: Expose withKnobs from addon-knobs ([#7555](https://github.com/storybookjs/storybook/pull/7555)) +- CLI: Don't output startup information on smoke test ([#6949](https://github.com/storybookjs/storybook/pull/6949)) +- Ondevice-knobs: Expose withKnobs from addon-knobs ([#7555](https://github.com/storybookjs/storybook/pull/7555)) ### Maintenance -* Addon-Jest: Add flexibility ([#7748](https://github.com/storybookjs/storybook/pull/7748)) +- Addon-Jest: Add flexibility ([#7748](https://github.com/storybookjs/storybook/pull/7748)) ## 5.1.11 (August 13, 2019) ### Bug Fixes -* Core: Add polyfill for fetch ([#7401](https://github.com/storybookjs/storybook/pull/7401)) -* Core: Revert "Fix #7167 addon-centered causes component to disappear when zooming" ([#7750](https://github.com/storybookjs/storybook/pull/7750)) +- Core: Add polyfill for fetch ([#7401](https://github.com/storybookjs/storybook/pull/7401)) +- Core: Revert "Fix #7167 addon-centered causes component to disappear when zooming" ([#7750](https://github.com/storybookjs/storybook/pull/7750)) ## 5.2.0-beta.29 (August 13, 2019) ### Breaking Changes -* Addon-docs: Packaging for 5.2 release ([#7741](https://github.com/storybookjs/storybook/pull/7741)) +- Addon-docs: Packaging for 5.2 release ([#7741](https://github.com/storybookjs/storybook/pull/7741)) ### Bug Fixes -* Core: Revert "Fix #7167 addon-centered causes component to disappear when zooming" ([#7750](https://github.com/storybookjs/storybook/pull/7750)) -* Core: Revert "Addon-centered: Fix disappearing when zoomed" ([#7749](https://github.com/storybookjs/storybook/pull/7749)) +- Core: Revert "Fix #7167 addon-centered causes component to disappear when zooming" ([#7750](https://github.com/storybookjs/storybook/pull/7750)) +- Core: Revert "Addon-centered: Fix disappearing when zoomed" ([#7749](https://github.com/storybookjs/storybook/pull/7749)) ## 5.2.0-beta.28 (August 10, 2019) ### Features -* Core: Disable CRA behaviors when preset detected ([#7696](https://github.com/storybookjs/storybook/pull/7696)) -* Addon-docs: Docs only MDX and navigation UI ([#7719](https://github.com/storybookjs/storybook/pull/7719)) +- Core: Disable CRA behaviors when preset detected ([#7696](https://github.com/storybookjs/storybook/pull/7696)) +- Addon-docs: Docs only MDX and navigation UI ([#7719](https://github.com/storybookjs/storybook/pull/7719)) ## 5.2.0-beta.27 (August 10, 2019) @@ -1346,159 +2644,159 @@ Publish failed ### Features -* UI: Improved component nav UI ([#7716](https://github.com/storybookjs/storybook/pull/7716)) +- UI: Improved component nav UI ([#7716](https://github.com/storybookjs/storybook/pull/7716)) ## 5.2.0-beta.25 (August 8, 2019) ### Features -* Addon-centered: ability to disable on specific story ([#7709](https://github.com/storybookjs/storybook/pull/7709)) +- Addon-centered: ability to disable on specific story ([#7709](https://github.com/storybookjs/storybook/pull/7709)) ### Bug Fixes -* Addon-jest: fix console warning ([#7705](https://github.com/storybookjs/storybook/pull/7705)) -* Core: Remove Object.append(...) from index.ejs ([#7707](https://github.com/storybookjs/storybook/pull/7707)) +- Addon-jest: fix console warning ([#7705](https://github.com/storybookjs/storybook/pull/7705)) +- Core: Remove Object.append(...) from index.ejs ([#7707](https://github.com/storybookjs/storybook/pull/7707)) ## 5.2.0-beta.24 (August 7, 2019) ### Features -* Addon-docs: DocsPage slots for fine-grained user control ([#7680](https://github.com/storybookjs/storybook/pull/7680)) +- Addon-docs: DocsPage slots for fine-grained user control ([#7680](https://github.com/storybookjs/storybook/pull/7680)) ### Bug Fixes -* Core: Add polyfill for fetch ([#7401](https://github.com/storybookjs/storybook/pull/7401)) +- Core: Add polyfill for fetch ([#7401](https://github.com/storybookjs/storybook/pull/7401)) ### Dependency Upgrades -* Addon-notes: Upgrade markdown-to-jsx dependency ([#7694](https://github.com/storybookjs/storybook/pull/7694)) +- Addon-notes: Upgrade markdown-to-jsx dependency ([#7694](https://github.com/storybookjs/storybook/pull/7694)) ## 5.2.0-beta.23 (August 6, 2019) ### Features -* CLI: Suggest possible matches on invalid command ([#7658](https://github.com/storybookjs/storybook/pull/7658)) -* Core: Allow injecting preview-body.html ([#7417](https://github.com/storybookjs/storybook/pull/7417)) +- CLI: Suggest possible matches on invalid command ([#7658](https://github.com/storybookjs/storybook/pull/7658)) +- Core: Allow injecting preview-body.html ([#7417](https://github.com/storybookjs/storybook/pull/7417)) ### Bug Fixes -* Core: Fix HMR error recovery ([#7684](https://github.com/storybookjs/storybook/pull/7684)) +- Core: Fix HMR error recovery ([#7684](https://github.com/storybookjs/storybook/pull/7684)) ## 5.2.0-beta.22 (August 3, 2019) ### Bug Fixes -* Core: Fix HMR for CSF files ([#7669](https://github.com/storybookjs/storybook/pull/7669)) +- Core: Fix HMR for CSF files ([#7669](https://github.com/storybookjs/storybook/pull/7669)) ### Maintenance -* React: Cleanup CSF stories in cra-kitchen-sink ([#7670](https://github.com/storybookjs/storybook/pull/7670)) +- React: Cleanup CSF stories in cra-kitchen-sink ([#7670](https://github.com/storybookjs/storybook/pull/7670)) ## 5.2.0-beta.21 (August 3, 2019) ### Features -* Addon-jest: Add placeholder info for missing tests ([#7660](https://github.com/storybookjs/storybook/pull/7660)) +- Addon-jest: Add placeholder info for missing tests ([#7660](https://github.com/storybookjs/storybook/pull/7660)) ### Bug Fixes -* Addon-docs: Pass framework configs through babel ([#7667](https://github.com/storybookjs/storybook/pull/7667)) -* Addon-centered: Fix disappearing when zoomed ([#7640](https://github.com/storybookjs/storybook/pull/7640)) +- Addon-docs: Pass framework configs through babel ([#7667](https://github.com/storybookjs/storybook/pull/7667)) +- Addon-centered: Fix disappearing when zoomed ([#7640](https://github.com/storybookjs/storybook/pull/7640)) ## 5.2.0-beta.20 (August 1, 2019) ### Bug Fixes -* Addon-docs: Pass everything through babel & partial TS conversion ([#7653](https://github.com/storybookjs/storybook/pull/7653)) -* Addon-docs: Error on non-string description ([#7650](https://github.com/storybookjs/storybook/pull/7650)) -* CLI: Fix `npx -p @storybook/cli sb migrate` in yarn project ([#7649](https://github.com/storybookjs/storybook/pull/7649)) +- Addon-docs: Pass everything through babel & partial TS conversion ([#7653](https://github.com/storybookjs/storybook/pull/7653)) +- Addon-docs: Error on non-string description ([#7650](https://github.com/storybookjs/storybook/pull/7650)) +- CLI: Fix `npx -p @storybook/cli sb migrate` in yarn project ([#7649](https://github.com/storybookjs/storybook/pull/7649)) ## 5.2.0-beta.19 (July 31, 2019) ### Bug Fixes -* Core: Change webpack-defined variables to globals ([#7622](https://github.com/storybookjs/storybook/pull/7622)) -* Addon-notes: Fix dark theming bug ([#7623](https://github.com/storybookjs/storybook/pull/7623)) -* CLI: Fix usage information ([#7627](https://github.com/storybookjs/storybook/pull/7627)) -* CLI: Show additional package information with `sb info` ([#7624](https://github.com/storybookjs/storybook/pull/7624)) +- Core: Change webpack-defined variables to globals ([#7622](https://github.com/storybookjs/storybook/pull/7622)) +- Addon-notes: Fix dark theming bug ([#7623](https://github.com/storybookjs/storybook/pull/7623)) +- CLI: Fix usage information ([#7627](https://github.com/storybookjs/storybook/pull/7627)) +- CLI: Show additional package information with `sb info` ([#7624](https://github.com/storybookjs/storybook/pull/7624)) ### Maintenance -* Typescript: Migrate Preact ([#7527](https://github.com/storybookjs/storybook/pull/7527)) +- Typescript: Migrate Preact ([#7527](https://github.com/storybookjs/storybook/pull/7527)) ## 5.1.10 (July 31, 2019) ### Breaking Changes -* Core: Remove project root `babel.config.js` loading ([#7573](https://github.com/storybookjs/storybook/pull/7573)) +- Core: Remove project root `babel.config.js` loading ([#7573](https://github.com/storybookjs/storybook/pull/7573)) ### Bug Fixes -* Addon-info: change stylesheetBase info height from 110vh to 100vh ([#7141](https://github.com/storybookjs/storybook/pull/7141)) -* React-native: Fix react native server ([#7187](https://github.com/storybookjs/storybook/pull/7187)) -* Addon-centered/contexts: Move optionalDependencies to peerDependencies ([#7315](https://github.com/storybookjs/storybook/pull/7315)) -* Addon-notes/info: Fix indenting on markdown code blocks ([#7158](https://github.com/storybookjs/storybook/pull/7158)) -* Addon-actions: fix serialization performance ([#7256](https://github.com/storybookjs/storybook/pull/7256)) -* Addon-notes: Fix dark theming bug ([#7623](https://github.com/storybookjs/storybook/pull/7623)) -* CLI: Fix usage information ([#7627](https://github.com/storybookjs/storybook/pull/7627)) -* Addon-centered: Fix component disappearing on zoom ([#7400](https://github.com/storybookjs/storybook/pull/7400)) -* Addon-analytics: Fix API signature ([#7410](https://github.com/storybookjs/storybook/pull/7410)) -* UI: Fix Sidebar input refresh on 'Enter' ([#7342](https://github.com/storybookjs/storybook/pull/7342)) -* Addon-knobs: Prevent rerender when a button callback returns false. ([#7197](https://github.com/storybookjs/storybook/pull/7197)) -* Core: Keep story data and legacy data in sync ([#7319](https://github.com/storybookjs/storybook/pull/7319)) -* CLI: Move the free port logic so that loadOptions don't override it ([#7237](https://github.com/storybookjs/storybook/pull/7237)) -* Addon-backgrounds: Fix unstretched preview background wrapper ([#7173](https://github.com/storybookjs/storybook/pull/7173)) +- Addon-info: change stylesheetBase info height from 110vh to 100vh ([#7141](https://github.com/storybookjs/storybook/pull/7141)) +- React-native: Fix react native server ([#7187](https://github.com/storybookjs/storybook/pull/7187)) +- Addon-centered/contexts: Move optionalDependencies to peerDependencies ([#7315](https://github.com/storybookjs/storybook/pull/7315)) +- Addon-notes/info: Fix indenting on markdown code blocks ([#7158](https://github.com/storybookjs/storybook/pull/7158)) +- Addon-actions: fix serialization performance ([#7256](https://github.com/storybookjs/storybook/pull/7256)) +- Addon-notes: Fix dark theming bug ([#7623](https://github.com/storybookjs/storybook/pull/7623)) +- CLI: Fix usage information ([#7627](https://github.com/storybookjs/storybook/pull/7627)) +- Addon-centered: Fix component disappearing on zoom ([#7400](https://github.com/storybookjs/storybook/pull/7400)) +- Addon-analytics: Fix API signature ([#7410](https://github.com/storybookjs/storybook/pull/7410)) +- UI: Fix Sidebar input refresh on 'Enter' ([#7342](https://github.com/storybookjs/storybook/pull/7342)) +- Addon-knobs: Prevent rerender when a button callback returns false. ([#7197](https://github.com/storybookjs/storybook/pull/7197)) +- Core: Keep story data and legacy data in sync ([#7319](https://github.com/storybookjs/storybook/pull/7319)) +- CLI: Move the free port logic so that loadOptions don't override it ([#7237](https://github.com/storybookjs/storybook/pull/7237)) +- Addon-backgrounds: Fix unstretched preview background wrapper ([#7173](https://github.com/storybookjs/storybook/pull/7173)) ### Maintenance -* Build: delete tests & snapshots from dist ([#7358](https://github.com/storybookjs/storybook/pull/7358)) +- Build: delete tests & snapshots from dist ([#7358](https://github.com/storybookjs/storybook/pull/7358)) ### Dependency Upgrades -* CLI: replaced merge-dirs dependency by fs-extra ([#7100](https://github.com/storybookjs/storybook/pull/7100)) +- CLI: replaced merge-dirs dependency by fs-extra ([#7100](https://github.com/storybookjs/storybook/pull/7100)) ## 5.2.0-beta.18 (July 30, 2019) ### Bug Fixes -* Addon-docs: Ensure getNotes/getInfo return string ([#7597](https://github.com/storybookjs/storybook/pull/7597)) -* React/Angular/HTML/Vue: Fix typings for `configure` ([#7598](https://github.com/storybookjs/storybook/pull/7598)) +- Addon-docs: Ensure getNotes/getInfo return string ([#7597](https://github.com/storybookjs/storybook/pull/7597)) +- React/Angular/HTML/Vue: Fix typings for `configure` ([#7598](https://github.com/storybookjs/storybook/pull/7598)) ### Maintenance -* Typescript: Migrate Vue ([#7578](https://github.com/storybookjs/storybook/pull/7578)) +- Typescript: Migrate Vue ([#7578](https://github.com/storybookjs/storybook/pull/7578)) ### Dependency Upgrades -* Upgrade corejs-upgrade-webpack-plugin lazy-universal-dotenv ([#7592](https://github.com/storybookjs/storybook/pull/7592)) +- Upgrade corejs-upgrade-webpack-plugin lazy-universal-dotenv ([#7592](https://github.com/storybookjs/storybook/pull/7592)) ## 5.2.0-beta.17 (July 29, 2019) ### Bug Fixes -* CLI: Fix storiesof-to-csf migration w/punctuation ([#7590](https://github.com/storybookjs/storybook/pull/7590)) +- CLI: Fix storiesof-to-csf migration w/punctuation ([#7590](https://github.com/storybookjs/storybook/pull/7590)) ## 5.2.0-beta.16 (July 29, 2019) ### Features -* Addon-docs: Configure jsx automagically ([#7581](https://github.com/storybookjs/storybook/pull/7581)) -* Addon-docs: Add DocsPage automagically ([#7579](https://github.com/storybookjs/storybook/pull/7579)) +- Addon-docs: Configure jsx automagically ([#7581](https://github.com/storybookjs/storybook/pull/7581)) +- Addon-docs: Add DocsPage automagically ([#7579](https://github.com/storybookjs/storybook/pull/7579)) ## 5.2.0-beta.15 (July 29, 2019) ### Features -* CRA: Add support for TSX in config folder ([#7566](https://github.com/storybookjs/storybook/pull/7566)) +- CRA: Add support for TSX in config folder ([#7566](https://github.com/storybookjs/storybook/pull/7566)) ### Bug Fixes -* Addon-info: only render Components in propTables ([#7477](https://github.com/storybookjs/storybook/pull/7477)) +- Addon-info: only render Components in propTables ([#7477](https://github.com/storybookjs/storybook/pull/7477)) ### Dependency Upgrades -* Update telejson dependency to 2.2.2 ([#7586](https://github.com/storybookjs/storybook/pull/7586)) -* Angular: Fix @angular-devkit/build-angular version ([#7585](https://github.com/storybookjs/storybook/pull/7585)) +- Update telejson dependency to 2.2.2 ([#7586](https://github.com/storybookjs/storybook/pull/7586)) +- Angular: Fix @angular-devkit/build-angular version ([#7585](https://github.com/storybookjs/storybook/pull/7585)) ## 5.2.0-beta.14 (July 29, 2019) @@ -1512,18 +2810,18 @@ If you're not using SB Docs, you probably don't have to worry about this. ### Breaking Changes -* Core: Rename `load` to `configure` ([#7576](https://github.com/storybookjs/storybook/pull/7576)) +- Core: Rename `load` to `configure` ([#7576](https://github.com/storybookjs/storybook/pull/7576)) ## 5.2.0-beta.12 (July 27, 2019) ### Bug Fixes -* Addon-knobs: Fix select knob default selection when using array values ([#7568](https://github.com/storybookjs/storybook/pull/7568)) -* Addon-knobs: Call onChanges only for changed props ([#6884](https://github.com/storybookjs/storybook/pull/6884)) +- Addon-knobs: Fix select knob default selection when using array values ([#7568](https://github.com/storybookjs/storybook/pull/7568)) +- Addon-knobs: Call onChanges only for changed props ([#6884](https://github.com/storybookjs/storybook/pull/6884)) ### Maintenance -* Angular: Print error message if angular.json has no default project specified ([#7574](https://github.com/storybookjs/storybook/pull/7574)) +- Angular: Print error message if angular.json has no default project specified ([#7574](https://github.com/storybookjs/storybook/pull/7574)) ## 5.2.0-beta.11 (July 26, 2019) @@ -1535,25 +2833,25 @@ This is a breaking release that undoes an unintentional breaking change introduc ### Breaking Changes -* Core: Remove project root `babel.config.js` loading ([#7573](https://github.com/storybookjs/storybook/pull/7573)) +- Core: Remove project root `babel.config.js` loading ([#7573](https://github.com/storybookjs/storybook/pull/7573)) ### Features -* React: Add hooks support to stories ([#7571](https://github.com/storybookjs/storybook/pull/7571)) +- React: Add hooks support to stories ([#7571](https://github.com/storybookjs/storybook/pull/7571)) ### Bug Fixes -* UI: Fix attribute warning on shortcut button click ([#7548](https://github.com/storybookjs/storybook/pull/7548)) +- UI: Fix attribute warning on shortcut button click ([#7548](https://github.com/storybookjs/storybook/pull/7548)) ## 5.2.0-beta.9 (July 26, 2019) ### Bug Fixes -* Angular: Fix automatic module metadata extraction for forRoot imports ([#7224](https://github.com/storybookjs/storybook/pull/7224)) +- Angular: Fix automatic module metadata extraction for forRoot imports ([#7224](https://github.com/storybookjs/storybook/pull/7224)) ### Maintenance -* Rename "Module" to Component Story Format ([#7564](https://github.com/storybookjs/storybook/pull/7564)) +- Rename "Module" to Component Story Format ([#7564](https://github.com/storybookjs/storybook/pull/7564)) ## 5.2.0-beta.8 (July 25, 2019) @@ -1561,34 +2859,34 @@ This is a breaking release that undoes an unintentional breaking change introduc `source-loader` is now part of `addon-docs` preset. If you're using both the `addon-docs` preset and `source-loader` in your project, you need to update. You can remove `source-loader` and let the preset take care of it. Alternatively, you can disable `source-loader` in the preset by setting `sourceLoaderOptions` to `null`. -* Addon-docs: Add source-loader to preset ([#7547](https://github.com/storybookjs/storybook/pull/7547)) -* Core: Don't allow duplicate titles ([#7542](https://github.com/storybookjs/storybook/pull/7542)) +- Addon-docs: Add source-loader to preset ([#7547](https://github.com/storybookjs/storybook/pull/7547)) +- Core: Don't allow duplicate titles ([#7542](https://github.com/storybookjs/storybook/pull/7542)) ### Bug Fixes -* Addon-storysource: Add source-loader dep to avoid breaking change ([#7554](https://github.com/storybookjs/storybook/pull/7554)) -* Addon-contexts: Ensure nodes is Array ([#7393](https://github.com/storybookjs/storybook/pull/7393)) +- Addon-storysource: Add source-loader dep to avoid breaking change ([#7554](https://github.com/storybookjs/storybook/pull/7554)) +- Addon-contexts: Ensure nodes is Array ([#7393](https://github.com/storybookjs/storybook/pull/7393)) ### Maintenance -* Angular: Log angular cli config errors ([#7484](https://github.com/storybookjs/storybook/pull/7484)) +- Angular: Log angular cli config errors ([#7484](https://github.com/storybookjs/storybook/pull/7484)) ## 5.2.0-beta.7 (July 23, 2019) ### Features -* Addon-docs: MDX function stories ([#7529](https://github.com/storybookjs/storybook/pull/7529)) -* CLI: update `sb init` to module format for Ember/Marko/Mithril/Rax/Riot/Svelte ([#7504](https://github.com/storybookjs/storybook/pull/7504)) -* CLI: update `sb init` to module format for Angular ([#7502](https://github.com/storybookjs/storybook/pull/7502)) -* CLI: update `sb init` to module format for React ([#7500](https://github.com/storybookjs/storybook/pull/7500)) +- Addon-docs: MDX function stories ([#7529](https://github.com/storybookjs/storybook/pull/7529)) +- CLI: update `sb init` to module format for Ember/Marko/Mithril/Rax/Riot/Svelte ([#7504](https://github.com/storybookjs/storybook/pull/7504)) +- CLI: update `sb init` to module format for Angular ([#7502](https://github.com/storybookjs/storybook/pull/7502)) +- CLI: update `sb init` to module format for React ([#7500](https://github.com/storybookjs/storybook/pull/7500)) ### Bug Fixes -* Ondevice-knobs: Graceful fail on missing default ([#7533](https://github.com/storybookjs/storybook/pull/7533)) +- Ondevice-knobs: Graceful fail on missing default ([#7533](https://github.com/storybookjs/storybook/pull/7533)) ### Maintenance -* Build: Attempt to fix travis timeouts ([#7531](https://github.com/storybookjs/storybook/pull/7531)) +- Build: Attempt to fix travis timeouts ([#7531](https://github.com/storybookjs/storybook/pull/7531)) ## 5.2.0-beta.6 (July 23, 2019) @@ -1598,300 +2896,300 @@ It is now recommended to only use ONE `load` call in your app and it will warn y ### Features -* Core: Top-level components in MDX/Module formats ([#7524](https://github.com/storybookjs/storybook/pull/7524)) -* Core: Module format `load` accept loader function ([#7518](https://github.com/storybookjs/storybook/pull/7518)) +- Core: Top-level components in MDX/Module formats ([#7524](https://github.com/storybookjs/storybook/pull/7524)) +- Core: Module format `load` accept loader function ([#7518](https://github.com/storybookjs/storybook/pull/7518)) ### Bug Fixes -* Addon-centered: Fix component disappearing on zoom ([#7400](https://github.com/storybookjs/storybook/pull/7400)) +- Addon-centered: Fix component disappearing on zoom ([#7400](https://github.com/storybookjs/storybook/pull/7400)) ### Maintenance -* Addon-knobs: enable Typescript `strict` flag ([#7515](https://github.com/storybookjs/storybook/pull/7515)) +- Addon-knobs: enable Typescript `strict` flag ([#7515](https://github.com/storybookjs/storybook/pull/7515)) ## 5.2.0-beta.5 (July 21, 2019) ### Features -* CLI: update `sb init` to module format for Vue ([#7501](https://github.com/storybookjs/storybook/pull/7501)) -* CLI: update `sb init` to module format for HTML/Polymer ([#7503](https://github.com/storybookjs/storybook/pull/7503)) +- CLI: update `sb init` to module format for Vue ([#7501](https://github.com/storybookjs/storybook/pull/7501)) +- CLI: update `sb init` to module format for HTML/Polymer ([#7503](https://github.com/storybookjs/storybook/pull/7503)) ### Bug Fixes -* Source-loader: Separate server and client code for IE support ([#7510](https://github.com/storybookjs/storybook/pull/7510)) +- Source-loader: Separate server and client code for IE support ([#7510](https://github.com/storybookjs/storybook/pull/7510)) ## 5.2.0-beta.4 (July 20, 2019) ### Breaking Changes -* Core: Module format story decorators ([#7490](https://github.com/storybookjs/storybook/pull/7490)) +- Core: Module format story decorators ([#7490](https://github.com/storybookjs/storybook/pull/7490)) ### Features -* Addon-jest: UI Redesign ([#7424](https://github.com/storybookjs/storybook/pull/7424)) -* Marko: support rerendering ([#7460](https://github.com/storybookjs/storybook/pull/7460)) +- Addon-jest: UI Redesign ([#7424](https://github.com/storybookjs/storybook/pull/7424)) +- Marko: support rerendering ([#7460](https://github.com/storybookjs/storybook/pull/7460)) ### Bug Fixes -* Addon-docs: Fix MDX source string escaping ([#7497](https://github.com/storybookjs/storybook/pull/7497)) +- Addon-docs: Fix MDX source string escaping ([#7497](https://github.com/storybookjs/storybook/pull/7497)) ### Dependency Upgrades -* Marko: Upgrade loader & config ([#7459](https://github.com/storybookjs/storybook/pull/7459)) -* Update core-js in addon-ondevice-actions package.json ([#7491](https://github.com/storybookjs/storybook/pull/7491)) +- Marko: Upgrade loader & config ([#7459](https://github.com/storybookjs/storybook/pull/7459)) +- Update core-js in addon-ondevice-actions package.json ([#7491](https://github.com/storybookjs/storybook/pull/7491)) ## 5.2.0-beta.3 (July 19, 2019) ### Features -* React-native: Add storyId as testID ([#7482](https://github.com/storybookjs/storybook/pull/7482)) +- React-native: Add storyId as testID ([#7482](https://github.com/storybookjs/storybook/pull/7482)) ### Bug Fixes -* React-native: On-device knobs input fixes ([#7475](https://github.com/storybookjs/storybook/pull/7475)) -* React-native: Fix crna-kitchen-sink ([#7200](https://github.com/storybookjs/storybook/pull/7200)) +- React-native: On-device knobs input fixes ([#7475](https://github.com/storybookjs/storybook/pull/7475)) +- React-native: Fix crna-kitchen-sink ([#7200](https://github.com/storybookjs/storybook/pull/7200)) ## 5.2.0-beta.2 (July 18, 2019) ### Features -* CLI: convert mdx to module format ([#7419](https://github.com/storybookjs/storybook/pull/7419)) -* CLI: sb migrate npm & typescript support ([#7463](https://github.com/storybookjs/storybook/pull/7463)) -* Addon-Docs: HTML support & example ([#7454](https://github.com/storybookjs/storybook/pull/7454)) +- CLI: convert mdx to module format ([#7419](https://github.com/storybookjs/storybook/pull/7419)) +- CLI: sb migrate npm & typescript support ([#7463](https://github.com/storybookjs/storybook/pull/7463)) +- Addon-Docs: HTML support & example ([#7454](https://github.com/storybookjs/storybook/pull/7454)) ### Bug Fixes -* Convert-storiesof-to-module: user exports, collisions, reserved keywords ([#7471](https://github.com/storybookjs/storybook/pull/7471)) -* React-native: On-device knobs fixes ([#7470](https://github.com/storybookjs/storybook/pull/7470)) +- Convert-storiesof-to-module: user exports, collisions, reserved keywords ([#7471](https://github.com/storybookjs/storybook/pull/7471)) +- React-native: On-device knobs fixes ([#7470](https://github.com/storybookjs/storybook/pull/7470)) ### Dependency Upgrades -* Addon-docs: Upgrade MDX to 1.1 ([#7476](https://github.com/storybookjs/storybook/pull/7476)) +- Addon-docs: Upgrade MDX to 1.1 ([#7476](https://github.com/storybookjs/storybook/pull/7476)) ## 5.2.0-beta.1 (July 18, 2019) ### Features -* React native: Emit event when story is rendered ([#7449](https://github.com/storybookjs/storybook/pull/7449)) +- React native: Emit event when story is rendered ([#7449](https://github.com/storybookjs/storybook/pull/7449)) ### Bug Fixes -* Addon-knobs: improve types via generics and readonlyarray ([#7411](https://github.com/storybookjs/storybook/pull/7411)) -* Ondevice-backgrounds: use same param key as addon-backgrounds ([#7437](https://github.com/storybookjs/storybook/pull/7437)) +- Addon-knobs: improve types via generics and readonlyarray ([#7411](https://github.com/storybookjs/storybook/pull/7411)) +- Ondevice-backgrounds: use same param key as addon-backgrounds ([#7437](https://github.com/storybookjs/storybook/pull/7437)) ## 5.2.0-beta.0 (July 15, 2019) ### Features -* Codemod: Convert module format to MDX ([#7418](https://github.com/storybookjs/storybook/pull/7418)) +- Codemod: Convert module format to MDX ([#7418](https://github.com/storybookjs/storybook/pull/7418)) ## 5.2.0-alpha.44 (July 15, 2019) ### Features -* CLI: Add migrate command ([#7414](https://github.com/storybookjs/storybook/pull/7414)) +- CLI: Add migrate command ([#7414](https://github.com/storybookjs/storybook/pull/7414)) ### Bug Fixes -* UI: Fix Panel rendered wrong at Docs-page ([#7327](https://github.com/storybookjs/storybook/pull/7327)) +- UI: Fix Panel rendered wrong at Docs-page ([#7327](https://github.com/storybookjs/storybook/pull/7327)) ### Maintenance -* Typescript: Fix types of client-api & storystore ([#7337](https://github.com/storybookjs/storybook/pull/7337)) +- Typescript: Fix types of client-api & storystore ([#7337](https://github.com/storybookjs/storybook/pull/7337)) ## 5.2.0-alpha.43 (July 13, 2019) ### Bug Fixes -* Addon-analytics: Fix API signature ([#7410](https://github.com/storybookjs/storybook/pull/7410)) -* Addon-knobs: fix knobs function return types ([#7391](https://github.com/storybookjs/storybook/pull/7391)) -* UI: Fix proptype for isToolshown ([#7405](https://github.com/storybookjs/storybook/pull/7405)) -* UI: Fix propType warnings ([#7408](https://github.com/storybookjs/storybook/pull/7408)) +- Addon-analytics: Fix API signature ([#7410](https://github.com/storybookjs/storybook/pull/7410)) +- Addon-knobs: fix knobs function return types ([#7391](https://github.com/storybookjs/storybook/pull/7391)) +- UI: Fix proptype for isToolshown ([#7405](https://github.com/storybookjs/storybook/pull/7405)) +- UI: Fix propType warnings ([#7408](https://github.com/storybookjs/storybook/pull/7408)) ### Maintenance -* Addon-actions: Use v4 UUID instead of v1 for action IDs ([#7397](https://github.com/storybookjs/storybook/pull/7397)) -* UI: Remove recompose ([#7385](https://github.com/storybookjs/storybook/pull/7385)) -* UI: FIX & IMPROVE styling interop of addon-background & addon-viewport ([#7385](https://github.com/storybookjs/storybook/pull/7385)) +- Addon-actions: Use v4 UUID instead of v1 for action IDs ([#7397](https://github.com/storybookjs/storybook/pull/7397)) +- UI: Remove recompose ([#7385](https://github.com/storybookjs/storybook/pull/7385)) +- UI: FIX & IMPROVE styling interop of addon-background & addon-viewport ([#7385](https://github.com/storybookjs/storybook/pull/7385)) ### Breaking Changes -* Move grid toolbar feature to background-addon ([#7385](https://github.com/storybookjs/storybook/pull/7385)) +- Move grid toolbar feature to background-addon ([#7385](https://github.com/storybookjs/storybook/pull/7385)) ## 5.2.0-alpha.42 (July 12, 2019) ### Breaking Changes -* Addon-docs: Remove primary parameter ([#7383](https://github.com/storybookjs/storybook/pull/7383)) +- Addon-docs: Remove primary parameter ([#7383](https://github.com/storybookjs/storybook/pull/7383)) ### Bug Fixes -* Addon-docs: Fix default separator inconsistency ([#7382](https://github.com/storybookjs/storybook/pull/7382)) -* UI: Fix placement of notificationistList on docs page ([#7290](https://github.com/storybookjs/storybook/pull/7290)) +- Addon-docs: Fix default separator inconsistency ([#7382](https://github.com/storybookjs/storybook/pull/7382)) +- UI: Fix placement of notificationistList on docs page ([#7290](https://github.com/storybookjs/storybook/pull/7290)) ### Maintenance -* Typescript: Migrate @storybook/html ([#7338](https://github.com/storybookjs/storybook/pull/7338)) +- Typescript: Migrate @storybook/html ([#7338](https://github.com/storybookjs/storybook/pull/7338)) ### Dependency Upgrades -* Bump lodash from 4.17.13 to 4.17.14 ([#7384](https://github.com/storybookjs/storybook/pull/7384)) -* [Security] Bump lodash.defaultsdeep from 4.6.0 to 4.6.1 ([#7370](https://github.com/storybookjs/storybook/pull/7370)) -* [Security] Bump lodash from 4.17.11 to 4.17.13 ([#7374](https://github.com/storybookjs/storybook/pull/7374)) -* [Security] Bump lodash.mergewith from 4.6.1 to 4.6.2 ([#7372](https://github.com/storybookjs/storybook/pull/7372)) -* [Security] Bump lodash.merge from 4.6.1 to 4.6.2 ([#7373](https://github.com/storybookjs/storybook/pull/7373)) -* [Security] Bump lodash-es from 4.17.11 to 4.17.14 ([#7371](https://github.com/storybookjs/storybook/pull/7371)) -* Upgrade react-select dependency to version 3 for addon-knobs ([#7336](https://github.com/storybookjs/storybook/pull/7336)) +- Bump lodash from 4.17.13 to 4.17.14 ([#7384](https://github.com/storybookjs/storybook/pull/7384)) +- [Security] Bump lodash.defaultsdeep from 4.6.0 to 4.6.1 ([#7370](https://github.com/storybookjs/storybook/pull/7370)) +- [Security] Bump lodash from 4.17.11 to 4.17.13 ([#7374](https://github.com/storybookjs/storybook/pull/7374)) +- [Security] Bump lodash.mergewith from 4.6.1 to 4.6.2 ([#7372](https://github.com/storybookjs/storybook/pull/7372)) +- [Security] Bump lodash.merge from 4.6.1 to 4.6.2 ([#7373](https://github.com/storybookjs/storybook/pull/7373)) +- [Security] Bump lodash-es from 4.17.11 to 4.17.14 ([#7371](https://github.com/storybookjs/storybook/pull/7371)) +- Upgrade react-select dependency to version 3 for addon-knobs ([#7336](https://github.com/storybookjs/storybook/pull/7336)) ## 5.2.0-alpha.41 (July 11, 2019) ### Features -* API: Preview hooks ([#6916](https://github.com/storybookjs/storybook/pull/6916)) -* Core: Custom webpack option for standalone storybook ([#6886](https://github.com/storybookjs/storybook/pull/6886)) +- API: Preview hooks ([#6916](https://github.com/storybookjs/storybook/pull/6916)) +- Core: Custom webpack option for standalone storybook ([#6886](https://github.com/storybookjs/storybook/pull/6886)) ### Bug Fixes -* Addon-knobs: Fix TypeError on KnobManager channel ([#7341](https://github.com/storybookjs/storybook/pull/7341)) -* React-native: Explicitly depend on emotion core and theming ([#7362](https://github.com/storybookjs/storybook/pull/7362)) +- Addon-knobs: Fix TypeError on KnobManager channel ([#7341](https://github.com/storybookjs/storybook/pull/7341)) +- React-native: Explicitly depend on emotion core and theming ([#7362](https://github.com/storybookjs/storybook/pull/7362)) ### Dependency Upgrades -* Bump @babel/preset-env from 7.5.0 to 7.5.4 ([#7364](https://github.com/storybookjs/storybook/pull/7364)) -* Update react-test-renderer requirement from 16.5.1 to 16.8.6 in /examples-native/crna-kitchen-sink ([#6372](https://github.com/storybookjs/storybook/pull/6372)) -* Bump rax-text from 0.6.5 to 1.0.0 ([#7346](https://github.com/storybookjs/storybook/pull/7346)) +- Bump @babel/preset-env from 7.5.0 to 7.5.4 ([#7364](https://github.com/storybookjs/storybook/pull/7364)) +- Update react-test-renderer requirement from 16.5.1 to 16.8.6 in /examples-native/crna-kitchen-sink ([#6372](https://github.com/storybookjs/storybook/pull/6372)) +- Bump rax-text from 0.6.5 to 1.0.0 ([#7346](https://github.com/storybookjs/storybook/pull/7346)) ## 5.2.0-alpha.40 (July 10, 2019) ### Bug Fixes -* Addon-knobs: Revert entrypoint deletion ([#7369](https://github.com/storybookjs/storybook/pull/7369)) -* Typescript: Fix types in api package ([#7072](https://github.com/storybookjs/storybook/pull/7072)) -* UI: Fix settings page route (about, shortcuts) ([#7241](https://github.com/storybookjs/storybook/pull/7241)) +- Addon-knobs: Revert entrypoint deletion ([#7369](https://github.com/storybookjs/storybook/pull/7369)) +- Typescript: Fix types in api package ([#7072](https://github.com/storybookjs/storybook/pull/7072)) +- UI: Fix settings page route (about, shortcuts) ([#7241](https://github.com/storybookjs/storybook/pull/7241)) ### Maintenance -* Linting: ADD an ignore for an eslint error about a missing dependency (puppeteer) ([#7239](https://github.com/storybookjs/storybook/pull/7239)) -* CI: ADD travis ([#7252](https://github.com/storybookjs/storybook/pull/7252)) -* Typescript: Migrate @storybook/angular ([#6570](https://github.com/storybookjs/storybook/pull/6570)) +- Linting: ADD an ignore for an eslint error about a missing dependency (puppeteer) ([#7239](https://github.com/storybookjs/storybook/pull/7239)) +- CI: ADD travis ([#7252](https://github.com/storybookjs/storybook/pull/7252)) +- Typescript: Migrate @storybook/angular ([#6570](https://github.com/storybookjs/storybook/pull/6570)) ### Dependency Upgrades -* Bump express-graphql from 0.7.1 to 0.8.0 ([#7345](https://github.com/storybookjs/storybook/pull/7345)) -* Bump react-native-modal-datetime-picker from 6.1.0 to 7.4.2 ([#6844](https://github.com/storybookjs/storybook/pull/6844)) +- Bump express-graphql from 0.7.1 to 0.8.0 ([#7345](https://github.com/storybookjs/storybook/pull/7345)) +- Bump react-native-modal-datetime-picker from 6.1.0 to 7.4.2 ([#6844](https://github.com/storybookjs/storybook/pull/6844)) ## 5.2.0-alpha.39 (July 10, 2019) ### Bug Fixes -* UI: Fix Sidebar input refresh on 'Enter' ([#7342](https://github.com/storybookjs/storybook/pull/7342)) -* Addon-knobs: Fix select options types to allow string[] and null ([#7356](https://github.com/storybookjs/storybook/pull/7356)) +- UI: Fix Sidebar input refresh on 'Enter' ([#7342](https://github.com/storybookjs/storybook/pull/7342)) +- Addon-knobs: Fix select options types to allow string[] and null ([#7356](https://github.com/storybookjs/storybook/pull/7356)) ### Maintenance -* Typescript: Migrate @storybook/react ([#7054](https://github.com/storybookjs/storybook/pull/7054)) -* Build: delete tests & snapshots from dist ([#7358](https://github.com/storybookjs/storybook/pull/7358)) +- Typescript: Migrate @storybook/react ([#7054](https://github.com/storybookjs/storybook/pull/7054)) +- Build: delete tests & snapshots from dist ([#7358](https://github.com/storybookjs/storybook/pull/7358)) ## 5.2.0-alpha.38 (July 9, 2019) ### Bug Fixes -* Addon-storysource: Replace loader with source-loader ([#7272](https://github.com/storybookjs/storybook/pull/7272)) +- Addon-storysource: Replace loader with source-loader ([#7272](https://github.com/storybookjs/storybook/pull/7272)) ### Maintenance -* Typescript: Migrate @storybook/addon-knobs ([#7180](https://github.com/storybookjs/storybook/pull/7180)) +- Typescript: Migrate @storybook/addon-knobs ([#7180](https://github.com/storybookjs/storybook/pull/7180)) ### Dependency Upgrades -* Upgrade all dependencies ([#7329](https://github.com/storybookjs/storybook/pull/7329)) +- Upgrade all dependencies ([#7329](https://github.com/storybookjs/storybook/pull/7329)) ## 5.2.0-alpha.37 (July 8, 2019) ### Bug Fixes -* Addon-docs: Use storyFn instead of getDecorated ([#7334](https://github.com/storybookjs/storybook/pull/7334)) -* Addon-knobs: Prevent rerender when a button callback returns false. ([#7197](https://github.com/storybookjs/storybook/pull/7197)) -* Addons: Fix null parameters in disable addons tab logic ([#7333](https://github.com/storybookjs/storybook/pull/7333)) -* Addon-docs: Fix renaming stories on module / MDX format ([#7319](https://github.com/storybookjs/storybook/pull/7319)) -* Addon-centered/contexts: Move optionalDependencies to peerDependencies ([#7315](https://github.com/storybookjs/storybook/pull/7315)) +- Addon-docs: Use storyFn instead of getDecorated ([#7334](https://github.com/storybookjs/storybook/pull/7334)) +- Addon-knobs: Prevent rerender when a button callback returns false. ([#7197](https://github.com/storybookjs/storybook/pull/7197)) +- Addons: Fix null parameters in disable addons tab logic ([#7333](https://github.com/storybookjs/storybook/pull/7333)) +- Addon-docs: Fix renaming stories on module / MDX format ([#7319](https://github.com/storybookjs/storybook/pull/7319)) +- Addon-centered/contexts: Move optionalDependencies to peerDependencies ([#7315](https://github.com/storybookjs/storybook/pull/7315)) ### Maintenance -* Typescript: migrate client api ([#7147](https://github.com/storybookjs/storybook/pull/7147)) -* Angular-cli: Add addon-docs example ([#7257](https://github.com/storybookjs/storybook/pull/7257)) +- Typescript: migrate client api ([#7147](https://github.com/storybookjs/storybook/pull/7147)) +- Angular-cli: Add addon-docs example ([#7257](https://github.com/storybookjs/storybook/pull/7257)) ## 5.2.0-alpha.36 (July 5, 2019) ### Features -* Addon-docs: Added inline option to Story block ([#7308](https://github.com/storybookjs/storybook/pull/7308)) -* Addon-knobs: Ensure unique knob names across groups ([#6793](https://github.com/storybookjs/storybook/pull/6793)) -* Core: Enable webpack to rebuild changes in node_modules ([#6265](https://github.com/storybookjs/storybook/pull/6265)) -* Addons: Disable option for addon tab ([#6923](https://github.com/storybookjs/storybook/pull/6923)) +- Addon-docs: Added inline option to Story block ([#7308](https://github.com/storybookjs/storybook/pull/7308)) +- Addon-knobs: Ensure unique knob names across groups ([#6793](https://github.com/storybookjs/storybook/pull/6793)) +- Core: Enable webpack to rebuild changes in node_modules ([#6265](https://github.com/storybookjs/storybook/pull/6265)) +- Addons: Disable option for addon tab ([#6923](https://github.com/storybookjs/storybook/pull/6923)) ### Bug Fixes -* Fix lint error from #6923 ([#7311](https://github.com/storybookjs/storybook/pull/7311)) -* Addon-actions: fix serialization performance ([#7256](https://github.com/storybookjs/storybook/pull/7256)) +- Fix lint error from #6923 ([#7311](https://github.com/storybookjs/storybook/pull/7311)) +- Addon-actions: fix serialization performance ([#7256](https://github.com/storybookjs/storybook/pull/7256)) ### Maintenance -* Typescript: Migrate @storybook/addon-event ([#7190](https://github.com/storybookjs/storybook/pull/7190)) -* Typescript: Improve actions type ([#7012](https://github.com/storybookjs/storybook/pull/7012)) +- Typescript: Migrate @storybook/addon-event ([#7190](https://github.com/storybookjs/storybook/pull/7190)) +- Typescript: Improve actions type ([#7012](https://github.com/storybookjs/storybook/pull/7012)) ## 5.2.0-alpha.35 (July 3, 2019) ### Bug Fixes -* React-Native: Fix null story check ([#7243](https://github.com/storybookjs/storybook/pull/7243)) +- React-Native: Fix null story check ([#7243](https://github.com/storybookjs/storybook/pull/7243)) ## 5.2.0-alpha.34 (July 2, 2019) ### Bug Fixes -* CLI: Fix `--preview-url` for static builds ([#7245](https://github.com/storybookjs/storybook/pull/7245)) -* Addon-docs: Fix non-React support & add Vue example ([#7222](https://github.com/storybookjs/storybook/pull/7222)) -* CLI: Move the free port logic so that loadOptions don't override it ([#7237](https://github.com/storybookjs/storybook/pull/7237)) +- CLI: Fix `--preview-url` for static builds ([#7245](https://github.com/storybookjs/storybook/pull/7245)) +- Addon-docs: Fix non-React support & add Vue example ([#7222](https://github.com/storybookjs/storybook/pull/7222)) +- CLI: Move the free port logic so that loadOptions don't override it ([#7237](https://github.com/storybookjs/storybook/pull/7237)) ## 5.2.0-alpha.33 (July 1, 2019) ### Features -* CLI: Add `--preview-url` for custom preview ([#7235](https://github.com/storybookjs/storybook/pull/7235)) +- CLI: Add `--preview-url` for custom preview ([#7235](https://github.com/storybookjs/storybook/pull/7235)) ### Bug Fixes -* React-Native: Upgrade to new `story_store` API ([#7234](https://github.com/storybookjs/storybook/pull/7234)) +- React-Native: Upgrade to new `story_store` API ([#7234](https://github.com/storybookjs/storybook/pull/7234)) ## 5.2.0-alpha.32 (June 29, 2019) ### Features -* Addon-docs: Add .story.mdx support to preset ([#7229](https://github.com/storybookjs/storybook/pull/7229)) +- Addon-docs: Add .story.mdx support to preset ([#7229](https://github.com/storybookjs/storybook/pull/7229)) ### Bug Fixes -* React-native: Fix react native server ([#7187](https://github.com/storybookjs/storybook/pull/7187)) -* Addon-docs: Fix source-loader in monorepo examples ([#7214](https://github.com/storybookjs/storybook/pull/7214)) +- React-native: Fix react native server ([#7187](https://github.com/storybookjs/storybook/pull/7187)) +- Addon-docs: Fix source-loader in monorepo examples ([#7214](https://github.com/storybookjs/storybook/pull/7214)) ### Maintenance -* Addon-docs: Convert repo stories to new module format ([#7175](https://github.com/storybookjs/storybook/pull/7175)) +- Addon-docs: Convert repo stories to new module format ([#7175](https://github.com/storybookjs/storybook/pull/7175)) ## 5.2.0-alpha.31 (June 27, 2019) ### Breaking Changes -* Module format: story field for name/parameters annotation ([#7202](https://github.com/storybookjs/storybook/pull/7202)) +- Module format: story field for name/parameters annotation ([#7202](https://github.com/storybookjs/storybook/pull/7202)) ### Features -* Core: Story sorting ([#6472](https://github.com/storybookjs/storybook/pull/6472)) +- Core: Story sorting ([#6472](https://github.com/storybookjs/storybook/pull/6472)) ### Maintenance -* Addon-docs: Fix source-loader CI errors ([#7203](https://github.com/storybookjs/storybook/pull/7203)) +- Addon-docs: Fix source-loader CI errors ([#7203](https://github.com/storybookjs/storybook/pull/7203)) ## 5.2.0-alpha.30 (June 25, 2019) @@ -1899,70 +3197,70 @@ This release merges `release/docs-technical-preview` branch back into `next` thr ### Features -* CLI: Add info command to print environment information ([#6937](https://github.com/storybookjs/storybook/pull/6937)) -* CLI: Use process.env.CI if available ([#7118](https://github.com/storybookjs/storybook/pull/7118)) -* Addon-docs: Source loader library ([#7117](https://github.com/storybookjs/storybook/pull/7117)) -* Addon-docs: Support non-story exports in MDX ([#7188](https://github.com/storybookjs/storybook/pull/7188)) -* Addon-docs: Support non-story exports in module format ([#7185](https://github.com/storybookjs/storybook/pull/7185)) -* Addon-docs: Docs mode with `--docs` flag ([#7154](https://github.com/storybookjs/storybook/pull/7154)) -* Addon-docs: Convert to module format codemod ([#7174](https://github.com/storybookjs/storybook/pull/7174)) -* Addon-docs: MDX support ([#7145](https://github.com/storybookjs/storybook/pull/7145)) -* Addon-docs: Component parameter codemod ([#7155](https://github.com/storybookjs/storybook/pull/7155)) -* Addon-docs: DocsPage and doc blocks ([#7119](https://github.com/storybookjs/storybook/pull/7119)) -* Addon-docs: Module story format & framework param ([#7110](https://github.com/storybookjs/storybook/pull/7110)) -* Addon-docs: Basic skeleton, UI viewMode handling ([#7107](https://github.com/storybookjs/storybook/pull/7107)) +- CLI: Add info command to print environment information ([#6937](https://github.com/storybookjs/storybook/pull/6937)) +- CLI: Use process.env.CI if available ([#7118](https://github.com/storybookjs/storybook/pull/7118)) +- Addon-docs: Source loader library ([#7117](https://github.com/storybookjs/storybook/pull/7117)) +- Addon-docs: Support non-story exports in MDX ([#7188](https://github.com/storybookjs/storybook/pull/7188)) +- Addon-docs: Support non-story exports in module format ([#7185](https://github.com/storybookjs/storybook/pull/7185)) +- Addon-docs: Docs mode with `--docs` flag ([#7154](https://github.com/storybookjs/storybook/pull/7154)) +- Addon-docs: Convert to module format codemod ([#7174](https://github.com/storybookjs/storybook/pull/7174)) +- Addon-docs: MDX support ([#7145](https://github.com/storybookjs/storybook/pull/7145)) +- Addon-docs: Component parameter codemod ([#7155](https://github.com/storybookjs/storybook/pull/7155)) +- Addon-docs: DocsPage and doc blocks ([#7119](https://github.com/storybookjs/storybook/pull/7119)) +- Addon-docs: Module story format & framework param ([#7110](https://github.com/storybookjs/storybook/pull/7110)) +- Addon-docs: Basic skeleton, UI viewMode handling ([#7107](https://github.com/storybookjs/storybook/pull/7107)) ### Bug Fixes -* Addon-backgrounds: Fix unstretched preview background wrapper ([#7173](https://github.com/storybookjs/storybook/pull/7173)) -* Addon-notes/info: Fix indenting on markdown code blocks ([#7158](https://github.com/storybookjs/storybook/pull/7158)) -* Core: Improve HMR error reporting, no refreshes needed for error recovery ([#6972](https://github.com/storybookjs/storybook/pull/6972)) -* Addon-info: change stylesheetBase info height from 110vh to 100vh ([#7141](https://github.com/storybookjs/storybook/pull/7141)) +- Addon-backgrounds: Fix unstretched preview background wrapper ([#7173](https://github.com/storybookjs/storybook/pull/7173)) +- Addon-notes/info: Fix indenting on markdown code blocks ([#7158](https://github.com/storybookjs/storybook/pull/7158)) +- Core: Improve HMR error reporting, no refreshes needed for error recovery ([#6972](https://github.com/storybookjs/storybook/pull/6972)) +- Addon-info: change stylesheetBase info height from 110vh to 100vh ([#7141](https://github.com/storybookjs/storybook/pull/7141)) ### Maintenance -* Typescript: Migrate addon viewport ([#7177](https://github.com/storybookjs/storybook/pull/7177)) +- Typescript: Migrate addon viewport ([#7177](https://github.com/storybookjs/storybook/pull/7177)) ### Dependency Upgrades -* Bump css-loader from 2.1.1 to 3.0.0 ([#7122](https://github.com/storybookjs/storybook/pull/7122)) -* Upgrade core-js to 3.x in devkits ([#7171](https://github.com/storybookjs/storybook/pull/7171)) -* UPGRADE lazy-universal-dotenv ([#7151](https://github.com/storybookjs/storybook/pull/7151)) +- Bump css-loader from 2.1.1 to 3.0.0 ([#7122](https://github.com/storybookjs/storybook/pull/7122)) +- Upgrade core-js to 3.x in devkits ([#7171](https://github.com/storybookjs/storybook/pull/7171)) +- UPGRADE lazy-universal-dotenv ([#7151](https://github.com/storybookjs/storybook/pull/7151)) ## 5.1.9 (June 20, 2019) ### Bug Fixes -* Core: Fix JSON babel config error reporting ([#7104](https://github.com/storybookjs/storybook/pull/7104)) -* UI: Fix about page version check message ([#7105](https://github.com/storybookjs/storybook/pull/7105)) +- Core: Fix JSON babel config error reporting ([#7104](https://github.com/storybookjs/storybook/pull/7104)) +- UI: Fix about page version check message ([#7105](https://github.com/storybookjs/storybook/pull/7105)) ### Dependency Upgrades -* Add missing dependencies to ui/react ([#7081](https://github.com/storybookjs/storybook/pull/7081)) -* UPGRADE lazy-universal-dotenv ([#7151](https://github.com/storybookjs/storybook/pull/7151)) -* Make compatible with yarn Pnp ([#6922](https://github.com/storybookjs/storybook/pull/6922)) +- Add missing dependencies to ui/react ([#7081](https://github.com/storybookjs/storybook/pull/7081)) +- UPGRADE lazy-universal-dotenv ([#7151](https://github.com/storybookjs/storybook/pull/7151)) +- Make compatible with yarn Pnp ([#6922](https://github.com/storybookjs/storybook/pull/6922)) ## 5.2.0-alpha.29 (June 17, 2019) ### Features -* Addon-notes: enable multiple sections in notes panel ([#6861](https://github.com/storybookjs/storybook/pull/6861)) -* Addon-context: title fallback ([#7078](https://github.com/storybookjs/storybook/pull/7078)) -* Addon-info: Fix rendering of code block ([#6016](https://github.com/storybookjs/storybook/pull/6016)) +- Addon-notes: enable multiple sections in notes panel ([#6861](https://github.com/storybookjs/storybook/pull/6861)) +- Addon-context: title fallback ([#7078](https://github.com/storybookjs/storybook/pull/7078)) +- Addon-info: Fix rendering of code block ([#6016](https://github.com/storybookjs/storybook/pull/6016)) ### Bug Fixes -* Core: Fix JSON babel config error reporting ([#7104](https://github.com/storybookjs/storybook/pull/7104)) -* UI: Fix about page version check message ([#7105](https://github.com/storybookjs/storybook/pull/7105)) +- Core: Fix JSON babel config error reporting ([#7104](https://github.com/storybookjs/storybook/pull/7104)) +- UI: Fix about page version check message ([#7105](https://github.com/storybookjs/storybook/pull/7105)) ### Maintenance -* Core: Refactor story_store ([#6382](https://github.com/storybookjs/storybook/pull/6382)) -* Core: Make compatible with yarn Pnp ([#6922](https://github.com/storybookjs/storybook/pull/6922)) +- Core: Refactor story_store ([#6382](https://github.com/storybookjs/storybook/pull/6382)) +- Core: Make compatible with yarn Pnp ([#6922](https://github.com/storybookjs/storybook/pull/6922)) ### Dependency Upgrades -* Bump jest-expo from 32.0.1 to 33.0.2 ([#6996](https://github.com/storybookjs/storybook/pull/6996)) +- Bump jest-expo from 32.0.1 to 33.0.2 ([#6996](https://github.com/storybookjs/storybook/pull/6996)) ## 5.2.0-alpha.28 (June 17, 2019) @@ -1970,20 +3268,20 @@ Publish failed ## 5.2.0-alpha.27 (June 17, 2019) -* CLI: improve bootstrap list ([#6993](https://github.com/storybookjs/storybook/pull/6993)) -* CLI: replaced merge-dirs dependency by fs-extra ([#7100](https://github.com/storybookjs/storybook/pull/7100)) +- CLI: improve bootstrap list ([#6993](https://github.com/storybookjs/storybook/pull/6993)) +- CLI: replaced merge-dirs dependency by fs-extra ([#7100](https://github.com/storybookjs/storybook/pull/7100)) ## 5.1.8 (June 14, 2019) ### Bug Fixes -* CLI: Fix RN template to not import addons ([#7096](https://github.com/storybookjs/storybook/pull/7096)) +- CLI: Fix RN template to not import addons ([#7096](https://github.com/storybookjs/storybook/pull/7096)) ## 5.1.7 (June 14, 2019) ### Bug Fixes -* UI: Fix warning of loading prop not being a string ([#7080](https://github.com/storybookjs/storybook/pull/7080)) +- UI: Fix warning of loading prop not being a string ([#7080](https://github.com/storybookjs/storybook/pull/7080)) ## 5.1.6 (June 14, 2019) @@ -1993,10 +3291,10 @@ Publish failed ### Bug Fixes -* Core: Upgrade plugin core-js fix ([#7086](https://github.com/storybookjs/storybook/pull/7086)) -* UI: Fix sidebar loading visibility ([#7073](https://github.com/storybookjs/storybook/pull/7073)) -* UI: Fix unnecessary large bundlesize ([#7091](https://github.com/storybookjs/storybook/pull/7091)) -* Addon-contexts, RN-server: Add core-js dep ([#7094](https://github.com/storybookjs/storybook/pull/7094)) +- Core: Upgrade plugin core-js fix ([#7086](https://github.com/storybookjs/storybook/pull/7086)) +- UI: Fix sidebar loading visibility ([#7073](https://github.com/storybookjs/storybook/pull/7073)) +- UI: Fix unnecessary large bundlesize ([#7091](https://github.com/storybookjs/storybook/pull/7091)) +- Addon-contexts, RN-server: Add core-js dep ([#7094](https://github.com/storybookjs/storybook/pull/7094)) ## 5.2.0-alpha.26 (June 14, 2019) @@ -2013,14 +3311,14 @@ Publish failed ### Bug Fixes -* Core: Fix core-js 3 errors ([#7051](https://github.com/storybookjs/storybook/pull/7051)) -* UI: Fix syntax highlighter plain text not visible ([#7057](https://github.com/storybookjs/storybook/pull/7057)) -* Addon-actions: Add default options to action(s) ([#6438](https://github.com/storybookjs/storybook/pull/6438)) +- Core: Fix core-js 3 errors ([#7051](https://github.com/storybookjs/storybook/pull/7051)) +- UI: Fix syntax highlighter plain text not visible ([#7057](https://github.com/storybookjs/storybook/pull/7057)) +- Addon-actions: Add default options to action(s) ([#6438](https://github.com/storybookjs/storybook/pull/6438)) ### Dependency Upgrades -* fix: add missing core-js dependency ([#7016](https://github.com/storybookjs/storybook/pull/7016)) -* chore: set react version to 16.8.3 to match react native ([#7008](https://github.com/storybookjs/storybook/pull/7008)) +- fix: add missing core-js dependency ([#7016](https://github.com/storybookjs/storybook/pull/7016)) +- chore: set react version to 16.8.3 to match react native ([#7008](https://github.com/storybookjs/storybook/pull/7008)) ## 5.2.0-alpha.24 (June 13, 2019) @@ -2041,9 +3339,9 @@ Merge in changes from 5.1.3/next branch. Releasing from the addon-docs branch to ### Bug Fixes -* UI: Fix links that are not working with plain left click ([#6970](https://github.com/storybookjs/storybook/pull/6970)) -* Core: Don't redefine `process` variable ([#6991](https://github.com/storybookjs/storybook/pull/6991)) -* Core: Don't mutate user's babel config ([#6987](https://github.com/storybookjs/storybook/pull/6987)) +- UI: Fix links that are not working with plain left click ([#6970](https://github.com/storybookjs/storybook/pull/6970)) +- Core: Don't redefine `process` variable ([#6991](https://github.com/storybookjs/storybook/pull/6991)) +- Core: Don't mutate user's babel config ([#6987](https://github.com/storybookjs/storybook/pull/6987)) ## 5.1.2 (June 6, 2019) @@ -2058,7 +3356,7 @@ Storybook 5.1 is a juicy upgrade including: - 🛠 Context addon: New UI for themes, internationalization, & more - 🎛 Presets: One-line configuration for babel, webpack, & addons -5.1 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.1.0-alpha.*`, `5.1.0-beta.*`, and `5.1.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier. + 5.1 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.1.0-alpha.*`, `5.1.0-beta.*`, and `5.1.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier. ## 5.1.0 (June 5, 2019) @@ -2068,30 +3366,30 @@ Publish failed ### Features -* UI: Add copy button for individual story ([#6719](https://github.com/storybookjs/storybook/pull/6719)) +- UI: Add copy button for individual story ([#6719](https://github.com/storybookjs/storybook/pull/6719)) ### Bug Fixes -* UI: Fix canvas rendering issue ([#6968](https://github.com/storybookjs/storybook/pull/6968)) -* Angular: Remove zonejs from devDeps; allow ^0.8.0 || ^0.9.0 ([#6957](https://github.com/storybookjs/storybook/pull/6957)) -* Jest: layout/design fixes ([#6847](https://github.com/storybookjs/storybook/pull/6847)) -* Angular: Add 'addParameters' to index.d.ts ([#6929](https://github.com/storybookjs/storybook/pull/6929)) +- UI: Fix canvas rendering issue ([#6968](https://github.com/storybookjs/storybook/pull/6968)) +- Angular: Remove zonejs from devDeps; allow ^0.8.0 || ^0.9.0 ([#6957](https://github.com/storybookjs/storybook/pull/6957)) +- Jest: layout/design fixes ([#6847](https://github.com/storybookjs/storybook/pull/6847)) +- Angular: Add 'addParameters' to index.d.ts ([#6929](https://github.com/storybookjs/storybook/pull/6929)) ### Maintenance -* FIX race condition in startup of verdaccio && FIX typos ([#6956](https://github.com/storybookjs/storybook/pull/6956)) -* Github org rename to storybookjs ([#6954](https://github.com/storybookjs/storybook/pull/6954)) +- FIX race condition in startup of verdaccio && FIX typos ([#6956](https://github.com/storybookjs/storybook/pull/6956)) +- Github org rename to storybookjs ([#6954](https://github.com/storybookjs/storybook/pull/6954)) ## 5.1.0-rc.4 (June 2, 2019) ### Features -* Core: deep merge global options parameter ([#6900](https://github.com/storybookjs/storybook/pull/6900)) +- Core: deep merge global options parameter ([#6900](https://github.com/storybookjs/storybook/pull/6900)) ### Bug Fixes -* Core: Fix webpack `process.*` variable definitions ([#6946](https://github.com/storybookjs/storybook/pull/6946)) -* Angular: Fix tsconfig.app.json detection for Angular 8 ([#6940](https://github.com/storybookjs/storybook/pull/6940)) +- Core: Fix webpack `process.*` variable definitions ([#6946](https://github.com/storybookjs/storybook/pull/6946)) +- Angular: Fix tsconfig.app.json detection for Angular 8 ([#6940](https://github.com/storybookjs/storybook/pull/6940)) ## 5.2.0-alpha.21 (June 2, 2019) @@ -2107,21 +3405,21 @@ Publish failed ### Features -* React-native: Add accessibility on searchbar ([#6819](https://github.com/storybookjs/storybook/pull/6819)) +- React-native: Add accessibility on searchbar ([#6819](https://github.com/storybookjs/storybook/pull/6819)) ### Bug Fixes -* Storyshots: make compatible with read-pkg-up version ([#6907](https://github.com/storybookjs/storybook/pull/6907)) -* Addon-info: Fix issue where forwardRefs throw ([#6859](https://github.com/storybookjs/storybook/pull/6859)) -* Addon-notes: Fix whitespace rendering bug ([#6881](https://github.com/storybookjs/storybook/pull/6881)) +- Storyshots: make compatible with read-pkg-up version ([#6907](https://github.com/storybookjs/storybook/pull/6907)) +- Addon-info: Fix issue where forwardRefs throw ([#6859](https://github.com/storybookjs/storybook/pull/6859)) +- Addon-notes: Fix whitespace rendering bug ([#6881](https://github.com/storybookjs/storybook/pull/6881)) ### Dependency Upgrades -* Bump @types/enzyme from 3.9.2 to 3.9.3 ([#6891](https://github.com/storybookjs/storybook/pull/6891)) -* Bump eslint-plugin-import from 2.17.2 to 2.17.3 ([#6894](https://github.com/storybookjs/storybook/pull/6894)) -* Bump mini-css-extract-plugin from 0.6.0 to 0.7.0 ([#6895](https://github.com/storybookjs/storybook/pull/6895)) -* Bump ts-node from 8.1.0 to 8.2.0 ([#6890](https://github.com/storybookjs/storybook/pull/6890)) -* Bump svelte from 3.4.2 to 3.4.4 ([#6892](https://github.com/storybookjs/storybook/pull/6892)) +- Bump @types/enzyme from 3.9.2 to 3.9.3 ([#6891](https://github.com/storybookjs/storybook/pull/6891)) +- Bump eslint-plugin-import from 2.17.2 to 2.17.3 ([#6894](https://github.com/storybookjs/storybook/pull/6894)) +- Bump mini-css-extract-plugin from 0.6.0 to 0.7.0 ([#6895](https://github.com/storybookjs/storybook/pull/6895)) +- Bump ts-node from 8.1.0 to 8.2.0 ([#6890](https://github.com/storybookjs/storybook/pull/6890)) +- Bump svelte from 3.4.2 to 3.4.4 ([#6892](https://github.com/storybookjs/storybook/pull/6892)) ## 5.2.0-alpha.19 (May 28, 2019) @@ -2131,7 +3429,7 @@ Publish failed ### Bug Fixes -* Core: Fix JS/JSON loading babel config ([#6878](https://github.com/storybookjs/storybook/pull/6878)) +- Core: Fix JS/JSON loading babel config ([#6878](https://github.com/storybookjs/storybook/pull/6878)) ## 5.2.0-alpha.18 (May 26, 2019) @@ -2167,55 +3465,55 @@ Publish failed ### Features -* Core: Support loglevel for production/ci builds ([#6825](https://github.com/storybookjs/storybook/pull/6825)) +- Core: Support loglevel for production/ci builds ([#6825](https://github.com/storybookjs/storybook/pull/6825)) ### Bug Fixes -* Addon-storysource: Fix link color ([#6876](https://github.com/storybookjs/storybook/pull/6876)) -* Addon-centered: Fix unnecessary scroll bar ([#6862](https://github.com/storybookjs/storybook/pull/6862)) -* UI: Apply customQueryParams to eject iframe button ([#6817](https://github.com/storybookjs/storybook/pull/6817)) -* Theming: resolve paths locally if possible ([#6808](https://github.com/storybookjs/storybook/pull/6808)) -* Addon-links: Fix withLinks decorator ([#6823](https://github.com/storybookjs/storybook/pull/6823)) +- Addon-storysource: Fix link color ([#6876](https://github.com/storybookjs/storybook/pull/6876)) +- Addon-centered: Fix unnecessary scroll bar ([#6862](https://github.com/storybookjs/storybook/pull/6862)) +- UI: Apply customQueryParams to eject iframe button ([#6817](https://github.com/storybookjs/storybook/pull/6817)) +- Theming: resolve paths locally if possible ([#6808](https://github.com/storybookjs/storybook/pull/6808)) +- Addon-links: Fix withLinks decorator ([#6823](https://github.com/storybookjs/storybook/pull/6823)) ### Maintenance -* Addon-contexts: component tests and readability improvements ([#6716](https://github.com/storybookjs/storybook/pull/6716)) +- Addon-contexts: component tests and readability improvements ([#6716](https://github.com/storybookjs/storybook/pull/6716)) ### Dependency Upgrades -* Upgrade CRA preset dependencies to match CRA v3 ([#6589](https://github.com/storybookjs/storybook/pull/6589)) -* Bump schedule from 0.4.0 to 0.5.0 ([#6843](https://github.com/storybookjs/storybook/pull/6843)) -* UPGRADE dependencies & fix a security vulnerability (low-prio) ([#6875](https://github.com/storybookjs/storybook/pull/6875)) -* Bump simplebar-react from 1.0.0-alpha.9 to 1.0.0 ([#6842](https://github.com/storybookjs/storybook/pull/6842)) -* Bump ts-loader from 5.4.5 to 6.0.1 ([#6839](https://github.com/storybookjs/storybook/pull/6839)) -* Bump @types/react from 16.8.17 to 16.8.18 ([#6841](https://github.com/storybookjs/storybook/pull/6841)) -* Bump core-js-pure from 3.1.0 to 3.1.1 ([#6840](https://github.com/storybookjs/storybook/pull/6840)) +- Upgrade CRA preset dependencies to match CRA v3 ([#6589](https://github.com/storybookjs/storybook/pull/6589)) +- Bump schedule from 0.4.0 to 0.5.0 ([#6843](https://github.com/storybookjs/storybook/pull/6843)) +- UPGRADE dependencies & fix a security vulnerability (low-prio) ([#6875](https://github.com/storybookjs/storybook/pull/6875)) +- Bump simplebar-react from 1.0.0-alpha.9 to 1.0.0 ([#6842](https://github.com/storybookjs/storybook/pull/6842)) +- Bump ts-loader from 5.4.5 to 6.0.1 ([#6839](https://github.com/storybookjs/storybook/pull/6839)) +- Bump @types/react from 16.8.17 to 16.8.18 ([#6841](https://github.com/storybookjs/storybook/pull/6841)) +- Bump core-js-pure from 3.1.0 to 3.1.1 ([#6840](https://github.com/storybookjs/storybook/pull/6840)) ## 5.1.0-rc.0 (May 21, 2019) ### Bug Fixes -* UI: Fix initial bottom panel size ([#6822](https://github.com/storybookjs/storybook/pull/6822)) -* UI: Fix syntaxthighlighter themes ([#6814](https://github.com/storybookjs/storybook/pull/6814)) -* Addon-knobs: Fix Boolean knob (#6366) ([#6830](https://github.com/storybookjs/storybook/pull/6830)) -* Theming: Change lib/theming so it no longer depends on react-inspector ([#6818](https://github.com/storybookjs/storybook/pull/6818)) -* Core: Handle loading `.storybook/babel.config.js` (#6633) ([#6634](https://github.com/storybookjs/storybook/pull/6634)) -* CLI: Fix init in create-react-library projects ([#6815](https://github.com/storybookjs/storybook/pull/6815)) -* HTML: support knobs for both cached and uncached nodes ([#6783](https://github.com/storybookjs/storybook/pull/6783)) -* Uncorrupt yarn lock ([#6811](https://github.com/storybookjs/storybook/pull/6811)) -* Core: set a better value for process in manager webpack config ([#6767](https://github.com/storybookjs/storybook/pull/6767)) +- UI: Fix initial bottom panel size ([#6822](https://github.com/storybookjs/storybook/pull/6822)) +- UI: Fix syntaxthighlighter themes ([#6814](https://github.com/storybookjs/storybook/pull/6814)) +- Addon-knobs: Fix Boolean knob (#6366) ([#6830](https://github.com/storybookjs/storybook/pull/6830)) +- Theming: Change lib/theming so it no longer depends on react-inspector ([#6818](https://github.com/storybookjs/storybook/pull/6818)) +- Core: Handle loading `.storybook/babel.config.js` (#6633) ([#6634](https://github.com/storybookjs/storybook/pull/6634)) +- CLI: Fix init in create-react-library projects ([#6815](https://github.com/storybookjs/storybook/pull/6815)) +- HTML: support knobs for both cached and uncached nodes ([#6783](https://github.com/storybookjs/storybook/pull/6783)) +- Uncorrupt yarn lock ([#6811](https://github.com/storybookjs/storybook/pull/6811)) +- Core: set a better value for process in manager webpack config ([#6767](https://github.com/storybookjs/storybook/pull/6767)) ### Maintenance -* Typescript: Migrate addon-centered ([#6772](https://github.com/storybookjs/storybook/pull/6772)) -* Add engine field to package.json in apps ([#6809](https://github.com/storybookjs/storybook/pull/6809)) -* Fix required engine for apps ([#6810](https://github.com/storybookjs/storybook/pull/6810)) +- Typescript: Migrate addon-centered ([#6772](https://github.com/storybookjs/storybook/pull/6772)) +- Add engine field to package.json in apps ([#6809](https://github.com/storybookjs/storybook/pull/6809)) +- Fix required engine for apps ([#6810](https://github.com/storybookjs/storybook/pull/6810)) ### Dependency Upgrades -* Upgrade lodash to latest ([#6832](https://github.com/storybookjs/storybook/pull/6832)) -* Bump svelte from 3.4.1 to 3.4.2 ([#6838](https://github.com/storybookjs/storybook/pull/6838)) -* Misc upgrades ([#6820](https://github.com/storybookjs/storybook/pull/6820)) +- Upgrade lodash to latest ([#6832](https://github.com/storybookjs/storybook/pull/6832)) +- Bump svelte from 3.4.1 to 3.4.2 ([#6838](https://github.com/storybookjs/storybook/pull/6838)) +- Misc upgrades ([#6820](https://github.com/storybookjs/storybook/pull/6820)) ## 5.2.0-alpha.12 (May 21, 2019) @@ -2244,18 +3542,18 @@ Publish failed ### Bug Fixes -* UI: Scrollbar supports theming again ([#6794](https://github.com/storybookjs/storybook/pull/6794)) -* UI: Fix scrolling styling ([#6785](https://github.com/storybookjs/storybook/pull/6785)) -* UI: Fix iframe refresh ([#6787](https://github.com/storybookjs/storybook/pull/6787)) -* UI: Preserve dimensions on resizing for panel ([#6696](https://github.com/storybookjs/storybook/pull/6696)) +- UI: Scrollbar supports theming again ([#6794](https://github.com/storybookjs/storybook/pull/6794)) +- UI: Fix scrolling styling ([#6785](https://github.com/storybookjs/storybook/pull/6785)) +- UI: Fix iframe refresh ([#6787](https://github.com/storybookjs/storybook/pull/6787)) +- UI: Preserve dimensions on resizing for panel ([#6696](https://github.com/storybookjs/storybook/pull/6696)) ### Maintenance -* Move chromatic to circle ci ([#6752](https://github.com/storybookjs/storybook/pull/6752)) +- Move chromatic to circle ci ([#6752](https://github.com/storybookjs/storybook/pull/6752)) ### Dependency Upgrades -* Bump fs-extra from 7.0.1 to 8.0.1 ([#6776](https://github.com/storybookjs/storybook/pull/6776)) +- Bump fs-extra from 7.0.1 to 8.0.1 ([#6776](https://github.com/storybookjs/storybook/pull/6776)) ## 5.2.0-alpha.8 (May 15, 2019) @@ -2297,7 +3595,7 @@ After: #### Breaking changes - Doc blocks & presets have moved. Update your MDX stories and `presets.js` file accordingly: - - `import { Preview, Story } from '@storybook/addon-docs/blocks'; + - `import { Preview, Story } from '@storybook/addon-docs/blocks';` - `module.exports = ['@storybook/addon-docs/common/preset'];` ## 5.1.0-beta.0 (May 10, 2019) @@ -9201,57 +10499,57 @@ Storybook 3.0 is our first fully community-driven release! Notable changes: ## v2.35.3 -Allow customConfig to override devtool. [PR668](https://github.com/storybookjs/react-storybook/pull/668) +Allow customConfig to override devtool. [PR668](https://github.com/storybookjs/storybook/pull/668) ## v2.35.2 03-January-2017 -Fixes issue [#601](https://github.com/storybookjs/react-storybook/issues/601) where it throws error when introduce a propType with a hypen. Add a [fix](https://github.com/kadirahq/babel-plugin-react-docgen/pull/23) to [`babel-plugin-react-docgen`](https://github.com/kadirahq/babel-plugin-react-docgen) to fix this issue. +Fixes issue [#601](https://github.com/storybookjs/storybook/issues/601) where it throws error when introduce a propType with a hypen. Add a [fix](https://github.com/kadirahq/babel-plugin-react-docgen/pull/23) to [`babel-plugin-react-docgen`](https://github.com/kadirahq/babel-plugin-react-docgen) to fix this issue. This release comes with the updated `babel-plugin-react-docgen`. ## v2.35.1 -- Revert [PR653](https://github.com/storybookjs/react-storybook/pull/653) where it's causing HMR to not working properly. +- Revert [PR653](https://github.com/storybookjs/storybook/pull/653) where it's causing HMR to not working properly. ## v2.35.0 18-December-2016 -- Using file-loader to load all the extensions [PR653](https://github.com/storybookjs/react-storybook/pull/653) -- Update css-loader dependency [PR648](https://github.com/storybookjs/react-storybook/pull/648) -- Check if stories are loaded from Jest [PR644](https://github.com/storybookjs/react-storybook/pull/644) +- Using file-loader to load all the extensions [PR653](https://github.com/storybookjs/storybook/pull/653) +- Update css-loader dependency [PR648](https://github.com/storybookjs/storybook/pull/648) +- Check if stories are loaded from Jest [PR644](https://github.com/storybookjs/storybook/pull/644) ## v2.34.0 05-December-2016 -Open the express router for developers (middleware.js file). [PR435](https://github.com/storybookjs/react-storybook/pull/435) +Open the express router for developers (middleware.js file). [PR435](https://github.com/storybookjs/storybook/pull/435) ## v2.33.1 01-December-2016 -Update Typescript definition file for global addDecorator. [PR634](https://github.com/storybookjs/react-storybook/pull/634) +Update Typescript definition file for global addDecorator. [PR634](https://github.com/storybookjs/storybook/pull/634) ## v2.33.0 28-November-2016 -Completely avoid re-rendering the preview iframe. [PR631](https://github.com/storybookjs/react-storybook/pull/631) +Completely avoid re-rendering the preview iframe. [PR631](https://github.com/storybookjs/storybook/pull/631) ## v2.32.2 28-November-2016 -Update postmsg channel module version [PR627](https://github.com/storybookjs/react-storybook/pull/627) +Update postmsg channel module version [PR627](https://github.com/storybookjs/storybook/pull/627) ## v2.32.1 22-November-2016 -Add support for react_perf comes with React 15.4.0. [PR623](https://github.com/storybookjs/react-storybook/pull/623) +Add support for react_perf comes with React 15.4.0. [PR623](https://github.com/storybookjs/storybook/pull/623) ## v2.32.0 @@ -9261,13 +10559,13 @@ Incorrect publish (error when running `npm publish`) 20-November-2016 -Add the react-storybook version to the build output. [PR621](https://github.com/storybookjs/react-storybook/pull/621) +Add the react-storybook version to the build output. [PR621](https://github.com/storybookjs/storybook/pull/621) ## v2.30.1 17-November-2016 -Update the postmsg channel module to fix issue [#555](https://github.com/storybookjs/react-storybook/issues/555) with [PR611](https://github.com/storybookjs/react-storybook/pull/611) +Update the postmsg channel module to fix issue [#555](https://github.com/storybookjs/storybook/issues/555) with [PR611](https://github.com/storybookjs/storybook/pull/611) ## v2.30.0 @@ -9285,19 +10583,19 @@ Update @kadira/storybook-ui to the latest. 10-November-2016 -Fix a typo in the story syntax error messages. [PR610](https://github.com/storybookjs/react-storybook/pull/610) +Fix a typo in the story syntax error messages. [PR610](https://github.com/storybookjs/storybook/pull/610) ## v2.29.5 09-November-2016 -Check if regex and regex.test is available before calling it. [PR608](https://github.com/storybookjs/react-storybook/pull/608) +Check if regex and regex.test is available before calling it. [PR608](https://github.com/storybookjs/storybook/pull/608) ## v2.29.3 08-November-2016 -Update webpack-hot-middleware to version 2.13.2 to fix the issue [#543](https://github.com/storybookjs/react-storybook/issues/543). +Update webpack-hot-middleware to version 2.13.2 to fix the issue [#543](https://github.com/storybookjs/storybook/issues/543). ## v2.29.3 @@ -9312,11 +10610,11 @@ There was a text called undefined listed always on the top of the preview. Add various fixes. -- Use webpack chunkhash to enable long-term caching. [PR597](https://github.com/storybookjs/react-storybook/pull/597) -- Fixed json loader testing for when test is multiple. [PR598](https://github.com/storybookjs/react-storybook/pull/598) -- Fix usage of custom favicon [PR592](https://github.com/storybookjs/react-storybook/pull/592) -- Update postcss-loader to v1.1.0 [PR599](https://github.com/storybookjs/react-storybook/pull/599) -- fix for `module.hot` is not available in a static build [PR600](https://github.com/storybookjs/react-storybook/pull/600) +- Use webpack chunkhash to enable long-term caching. [PR597](https://github.com/storybookjs/storybook/pull/597) +- Fixed json loader testing for when test is multiple. [PR598](https://github.com/storybookjs/storybook/pull/598) +- Fix usage of custom favicon [PR592](https://github.com/storybookjs/storybook/pull/592) +- Update postcss-loader to v1.1.0 [PR599](https://github.com/storybookjs/storybook/pull/599) +- fix for `module.hot` is not available in a static build [PR600](https://github.com/storybookjs/storybook/pull/600) ## v2.29.1 @@ -9335,13 +10633,13 @@ This will fix some of the compilation issues such as #580. 28-October-2016 -Remove preview decorator support. [PR583](https://github.com/storybookjs/react-storybook/pull/583). +Remove preview decorator support. [PR583](https://github.com/storybookjs/storybook/pull/583). ## v2.28.0 28-October-2016 -Add preview decorator support. [PR582](https://github.com/storybookjs/react-storybook/pull/582). +Add preview decorator support. [PR582](https://github.com/storybookjs/storybook/pull/582). This will help us bring storybook designer with some great power. ## v2.27.0 @@ -9350,11 +10648,11 @@ This will help us bring storybook designer with some great power. Add a few usability improvements to Storybook. -- Display storybook version. [PR559](https://github.com/storybookjs/react-storybook/pull/559) -- Make the storybooks cacheable. [PR578](https://github.com/storybookjs/react-storybook/pull/578) -- Change the devtool to eval and remove the use of source maps. [PR577](https://github.com/storybookjs/react-storybook/pull/577) -- Update `babel-preset-react-app` to the latest. [PR576](https://github.com/storybookjs/react-storybook/pull/576) -- Ship `json-loader` by default. [PR575](https://github.com/storybookjs/react-storybook/pull/575) +- Display storybook version. [PR559](https://github.com/storybookjs/storybook/pull/559) +- Make the storybooks cacheable. [PR578](https://github.com/storybookjs/storybook/pull/578) +- Change the devtool to eval and remove the use of source maps. [PR577](https://github.com/storybookjs/storybook/pull/577) +- Update `babel-preset-react-app` to the latest. [PR576](https://github.com/storybookjs/storybook/pull/576) +- Ship `json-loader` by default. [PR575](https://github.com/storybookjs/storybook/pull/575) ## v2.26.0 @@ -9362,21 +10660,21 @@ Add a few usability improvements to Storybook. Get some new features from CRA. -- Add jsx as a resolve extension [PR563](https://github.com/storybookjs/react-storybook/pull/563) -- Allow to use postcss for CSS @imports [PR564](https://github.com/storybookjs/react-storybook/pull/564) -- Use process.env as a proper object [PR565](https://github.com/storybookjs/react-storybook/pull/565) +- Add jsx as a resolve extension [PR563](https://github.com/storybookjs/storybook/pull/563) +- Allow to use postcss for CSS @imports [PR564](https://github.com/storybookjs/storybook/pull/564) +- Use process.env as a proper object [PR565](https://github.com/storybookjs/storybook/pull/565) ## v2.25.1 23-October-2016 -Add a potential fix to [558](https://github.com/storybookjs/react-storybook/issues/558) by updating babel-plugin-react-docgen to the latest(v1.3.2). +Add a potential fix to [558](https://github.com/storybookjs/storybook/issues/558) by updating babel-plugin-react-docgen to the latest(v1.3.2). ## v2.25.0 21-October-2016 -Add react docgen info into React classes with the react-docgen babel plugin. [PR557](https://github.com/storybookjs/react-storybook/pull/557). +Add react docgen info into React classes with the react-docgen babel plugin. [PR557](https://github.com/storybookjs/storybook/pull/557). With this: - We could get docgen info with any React component class using `ClassName.__docgenInfo`. @@ -9388,19 +10686,19 @@ Additionally, added `yarn.lock`. 19-October-2016 -Do not show git command output. [PR554](https://github.com/storybookjs/react-storybook/pull/554) +Do not show git command output. [PR554](https://github.com/storybookjs/storybook/pull/554) ## v2.24.0 07-October-2016 -- Export git repository info to support custom tool integrations [PR536](https://github.com/storybookjs/react-storybook/pull/536) +- Export git repository info to support custom tool integrations [PR536](https://github.com/storybookjs/storybook/pull/536) ## v2.23.0 06-October-2016 -- Remove the experimental database addon from react-storybook [PR535](https://github.com/storybookjs/react-storybook/pull/535) +- Remove the experimental database addon from react-storybook [PR535](https://github.com/storybookjs/storybook/pull/535) ## v2.22.0 @@ -9408,142 +10706,142 @@ Do not show git command output. [PR554](https://github.com/storybookjs/react-sto Add some nice development experiment based on suggestion from Dan Abramov. -- Set a color to the Storybook URL in the console. [PR533](https://github.com/storybookjs/react-storybook/pull/533) -- Add better error message when there's no React element in the story. [PR534](https://github.com/storybookjs/react-storybook/pull/534) +- Set a color to the Storybook URL in the console. [PR533](https://github.com/storybookjs/storybook/pull/533) +- Add better error message when there's no React element in the story. [PR534](https://github.com/storybookjs/storybook/pull/534) ## v2.21.0 05-October-2016 -- Get the latest features from CRA including NODE_PATH support, public folder support and some other minor changes. [#468](https://github.com/storybookjs/react-storybook/issues/468) +- Get the latest features from CRA including NODE_PATH support, public folder support and some other minor changes. [#468](https://github.com/storybookjs/storybook/issues/468) - Also bumped `@kadira/storybook-channel-postmsg` to `^1.0.3` ## v2.20.1 28-September-2016 -- Fix story kind order bug [PR499](https://github.com/storybookjs/react-storybook/pull/499) -- Prefix config environment variables [PR503](https://github.com/storybookjs/react-storybook/pull/503) +- Fix story kind order bug [PR499](https://github.com/storybookjs/storybook/pull/499) +- Prefix config environment variables [PR503](https://github.com/storybookjs/storybook/pull/503) ## v2.20.0 26-September-2016 -- Use postMessage channel [PR498](https://github.com/storybookjs/react-storybook/pull/498) -- Support dynamic panel titles [PR497](https://github.com/storybookjs/react-storybook/pull/497) +- Use postMessage channel [PR498](https://github.com/storybookjs/storybook/pull/498) +- Support dynamic panel titles [PR497](https://github.com/storybookjs/storybook/pull/497) ## v2.19.0 26-September-2016 -- Support layout options [PR494](https://github.com/storybookjs/react-storybook/pull/494) -- Update Typescript definitions [PR491](https://github.com/storybookjs/react-storybook/pull/491) and [PR493](https://github.com/storybookjs/react-storybook/pull/493) +- Support layout options [PR494](https://github.com/storybookjs/storybook/pull/494) +- Update Typescript definitions [PR491](https://github.com/storybookjs/storybook/pull/491) and [PR493](https://github.com/storybookjs/storybook/pull/493) ## v2.18.1 23-September-2016 -- Stop uglifyjs from mangling names [PR483](https://github.com/storybookjs/react-storybook/pull/483) +- Stop uglifyjs from mangling names [PR483](https://github.com/storybookjs/storybook/pull/483) ## v2.18.0 23-September-2016 -- Remove `STORYBOOK_` prefix from config env [PR481](https://github.com/storybookjs/react-storybook/pull/481) +- Remove `STORYBOOK_` prefix from config env [PR481](https://github.com/storybookjs/storybook/pull/481) ## v2.17.0 22-September-2016 -- Add support for StoryShots. [PR479](https://github.com/storybookjs/react-storybook/pull/479) -- Fix some typos: [PR477](https://github.com/storybookjs/react-storybook/pull/477) & [PR478](https://github.com/storybookjs/react-storybook/pull/478) +- Add support for StoryShots. [PR479](https://github.com/storybookjs/storybook/pull/479) +- Fix some typos: [PR477](https://github.com/storybookjs/storybook/pull/477) & [PR478](https://github.com/storybookjs/storybook/pull/478) ## v2.16.1 21-September-2016 -- Fix the 404 error for `addon-db.json` file [PR472](https://github.com/storybookjs/react-storybook/pull/472) -- Serve/Bundle the storybook favicon [PR473](https://github.com/storybookjs/react-storybook/pull/473) +- Fix the 404 error for `addon-db.json` file [PR472](https://github.com/storybookjs/storybook/pull/472) +- Serve/Bundle the storybook favicon [PR473](https://github.com/storybookjs/storybook/pull/473) ## v2.16.0 21-September-2016 -- Move the babel config loading logic into a seperate file. [PR469](https://github.com/storybookjs/react-storybook/pull/469) +- Move the babel config loading logic into a seperate file. [PR469](https://github.com/storybookjs/storybook/pull/469) - Update airbnd eslint rules to the latest. ## v2.15.1 19-September-2016 -Add a fix to webpack custom resolve.alias not working. [PR465](https://github.com/storybookjs/react-storybook/pull/465) +Add a fix to webpack custom resolve.alias not working. [PR465](https://github.com/storybookjs/storybook/pull/465) ## v2.15.0 19-September-2016 -- Use @kadira/storybook-addons as a resolve.alias. So, we can support addons for NPM2 too. [PR462](https://github.com/storybookjs/react-storybook/pull/462) +- Use @kadira/storybook-addons as a resolve.alias. So, we can support addons for NPM2 too. [PR462](https://github.com/storybookjs/storybook/pull/462) ## v2.14.0 14-September-2016 -- Watch missing NPM modules and force webpack rebuild. [PR446](https://github.com/storybookjs/react-storybook/pull/446) -- Fix issue on error message hanging after even it solved. [PR447](https://github.com/storybookjs/react-storybook/pull/447) -- Allow to reload if HMR goes crazy. [PR448](https://github.com/storybookjs/react-storybook/pull/448) -- Add support to get custom env variables. [PR450](https://github.com/storybookjs/react-storybook/pull/450) +- Watch missing NPM modules and force webpack rebuild. [PR446](https://github.com/storybookjs/storybook/pull/446) +- Fix issue on error message hanging after even it solved. [PR447](https://github.com/storybookjs/storybook/pull/447) +- Allow to reload if HMR goes crazy. [PR448](https://github.com/storybookjs/storybook/pull/448) +- Add support to get custom env variables. [PR450](https://github.com/storybookjs/storybook/pull/450) ## v2.13.1 14-September-2016 -- Fix 404 error when db file does not exist [PR449](https://github.com/storybookjs/react-storybook/pull/449) +- Fix 404 error when db file does not exist [PR449](https://github.com/storybookjs/storybook/pull/449) ## v2.13.0 9-September-2016 -- Fix [#443](https://github.com/storybookjs/react-storybook/issues/443) where the static version of Storybook doesn't like Safari. +- Fix [#443](https://github.com/storybookjs/storybook/issues/443) where the static version of Storybook doesn't like Safari. - Update postcss-loader to 0.13.0. ## v2.12.1 8-September-2016 -- Parse static directory provided by env as a list. [PR436](https://github.com/storybookjs/react-storybook/pull/436) +- Parse static directory provided by env as a list. [PR436](https://github.com/storybookjs/storybook/pull/436) ## v2.12.0 8-September-2016 -- Do not include addon register file on preview. [PR426](https://github.com/storybookjs/react-storybook/pull/426) -- Update css-loader to version 0.25.0. [PR427](https://github.com/storybookjs/react-storybook/pull/427) -- Get the head.html values for every page request. [PR432](https://github.com/storybookjs/react-storybook/pull/432) +- Do not include addon register file on preview. [PR426](https://github.com/storybookjs/storybook/pull/426) +- Update css-loader to version 0.25.0. [PR427](https://github.com/storybookjs/storybook/pull/427) +- Get the head.html values for every page request. [PR432](https://github.com/storybookjs/storybook/pull/432) ## v2.11.0 4-September-2016 - Remove babel-polyfill since we don't use it. -- Update versions with the help from greenkeeper. [PR421](https://github.com/storybookjs/react-storybook/pull/421) +- Update versions with the help from greenkeeper. [PR421](https://github.com/storybookjs/storybook/pull/421) ## v2.10.0 3-September-2016 -- Adding airbnb-js-shims again. [PR419](https://github.com/storybookjs/react-storybook/pull/419) +- Adding airbnb-js-shims again. [PR419](https://github.com/storybookjs/storybook/pull/419) ## v2.9.1 2-September-2016. -- Use the config directory to store the addon database file [PR418](https://github.com/storybookjs/react-storybook/pull/418). +- Use the config directory to store the addon database file [PR418](https://github.com/storybookjs/storybook/pull/418). ## v2.9.0 2-September-2016. -- Copy the addon-db.json file when building static storybooks [PR417](https://github.com/storybookjs/react-storybook/pull/417). +- Copy the addon-db.json file when building static storybooks [PR417](https://github.com/storybookjs/storybook/pull/417). ## v2.8.0 @@ -9555,25 +10853,25 @@ Add a fix to webpack custom resolve.alias not working. [PR465](https://github.co 1-September-2016 -- Add addon database feature [PR415](https://github.com/storybookjs/react-storybook/pull/415). +- Add addon database feature [PR415](https://github.com/storybookjs/storybook/pull/415). ## v2.6.1 31-August-2016 -- Bring back HMR dev logs. [PR412](https://github.com/storybookjs/react-storybook/pull/412). +- Bring back HMR dev logs. [PR412](https://github.com/storybookjs/storybook/pull/412). ## v2.6.0 30-August-2016 -- Allow start/build params from env variables. [PR413](https://github.com/storybookjs/react-storybook/pull/413) +- Allow start/build params from env variables. [PR413](https://github.com/storybookjs/storybook/pull/413) ## v2.5.2 29-August-2016 -- Remove the use of babel-runtime/core-js modules. [PR410](https://github.com/storybookjs/react-storybook/pull/410) +- Remove the use of babel-runtime/core-js modules. [PR410](https://github.com/storybookjs/storybook/pull/410) ## v2.5.1 @@ -9585,13 +10883,13 @@ Add a fix to webpack custom resolve.alias not working. [PR465](https://github.co 24-August-2016 -- We are no longer shipping extra polyfills anymore. [PR402](https://github.com/storybookjs/react-storybook/pull/402) +- We are no longer shipping extra polyfills anymore. [PR402](https://github.com/storybookjs/storybook/pull/402) ## v2.4.2 24-August-2016 -- Allow file-loader URLs to work on subpaths. [PR401](https://github.com/storybookjs/react-storybook/pull/401) +- Allow file-loader URLs to work on subpaths. [PR401](https://github.com/storybookjs/storybook/pull/401) ## v2.4.1 @@ -9603,34 +10901,34 @@ Add a fix to webpack custom resolve.alias not working. [PR465](https://github.co 23-August-2016 -- Simplify the option to stop tracking. [PR399](https://github.com/storybookjs/react-storybook/pull/399) -- Use JSON5 instead of CJSON to parse .babelrc. [PR398](https://github.com/storybookjs/react-storybook/pull/398) -- Add webpack2 support by changing the use of OccurenceOrderPlugin. [PR397](https://github.com/storybookjs/react-storybook/pull/397) +- Simplify the option to stop tracking. [PR399](https://github.com/storybookjs/storybook/pull/399) +- Use JSON5 instead of CJSON to parse .babelrc. [PR398](https://github.com/storybookjs/storybook/pull/398) +- Add webpack2 support by changing the use of OccurenceOrderPlugin. [PR397](https://github.com/storybookjs/storybook/pull/397) - Use @kadira/storybook-ui 2.3.0, which has new APIs to set URL for addons. ## v2.3.0 16-August-2016 -- Implement anonymous usage tracking. [PR384](https://github.com/storybookjs/react-storybook/pull/384) +- Implement anonymous usage tracking. [PR384](https://github.com/storybookjs/storybook/pull/384) ## v2.2.3 15-August-2016 -- Add a hash to media file's filename. Otherwise, it'll cause issues when there are multiple images with the same filename but in different directories. [PR380](https://github.com/storybookjs/react-storybook/pull/380) +- Add a hash to media file's filename. Otherwise, it'll cause issues when there are multiple images with the same filename but in different directories. [PR380](https://github.com/storybookjs/storybook/pull/380) ## v2.2.2 10-August-2016 -- Remove unused extract-text-webpack-plugin. This will add webpack2 support. [PR369](https://github.com/storybookjs/react-storybook/pull/369). +- Remove unused extract-text-webpack-plugin. This will add webpack2 support. [PR369](https://github.com/storybookjs/storybook/pull/369). ## v2.2.1 09-August-2016 -- Use @kadira/storybook-channel modules. [#PR359](https://github.com/storybookjs/react-storybook/pull/359). +- Use @kadira/storybook-channel modules. [#PR359](https://github.com/storybookjs/storybook/pull/359). - Update @kadira/storybook-ui to the latest. ## v2.2.0 @@ -9639,25 +10937,25 @@ Add a fix to webpack custom resolve.alias not working. [PR465](https://github.co This release bring some webpack config related optimizations and the NPM2 support. Here are the notable changes: -- Use es6-shim directly into webpack config. [PR355](https://github.com/storybookjs/react-storybook/pull/355) -- Use the default babel-config based on CRA's config. [PR354](https://github.com/storybookjs/react-storybook/pull/354) -- Add NPM2 support. [PR356](https://github.com/storybookjs/react-storybook/pull/356) -- Add autofixer defaults. [PR357](https://github.com/storybookjs/react-storybook/pull/357) +- Use es6-shim directly into webpack config. [PR355](https://github.com/storybookjs/storybook/pull/355) +- Use the default babel-config based on CRA's config. [PR354](https://github.com/storybookjs/storybook/pull/354) +- Add NPM2 support. [PR356](https://github.com/storybookjs/storybook/pull/356) +- Add autofixer defaults. [PR357](https://github.com/storybookjs/storybook/pull/357) ## v2.1.1 03-August-2016 -Remove default webpack config for all config types. [PR348](https://github.com/storybookjs/react-storybook/pull/348) +Remove default webpack config for all config types. [PR348](https://github.com/storybookjs/storybook/pull/348) Now we only use the Create React App based config if there's no custom webpack config. -This will fix issues like [#347](https://github.com/storybookjs/react-storybook/issues/347). +This will fix issues like [#347](https://github.com/storybookjs/storybook/issues/347). ## v2.1.0 02-August-2016 -Add support for the addon API. See [PR346](https://github.com/storybookjs/react-storybook/pull/346). +Add support for the addon API. See [PR346](https://github.com/storybookjs/storybook/pull/346). Here after we are using most of the features including actions,links as plugins. So, this introduced a huge area to add customizations to Storybook. @@ -9667,7 +10965,7 @@ Unfortunately, as of this version, there are no docs for this feature. But, you - actions addon (powers the action logger): [addon-actions](https://github.com/kadirahq/storybook-addon-actions) - links addon (powers the linkTo feature): [addon-links](https://github.com/kadirahq/storybook-addon-links) -Have a look at [here](https://github.com/storybookjs/react-storybook/blob/master/src/server/config.js#L88) to how to configure addons. +Have a look at [here](https://github.com/storybookjs/storybook/blob/master/src/server/config.js#L88) to how to configure addons. ## v2.0.0 @@ -9675,7 +10973,7 @@ Have a look at [here](https://github.com/storybookjs/react-storybook/blob/master This is the starting of the next major version of Storybook. This version is almost compatible with `v1.x.x` but defaults have been changes as discussed below. That's why we are starting out a new version. -- Update defaults to match create-react-app. [PR342](https://github.com/storybookjs/react-storybook/pull/342). Here are the notable changes: +- Update defaults to match create-react-app. [PR342](https://github.com/storybookjs/storybook/pull/342). Here are the notable changes: - Add postcss based CSS loader. - Add file-loader for images and common types. - Add url-loader for shorter media files. @@ -9685,17 +10983,17 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.41.0 -- Fix nodejs require errors [#337](https://github.com/storybookjs/react-storybook/pull/337). -- Add getStorybook method to client API [#332](https://github.com/storybookjs/react-storybook/pull/332). +- Fix nodejs require errors [#337](https://github.com/storybookjs/storybook/pull/337). +- Add getStorybook method to client API [#332](https://github.com/storybookjs/storybook/pull/332). ## v1.40.0 -- Fix duplicate decorator bug [#335](https://github.com/storybookjs/react-storybook/pull/335). +- Fix duplicate decorator bug [#335](https://github.com/storybookjs/storybook/pull/335). ## v1.39.1 -- Update babel packages [#325](https://github.com/storybookjs/react-storybook/pull/325). -- Hide HMR info logs [#331](https://github.com/storybookjs/react-storybook/pull/331). +- Update babel packages [#325](https://github.com/storybookjs/storybook/pull/325). +- Hide HMR info logs [#331](https://github.com/storybookjs/storybook/pull/331). ## v1.39.0 @@ -9703,19 +11001,19 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.38.3 -- Add names for action and linkTo functions [#321](https://github.com/storybookjs/react-storybook/pull/321). +- Add names for action and linkTo functions [#321](https://github.com/storybookjs/storybook/pull/321). ## v1.38.2 -- Fix error in prepublish script [#319](https://github.com/storybookjs/react-storybook/pull/319). +- Fix error in prepublish script [#319](https://github.com/storybookjs/storybook/pull/319). ## v1.38.1 -- Improve Windows support by writing prepublish script using shelljs [#308](https://github.com/storybookjs/react-storybook/pull/308). +- Improve Windows support by writing prepublish script using shelljs [#308](https://github.com/storybookjs/storybook/pull/308). ## v1.38.0 -- v1.37.0 was a nightmare since it contains the npm-shrinkwrap.json. Fixed by removing it. See: [#306](https://github.com/storybookjs/react-storybook/issues/306) and [#305](https://github.com/storybookjs/react-storybook/pull/305). +- v1.37.0 was a nightmare since it contains the npm-shrinkwrap.json. Fixed by removing it. See: [#306](https://github.com/storybookjs/storybook/issues/306) and [#305](https://github.com/storybookjs/storybook/pull/305). ## v1.37.0 @@ -9723,7 +11021,7 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.36.0 -- Support watchOptions configuration. See: [PR287](https://github.com/storybookjs/react-storybook/pull/287) +- Support watchOptions configuration. See: [PR287](https://github.com/storybookjs/storybook/pull/287) ## v1.35.2 @@ -9731,7 +11029,7 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.35.1 -- Fix issue related to bad error handling. See issue [#275](https://github.com/storybookjs/react-storybook/issues/275): +- Fix issue related to bad error handling. See issue [#275](https://github.com/storybookjs/storybook/issues/275): ## v1.35.0 @@ -9739,7 +11037,7 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.34.1 -- Don't always override NODE_ENV in build-storybook. [PR272](https://github.com/storybookjs/react-storybook/pull/272) +- Don't always override NODE_ENV in build-storybook. [PR272](https://github.com/storybookjs/storybook/pull/272) ## v1.34.0 @@ -9747,7 +11045,7 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.33.0 -- Introduce an [extension API](https://github.com/storybookjs/react-storybook/blob/master/docs/extensions.md) for Storybook. See: [PR258](https://github.com/storybookjs/react-storybook/pull/258) +- Introduce an [extension API](https://github.com/storybookjs/storybook/blob/master/docs/extensions.md) for Storybook. See: [PR258](https://github.com/storybookjs/storybook/pull/258) ## v1.32.1 @@ -9759,67 +11057,67 @@ This is the starting of the next major version of Storybook. This version is alm ## v1.31.0 -- Pass a `context` argument to stories [PR250](https://github.com/storybookjs/react-storybook/pull/250) +- Pass a `context` argument to stories [PR250](https://github.com/storybookjs/storybook/pull/250) ## v1.30.0 -- Fuzzy search kinds [PR247](https://github.com/storybookjs/react-storybook/pull/247) +- Fuzzy search kinds [PR247](https://github.com/storybookjs/storybook/pull/247) ## v1.29.5 -- Update dependency version to fix filter crash [PR246](https://github.com/storybookjs/react-storybook/pull/246) +- Update dependency version to fix filter crash [PR246](https://github.com/storybookjs/storybook/pull/246) ## v1.29.4 -- Protect index.html/iframe.html from being overwritten [PR243](https://github.com/storybookjs/react-storybook/pull/243) +- Protect index.html/iframe.html from being overwritten [PR243](https://github.com/storybookjs/storybook/pull/243) ## v1.29.3 -- Update @kadira/storybook-core version [PR241](https://github.com/storybookjs/react-storybook/pull/241) -- Add es6-shim by default [PR238](https://github.com/storybookjs/react-storybook/pull/238) +- Update @kadira/storybook-core version [PR241](https://github.com/storybookjs/storybook/pull/241) +- Add es6-shim by default [PR238](https://github.com/storybookjs/storybook/pull/238) ## v1.29.2 -- Use url.resolve instead of path.join [PR240](https://github.com/storybookjs/react-storybook/pull/240) +- Use url.resolve instead of path.join [PR240](https://github.com/storybookjs/storybook/pull/240) ## v1.29.1 -- Copy missed manager.js.map file on static build [PR236](https://github.com/storybookjs/react-storybook/pull/236) +- Copy missed manager.js.map file on static build [PR236](https://github.com/storybookjs/storybook/pull/236) ## v1.29.0 -- Multiple static dirs (comma separated) [PR229](https://github.com/storybookjs/react-storybook/pull/229) +- Multiple static dirs (comma separated) [PR229](https://github.com/storybookjs/storybook/pull/229) ## v1.28.5 -- Support ECMAScript stage-0 [PR228](https://github.com/storybookjs/react-storybook/pull/228) to fix [Issue #227](https://github.com/storybookjs/react-storybook/issues/227) +- Support ECMAScript stage-0 [PR228](https://github.com/storybookjs/storybook/pull/228) to fix [Issue #227](https://github.com/storybookjs/storybook/issues/227) ## v1.28.4 -- Support custom webpack public path for dev-server and static build started by [PR226](https://github.com/storybookjs/react-storybook/pull/226) +- Support custom webpack public path for dev-server and static build started by [PR226](https://github.com/storybookjs/storybook/pull/226) ## v1.28.3 -- Revert [PR226](https://github.com/storybookjs/react-storybook/pull/226) +- Revert [PR226](https://github.com/storybookjs/storybook/pull/226) ## v1.28.2 -- Support custom webpack publicPath [PR226](https://github.com/storybookjs/react-storybook/pull/226) +- Support custom webpack publicPath [PR226](https://github.com/storybookjs/storybook/pull/226) ## v1.28.1 -- Add charset meta tags to HTML heads [PR216](https://github.com/storybookjs/react-storybook/pull/216) +- Add charset meta tags to HTML heads [PR216](https://github.com/storybookjs/storybook/pull/216) ## v1.28.0 - Moved storybook serving code into a middleware to support more advanced use cases. -- Refactored dev server to use storybook middleware [PR211](https://github.com/storybookjs/react-storybook/pull/211) +- Refactored dev server to use storybook middleware [PR211](https://github.com/storybookjs/storybook/pull/211) ## v1.27.0 -- Move modules to storybook-core repo. [PR196](https://github.com/storybookjs/react-storybook/pull/196) +- Move modules to storybook-core repo. [PR196](https://github.com/storybookjs/storybook/pull/196) - Add stack-source-map again only for Chrome to get better error stacks. -- Add ability to control the hostname. See [PR195](https://github.com/storybookjs/react-storybook/pull/195) and [PR198](https://github.com/storybookjs/react-storybook/pull/198) +- Add ability to control the hostname. See [PR195](https://github.com/storybookjs/storybook/pull/195) and [PR198](https://github.com/storybookjs/storybook/pull/198) ## v1.26.0 @@ -9831,34 +11129,34 @@ This is the starting of the next major version of Storybook. This version is alm 11-May-2016 -- Fix several publishing related issues. See: [#188](https://github.com/storybookjs/react-storybook/pull/188). -- Fix babel extends issue. See: [PR185](https://github.com/storybookjs/react-storybook/pull/185). +- Fix several publishing related issues. See: [#188](https://github.com/storybookjs/storybook/pull/188). +- Fix babel extends issue. See: [PR185](https://github.com/storybookjs/storybook/pull/185). - Fix issue with removing a preset from users babelrc. - - Fixes: [#183](https://github.com/storybookjs/react-storybook/issues/183). - - [PR184](https://github.com/storybookjs/react-storybook/pull/184) -- Make left panel scrollable with keeping the filterbox always. See: [PR182](https://github.com/storybookjs/react-storybook/pull/182). + - Fixes: [#183](https://github.com/storybookjs/storybook/issues/183). + - [PR184](https://github.com/storybookjs/storybook/pull/184) +- Make left panel scrollable with keeping the filterbox always. See: [PR182](https://github.com/storybookjs/storybook/pull/182). - Add `qs` as a direct dependency as it's used in preview. ## v1.24.0 10-May-2016 -- Add a potential fix for the double scrollbar issue. See: [179](https://github.com/storybookjs/react-storybook/issues/179). -- Add scrolling support to the left panel. Fixes [#177](https://github.com/storybookjs/react-storybook/issues/177). -- Remove NODE_ENV=production flag. Fixes [#158](https://github.com/storybookjs/react-storybook/issues/158) +- Add a potential fix for the double scrollbar issue. See: [179](https://github.com/storybookjs/storybook/issues/179). +- Add scrolling support to the left panel. Fixes [#177](https://github.com/storybookjs/storybook/issues/177). +- Remove NODE_ENV=production flag. Fixes [#158](https://github.com/storybookjs/storybook/issues/158) ## v1.23.0 09-May-2016 -- Add shortcuts to jump to previous and next stories. See [PR176](https://github.com/storybookjs/react-storybook/pull/176) -- Fix loader concatenation bug specially when custom config doesn't have a loaders section. [PR173](https://github.com/storybookjs/react-storybook/pull/173) +- Add shortcuts to jump to previous and next stories. See [PR176](https://github.com/storybookjs/storybook/pull/176) +- Fix loader concatenation bug specially when custom config doesn't have a loaders section. [PR173](https://github.com/storybookjs/storybook/pull/173) ## v1.22.1 06-May-2016 -- Add a potential fix for [#167](https://github.com/storybookjs/react-storybook/issues/167) +- Add a potential fix for [#167](https://github.com/storybookjs/storybook/issues/167) - basically, this moved back babel-packages required by webpack. ## v1.22.0 @@ -9871,95 +11169,95 @@ This is the starting of the next major version of Storybook. This version is alm 06-May-2016 -- Add configType argument to custom config function. See: [PR169](https://github.com/storybookjs/react-storybook/pull/169) -- Add the unicode version of the Keyboard Shortcut Icon. See: [PR170](https://github.com/storybookjs/react-storybook/pull/170) +- Add configType argument to custom config function. See: [PR169](https://github.com/storybookjs/storybook/pull/169) +- Add the unicode version of the Keyboard Shortcut Icon. See: [PR170](https://github.com/storybookjs/storybook/pull/170) ## v1.20.0 05-May-2016 -- Allow to configure webpack as the user wants. See [PR160](https://github.com/storybookjs/react-storybook/pull/160) -- Add typescript typings support for the core API. See [PR157](https://github.com/storybookjs/react-storybook/pull/157) -- Implement Mantra architecture and some new features including permalinks, full screen support. See: [PR165](https://github.com/storybookjs/react-storybook/pull/165) -- Remove some typo in docs. See: [PR154](https://github.com/storybookjs/react-storybook/pull/154) -- Move UI testing libraries to devDependencies. See: [PR153](https://github.com/storybookjs/react-storybook/pull/153) +- Allow to configure webpack as the user wants. See [PR160](https://github.com/storybookjs/storybook/pull/160) +- Add typescript typings support for the core API. See [PR157](https://github.com/storybookjs/storybook/pull/157) +- Implement Mantra architecture and some new features including permalinks, full screen support. See: [PR165](https://github.com/storybookjs/storybook/pull/165) +- Remove some typo in docs. See: [PR154](https://github.com/storybookjs/storybook/pull/154) +- Move UI testing libraries to devDependencies. See: [PR153](https://github.com/storybookjs/storybook/pull/153) ## v1.19.0 27-April-2016 -- Add airbnb-js-shims to client-side JS. See: [PR147](https://github.com/storybookjs/react-storybook/pull/147) -- Remove self-closing div tag, which is invalid HTML. See: [PR148](https://github.com/storybookjs/react-storybook/pull/148) -- Search for a .babelrc in the storybook config directory first, then the project root. See: [PR149](https://github.com/storybookjs/react-storybook/pull/149) +- Add airbnb-js-shims to client-side JS. See: [PR147](https://github.com/storybookjs/storybook/pull/147) +- Remove self-closing div tag, which is invalid HTML. See: [PR148](https://github.com/storybookjs/storybook/pull/148) +- Search for a .babelrc in the storybook config directory first, then the project root. See: [PR149](https://github.com/storybookjs/storybook/pull/149) ## v1.18.0 26-April-2016 -- Link Storybook menu to the repo. See: [PR137](https://github.com/storybookjs/react-storybook/pull/137) -- Implement keyboard shortcuts and fuzzy search. See: [PR141](https://github.com/storybookjs/react-storybook/pull/141) +- Link Storybook menu to the repo. See: [PR137](https://github.com/storybookjs/storybook/pull/137) +- Implement keyboard shortcuts and fuzzy search. See: [PR141](https://github.com/storybookjs/storybook/pull/141) ## v1.17.2 25-April-2016 -- Fix an error which only occurs on Firefox. See: [PR144](https://github.com/storybookjs/react-storybook/pull/144) +- Fix an error which only occurs on Firefox. See: [PR144](https://github.com/storybookjs/storybook/pull/144) ## v1.17.1 21-April-2016 -- Fix a regression introduce by `v1.17.0`. See: [PR133](https://github.com/storybookjs/react-storybook/pull/133) +- Fix a regression introduce by `v1.17.0`. See: [PR133](https://github.com/storybookjs/storybook/pull/133) ## v1.17.0 21-April-2016 -- Check all the arguments passed to action for events. See: [PR132](https://github.com/storybookjs/react-storybook/pull/132) +- Check all the arguments passed to action for events. See: [PR132](https://github.com/storybookjs/storybook/pull/132) ## v1.16.1 21-April-2016 -- Fix action logs highlighting issue, which comes as a regression of [PR126](https://github.com/storybookjs/react-storybook/pull/126). +- Fix action logs highlighting issue, which comes as a regression of [PR126](https://github.com/storybookjs/storybook/pull/126). ## v1.16.0 20-April-2016 - Prevent re-rendering the preview iframe when there is an action. - - Related issue: [#116](https://github.com/storybookjs/react-storybook/issues/116) - - Related PR: [PR126](https://github.com/storybookjs/react-storybook/pull/126) + - Related issue: [#116](https://github.com/storybookjs/storybook/issues/116) + - Related PR: [PR126](https://github.com/storybookjs/storybook/pull/126) ## v1.15.0 20-April-2016 -- Improve action logger UI and increase max log count to 10. See [PR123](https://github.com/storybookjs/react-storybook/pull/123) +- Improve action logger UI and increase max log count to 10. See [PR123](https://github.com/storybookjs/storybook/pull/123) ## v1.14.0 19-April-2016 -- Add syntax highlights to the logger. See: [PR118](https://github.com/storybookjs/react-storybook/pull/118) +- Add syntax highlights to the logger. See: [PR118](https://github.com/storybookjs/storybook/pull/118) ## v1.13.0 -- Add some UI test cases. See [PR103](https://github.com/storybookjs/react-storybook/pull/103) -- Implement `.addDecorator()` API. See [PR115](https://github.com/storybookjs/react-storybook/pull/115) -- Add code folding support. See [PR111](https://github.com/storybookjs/react-storybook/pull/111) +- Add some UI test cases. See [PR103](https://github.com/storybookjs/storybook/pull/103) +- Implement `.addDecorator()` API. See [PR115](https://github.com/storybookjs/storybook/pull/115) +- Add code folding support. See [PR111](https://github.com/storybookjs/storybook/pull/111) ## v1.12.0 14-April-2016 -- Add support for webpack module preLoaders. See: [PR107](https://github.com/storybookjs/react-storybook/pull/107) +- Add support for webpack module preLoaders. See: [PR107](https://github.com/storybookjs/storybook/pull/107) ## v1.11.0 13-April-2016 -- Add support for React DevTools. See: [PR104](https://github.com/storybookjs/react-storybook/pull/104) +- Add support for React DevTools. See: [PR104](https://github.com/storybookjs/storybook/pull/104) ## v1.10.2 @@ -9973,32 +11271,32 @@ Fix various issues related to static bundling. ## v1.10.1 -- Don't serve index.html in static directory as a site index. See [PR100](https://github.com/storybookjs/react-storybook/pull/100) -- Use cjson for parsing .babelrc files (support comments). See [PR98](https://github.com/storybookjs/react-storybook/pull/98) -- Remove the dist directory before running babel to avoid older code. See [PR101](https://github.com/storybookjs/react-storybook/pull/101) +- Don't serve index.html in static directory as a site index. See [PR100](https://github.com/storybookjs/storybook/pull/100) +- Use cjson for parsing .babelrc files (support comments). See [PR98](https://github.com/storybookjs/storybook/pull/98) +- Remove the dist directory before running babel to avoid older code. See [PR101](https://github.com/storybookjs/storybook/pull/101) ## v1.10.0 -- Add custom head support inside the iframe. See [PR77](https://github.com/storybookjs/react-storybook/pull/77) -- Unmount components before rendering into DOM node. Fix: [#81](https://github.com/storybookjs/react-storybook/issues/81) -- Add a static file builder. See [PR88](https://github.com/storybookjs/react-storybook/pull/88) -- Fix search box's lineHeight to work with all the browsers. See: [PR94](https://github.com/storybookjs/react-storybook/pull/94) -- Add the search box. See: [PR91](https://github.com/storybookjs/react-storybook/pull/91). +- Add custom head support inside the iframe. See [PR77](https://github.com/storybookjs/storybook/pull/77) +- Unmount components before rendering into DOM node. Fix: [#81](https://github.com/storybookjs/storybook/issues/81) +- Add a static file builder. See [PR88](https://github.com/storybookjs/storybook/pull/88) +- Fix search box's lineHeight to work with all the browsers. See: [PR94](https://github.com/storybookjs/storybook/pull/94) +- Add the search box. See: [PR91](https://github.com/storybookjs/storybook/pull/91). ## v1.9.0 Add some minor improvements. -- Avoid deprecated warning in Chrome Canary. See: [PR85](https://github.com/storybookjs/react-storybook/pull/85) -- Fix the React Warning about CSS property. See: [PR84](https://github.com/storybookjs/react-storybook/pull/84) -- Transition on latest logged action. See: [PR80](https://github.com/storybookjs/react-storybook/pull/80) +- Avoid deprecated warning in Chrome Canary. See: [PR85](https://github.com/storybookjs/storybook/pull/85) +- Fix the React Warning about CSS property. See: [PR84](https://github.com/storybookjs/storybook/pull/84) +- Transition on latest logged action. See: [PR80](https://github.com/storybookjs/storybook/pull/80) ## v1.8.0 - Add story linking functionality. - - [Documentation](https://github.com/storybookjs/react-storybook/blob/master/docs/api.md#linking-stories). - - Original feature request: [#50](https://github.com/storybookjs/react-storybook/issues/50) - - Implementation: [PR86](https://github.com/storybookjs/react-storybook/pull/86) + - [Documentation](https://github.com/storybookjs/storybook/blob/master/docs/api.md#linking-stories). + - Original feature request: [#50](https://github.com/storybookjs/storybook/issues/50) + - Implementation: [PR86](https://github.com/storybookjs/storybook/pull/86) ## v1.7.0 @@ -10006,30 +11304,30 @@ Add some minor improvements. ## v1.6.0 -- Make scrollable layout. See: [PR](https://github.com/storybookjs/react-storybook/pull/70) +- Make scrollable layout. See: [PR](https://github.com/storybookjs/storybook/pull/70) - Add npm3 requirement to the `package.json`. - Add `react` and `react-dom` to devDependencies. ## v1.5.0 -- Add support for most of the custom webpack configuration. See [PR64](https://github.com/storybookjs/react-storybook/pull/64) +- Add support for most of the custom webpack configuration. See [PR64](https://github.com/storybookjs/storybook/pull/64) ## v1.4.0 -- Add CLI option to specify the config dir. See [PR52](https://github.com/storybookjs/react-storybook/pull/52). +- Add CLI option to specify the config dir. See [PR52](https://github.com/storybookjs/storybook/pull/52). ## v1.3.0 -- Load the `.babelrc` manually. Fixed: [#41](https://github.com/storybookjs/react-storybook/issues/41) -- Add a better contributing guide. See [CONTRIBUTING.md](https://github.com/storybookjs/react-storybook/blob/master/CONTRIBUTING.md) +- Load the `.babelrc` manually. Fixed: [#41](https://github.com/storybookjs/storybook/issues/41) +- Add a better contributing guide. See [CONTRIBUTING.md](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md) - Add a development utility `npm run dev` which watches "src" directory and run `npm run prepublish`. ## v1.2.0 -- Add a button to clear logs in the ActionLogger. This is requested in [PR21](https://github.com/storybookjs/react-storybook/issues/21). -- Remove navigation list order hijacking. See [commit](https://github.com/storybookjs/react-storybook/commit/166365fd38f51f79e69e028a1c11e2620eddcb99). -- Fix a typo in .gitignore. See [PR31](https://github.com/storybookjs/react-storybook/pull/31). -- Add support for JSX. See [PR18](https://github.com/storybookjs/react-storybook/pull/18). +- Add a button to clear logs in the ActionLogger. This is requested in [PR21](https://github.com/storybookjs/storybook/issues/21). +- Remove navigation list order hijacking. See [commit](https://github.com/storybookjs/storybook/commit/166365fd38f51f79e69e028a1c11e2620eddcb99). +- Fix a typo in .gitignore. See [PR31](https://github.com/storybookjs/storybook/pull/31). +- Add support for JSX. See [PR18](https://github.com/storybookjs/storybook/pull/18). ## v1.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b591ce9d790..4b85ab64f154 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for your interest in improving Storybook! We are a community-driven proje Please review this document to help to streamline the process and save everyone's precious time. -This repo uses yarn workspaces, so you should install `yarn@1.3.2` or higher as a package manager. See [installation guide](https://yarnpkg.com/en/docs/install). +This repo uses yarn workspaces, so you should install `yarn` as the package manager. See [installation guide](https://yarnpkg.com/en/docs/install). ## Issues @@ -30,6 +30,8 @@ cd storybook yarn bootstrap ``` +> NOTE: on windows you may need to run `yarn` before `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: @@ -126,6 +128,18 @@ It can be immensely helpful to get feedback in your editor, if you're using VsCo This should enable auto-fix for all source files, and give linting warnings and errors within your editor. +### 2d. Run Cypress tests + +First make sure the repo is bootstrapped. + +Then run `yarn build-storybooks`, this creates a static website from all examples. + +Then run `yarn serve-storybooks`, this will run the static site on the port cypress expects. + +Then run `yarn add cypress -W --optional`. When this has completed cypress should be installed on your system. If it is already on your system, this step can be skipped. + +Then run `yarn cypress open` if you want to see the tests run in the UI, or `yarn cypress run` to run the tests headless. + ### Reproductions #### In the monorepo @@ -140,6 +154,8 @@ git clone https://github.com/storybookjs/storybook.git cd storybook yarn bootstrap --core +# NOTE: on windows you may need to run `yarn` before `yarn bootstrap`! + # make changes to try and reproduce the problem, such as adding components + stories cd examples/cra-kitchen-sink yarn storybook @@ -207,6 +223,8 @@ Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if **As a PR submitter**, you should reference the issue if there is one, include a short description of what you contributed and, if it is a code change, instructions for how to manually test out the change. This is informally enforced by our [PR template](https://github.com/storybookjs/storybook/blob/master/.github/PULL_REQUEST_TEMPLATE.md). If your PR is reviewed as only needing trivial changes (e.g. small typos etc), and you have commit access then you can merge the PR after making those changes. +> NOTE: Although the latest stable version of storybook corresponds to the `master` branch, nearly all Storybook development happens in the `next` branch. If you submit a PR, branch off `next` and target your PR to `next`. + **As a PR reviewer**, you should read through the changes and comment on any potential problems. If you see something cool, a kind word never hurts either! Additionally, you should follow the testing instructions and manually test the changes. If the instructions are missing, unclear, or overly complex, feel free to request better instructions from the submitter. Unless the PR is tagged with the `do not merge` label, if you approve the review and there is no other required discussion or changes, you should also go ahead and merge the PR. ## Issue Triage @@ -263,19 +281,31 @@ If you run into trouble here, make sure your node, npm, and **_yarn_** are on th 1. `cd ~` (optional) 2. `git clone https://github.com/storybookjs/storybook.git` _bonus_: use your own fork for this step 3. `cd storybook` -4. `yarn` -5. `yarn bootstrap --core` -6. `yarn test --core` -7. `yarn dev` _You must have this running for your changes to show up_ +4. `yarn bootstrap --core` +5. `yarn test --core` +6. `yarn dev` _You must have this running for your changes to show up_ + +> NOTE: on windows you may need to run `yarn` before `yarn bootstrap` (between steps 3 and 4). #### Bootstrapping everything _This method is slow_ 1. `yarn bootstrap --all` -2. Have a beer 🍺 +2. Take a break 🍵 3. `yarn test` (to verify everything worked) +#### Building specific packages + +If you're working on one or a few packages, for every change that you make, you have to rebuild those packages. To make the process easier, there is a CLI command for that: + +- Run `yarn build` to bring you a list of packages to select from. There will be also an option to run in watch mode. +- Run `yarn build ` to build that package specifically. \ + For the package name, use its short version. Example: for `@storybook/addon-docs`, run `yarn build addon-docs`. +- Run `yarn build --all` to build everything. +- Add `--watch` to run automatically in watch more if you are either building a selection of packages by name or building all. + Example: `yarn build core addon-docs --watch` or `yarn build --all --watch`. + ### Working with the kitchen sink apps Within the `examples` folder of the Storybook repo, you will find kitchen sink examples of storybook implementations for the various platforms that storybook supports. @@ -283,9 +313,10 @@ Within the `examples` folder of the Storybook repo, you will find kitchen sink e Not only do these show many of the options and add-ons available, they are also automatically linked to all the development packages. We highly encourage you to use these to develop/test contributions on. #### React and Vue + 1. `cd examples/official-storybook` -2. `yarn storybook` -3. Verify that your local version works +2. `yarn storybook` +3. Verify that your local version works ### Working with your own app diff --git a/MIGRATION.md b/MIGRATION.md index ae7149b87b86..63d44855f091 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,92 +1,652 @@ -# Migration - -- [Migration](#migration) - - [From version 5.2.x to 5.3.x](#from-version-52x-to-53x) - - [To tri-config configuration](#to-triconfig-configuration) - - [Create React App preset](#create-react-app-preset) - - [Description doc block](#description-doc-block) - - [React Native Async Storage](#react-native-async-storage) - - [Deprecate displayName parameter](#deprecate-displayname-parameter) - - [Unified docs preset](#unified-docs-preset) - - [Simplified hierarchy separators](#simplified-hierarchy-separators) - - [From version 5.1.x to 5.2.x](#from-version-51x-to-52x) - - [Source-loader](#source-loader) - - [Default viewports](#default-viewports) - - [Grid toolbar-feature](#grid-toolbar-feature) - - [Docs mode docgen](#docs-mode-docgen) - - [storySort option](#storysort-option) - - [From version 5.1.x to 5.1.10](#from-version-51x-to-5110) - - [babel.config.js support](#babelconfigjs-support) - - [From version 5.0.x to 5.1.x](#from-version-50x-to-51x) - - [React native server](#react-native-server) - - [Angular 7](#angular-7) - - [CoreJS 3](#corejs-3) - - [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) - - [sortStoriesByKind](#sortstoriesbykind) - - [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, decorator renamed](#addon-a11y-uses-parameters-decorator-renamed) - - [New keyboard shortcuts defaults](#new-keyboard-shortcuts-defaults) - - [New URL structure](#new-url-structure) - - [Rename of the `--secure` cli parameter to `--https`](#rename-of-the---secure-cli-parameter-to---https) - - [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) - - [From version 3.4.x to 4.0.x](#from-version-34x-to-40x) - - [React 16.3+](#react-163) - - [Generic addons](#generic-addons) - - [Knobs select ordering](#knobs-select-ordering) - - [Knobs URL parameters](#knobs-url-parameters) - - [Keyboard shortcuts moved](#keyboard-shortcuts-moved) - - [Removed addWithInfo](#removed-addwithinfo) - - [Removed RN packager](#removed-rn-packager) - - [Removed RN addons](#removed-rn-addons) - - [Storyshots Changes](#storyshots-changes) - - [Webpack 4](#webpack-4) - - [Babel 7](#babel-7) - - [Create-react-app](#create-react-app) - - [Upgrade CRA1 to babel 7](#upgrade-cra1-to-babel-7) - - [Migrate CRA1 while keeping babel 6](#migrate-cra1-while-keeping-babel-6) - - [start-storybook opens browser](#start-storybook-opens-browser) - - [CLI Rename](#cli-rename) - - [Addon story parameters](#addon-story-parameters) - - [From version 3.3.x to 3.4.x](#from-version-33x-to-34x) - - [From version 3.2.x to 3.3.x](#from-version-32x-to-33x) - - [`babel-core` is now a peer dependency (#2494)](#babel-core-is-now-a-peer-dependency-2494) - - [Base webpack config now contains vital plugins (#1775)](#base-webpack-config-now-contains-vital-plugins-1775) - - [Refactored Knobs](#refactored-knobs) - - [From version 3.1.x to 3.2.x](#from-version-31x-to-32x) - - [Moved TypeScript addons definitions](#moved-typescript-addons-definitions) - - [Updated Addons API](#updated-addons-api) - - [From version 3.0.x to 3.1.x](#from-version-30x-to-31x) - - [Moved TypeScript definitions](#moved-typescript-definitions) - - [Deprecated head.html](#deprecated-headhtml) - - [From version 2.x.x to 3.x.x](#from-version-2xx-to-3xx) - - [Webpack upgrade](#webpack-upgrade) - - [Packages renaming](#packages-renaming) - - [Deprecated embedded addons](#deprecated-embedded-addons) +

Migration

+ +- [From version 5.3.x to 6.0.x](#from-version-53x-to-60x) + - [Hoisted CSF annotations](#hoisted-csf-annotations) + - [Zero config typescript](#zero-config-typescript) + - [Correct globs in main.js](#correct-globs-in-mainjs) + - [Backgrounds addon has a new api](#backgrounds-addon-has-a-new-api) + - [CRA preset removed](#cra-preset-removed) + - [Args passed as first argument to story](#args-passed-as-first-argument-to-story) + - [6.0 Docs breaking changes](#60-docs-breaking-changes) + - [Remove framework-specific docs presets](#remove-framework-specific-docs-presets) + - [Docs theme separated](#docs-theme-separated) + - [DocsPage slots removed](#docspage-slots-removed) + - [React prop tables with Typescript](#react-prop-tables-with-typescript) + - [React.FC interfaces](#reactfc-interfaces) + - [Imported types](#imported-types) + - [Rolling back](#rolling-back) + - [New addon presets](#new-addon-presets) + - [Removed babel-preset-vue from Vue preset](#removed-babel-preset-vue-from-vue-preset) + - [Removed Deprecated APIs](#removed-deprecated-apis) + - [New setStories event](#new-setstories-event) + - [Client API changes](#client-api-changes) + - [Removed Legacy Story APIs](#removed-legacy-story-apis) + - [Can no longer add decorators/parameters after stories](#can-no-longer-add-decoratorsparameters-after-stories) + - [Changed Parameter Handling](#changed-parameter-handling) + - [Simplified Render Context](#simplified-render-context) + - [Story Store immutable outside of configuration](#story-store-immutable-outside-of-configuration) + - [Improved story source handling](#improved-story-source-handling) + - [6.0 Addon API changes](#60-addon-api-changes) + - [Actions addon uses parameters](#actions-addon-uses-parameters) + - [Removed action decorator APIs](#removed-action-decorator-apis) + - [Removed withA11y decorator](#removed-witha11y-decorator) + - [Essentials addon disables differently](#essentials-addon-disables-differently) + - [6.0 Deprecations](#60-deprecations) + - [Deprecated addon-info, addon-notes](#deprecated-addon-info-addon-notes) + - [Deprecated addon-contexts](#deprecated-addon-contexts) + - [Removed addon-centered](#removed-addon-centered) + - [Deprecated polymer](#deprecated-polymer) +- [From version 5.2.x to 5.3.x](#from-version-52x-to-53x) + - [To main.js configuration](#to-mainjs-configuration) + - [Using main.js](#using-mainjs) + - [Using preview.js](#using-previewjs) + - [Using manager.js](#using-managerjs) + - [Create React App preset](#create-react-app-preset) + - [Description doc block](#description-doc-block) + - [React Native Async Storage](#react-native-async-storage) + - [Deprecate displayName parameter](#deprecate-displayname-parameter) + - [Unified docs preset](#unified-docs-preset) + - [Simplified hierarchy separators](#simplified-hierarchy-separators) + - [Addon StoryShots Puppeteer uses external puppeteer](#addon-storyshots-puppeteer-uses-external-puppeteer) +- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x) + - [Source-loader](#source-loader) + - [Default viewports](#default-viewports) + - [Grid toolbar-feature](#grid-toolbar-feature) + - [Docs mode docgen](#docs-mode-docgen) + - [storySort option](#storysort-option) +- [From version 5.1.x to 5.1.10](#from-version-51x-to-5110) + - [babel.config.js support](#babelconfigjs-support) +- [From version 5.0.x to 5.1.x](#from-version-50x-to-51x) + - [React native server](#react-native-server) + - [Angular 7](#angular-7) + - [CoreJS 3](#corejs-3) +- [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) + - [sortStoriesByKind](#sortstoriesbykind) + - [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, decorator renamed](#addon-a11y-uses-parameters-decorator-renamed) + - [Addon centered decorator deprecated](#addon-centered-decorator-deprecated) + - [New keyboard shortcuts defaults](#new-keyboard-shortcuts-defaults) + - [New URL structure](#new-url-structure) + - [Rename of the `--secure` cli parameter to `--https`](#rename-of-the---secure-cli-parameter-to---https) + - [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) +- [From version 3.4.x to 4.0.x](#from-version-34x-to-40x) + - [React 16.3+](#react-163) + - [Generic addons](#generic-addons) + - [Knobs select ordering](#knobs-select-ordering) + - [Knobs URL parameters](#knobs-url-parameters) + - [Keyboard shortcuts moved](#keyboard-shortcuts-moved) + - [Removed addWithInfo](#removed-addwithinfo) + - [Removed RN packager](#removed-rn-packager) + - [Removed RN addons](#removed-rn-addons) + - [Storyshots Changes](#storyshots-changes) + - [Webpack 4](#webpack-4) + - [Babel 7](#babel-7) + - [Create-react-app](#create-react-app) + - [Upgrade CRA1 to babel 7](#upgrade-cra1-to-babel-7) + - [Migrate CRA1 while keeping babel 6](#migrate-cra1-while-keeping-babel-6) + - [start-storybook opens browser](#start-storybook-opens-browser) + - [CLI Rename](#cli-rename) + - [Addon story parameters](#addon-story-parameters) +- [From version 3.3.x to 3.4.x](#from-version-33x-to-34x) +- [From version 3.2.x to 3.3.x](#from-version-32x-to-33x) + - [`babel-core` is now a peer dependency (#2494)](#babel-core-is-now-a-peer-dependency-2494) + - [Base webpack config now contains vital plugins (#1775)](#base-webpack-config-now-contains-vital-plugins-1775) + - [Refactored Knobs](#refactored-knobs) +- [From version 3.1.x to 3.2.x](#from-version-31x-to-32x) + - [Moved TypeScript addons definitions](#moved-typescript-addons-definitions) + - [Updated Addons API](#updated-addons-api) +- [From version 3.0.x to 3.1.x](#from-version-30x-to-31x) + - [Moved TypeScript definitions](#moved-typescript-definitions) + - [Deprecated head.html](#deprecated-headhtml) +- [From version 2.x.x to 3.x.x](#from-version-2xx-to-3xx) + - [Webpack upgrade](#webpack-upgrade) + - [Packages renaming](#packages-renaming) + - [Deprecated embedded addons](#deprecated-embedded-addons) + +## From version 5.3.x to 6.0.x + +### Hoisted CSF annotations + +Storybook 6 introduces hoisted CSF annotations and deprecates the `StoryFn.story` object-style annotation. + +In 5.x CSF, you would annotate a story like this: +```js +export const Basic = () => -); +export const accessible = () => ; export const inaccessible = () => ( - + ); ``` +If you wish to selectively disable `a11y` checks for a subset of stories, you can control this with story parameters: + +```js +export const MyNonCheckedStory = () => ; +MyNonCheckedStory.parameters = { + a11y: { disable: true }, +}; +``` + ## Parameters For more customizability use parameters to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). @@ -56,11 +54,8 @@ You can override these options [at story level too](https://storybook.js.org/doc import React from 'react'; import { storiesOf, addDecorator, addParameters } from '@storybook/react'; -import { withA11y } from '@storybook/addon-a11y'; - export default { title: 'button', - decorators: [withA11y], parameters: { a11y: { // optional selector which element to inspect @@ -69,27 +64,23 @@ export default { config: {}, // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) options: {}, + // optional flag to prevent the automatic check + manual: true, }, }, }; -export const accessible = () => ( - -); +export const accessible = () => ; export const inaccessible = () => ( - + ); ``` ## Roadmap -* Make UI accessible -* Show in story where violations are. -* Add more example tests -* Add tests -* Make CI integration possible +- Make UI accessible +- Show in story where violations are. +- Add more example tests +- Add tests +- Make CI integration possible diff --git a/addons/a11y/package.json b/addons/a11y/package.json index 772ba9dfdadf..0bbf39e48dcc 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "a11y addon for storybook", "keywords": [ "a11y", @@ -20,41 +20,55 @@ "directory": "addons/a11y" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", - "axe-core": "^3.3.2", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/channels": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", + "axe-core": "^3.5.2", "core-js": "^3.0.1", "global": "^4.3.2", - "memoizerific": "^1.11.3", - "react": "^16.8.3", - "react-redux": "^7.0.2", + "lodash": "^4.17.15", "react-sizeme": "^2.5.2", - "redux": "^4.0.1", - "ts-dedent": "^1.1.0", + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1", "util-deprecate": "^1.0.2" }, "devDependencies": { - "@types/react-redux": "^7.0.6" + "@testing-library/react": "^10.0.4", + "@types/webpack-env": "^1.15.2", + "react": "^16.13.1", + "react-dom": "^16.13.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/a11y/preset.js b/addons/a11y/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/a11y/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/a11y/src/a11yHighlight.ts b/addons/a11y/src/a11yHighlight.ts new file mode 100644 index 000000000000..155e58f8919d --- /dev/null +++ b/addons/a11y/src/a11yHighlight.ts @@ -0,0 +1,46 @@ +import { document } from 'global'; +import addons from '@storybook/addons'; +import { STORY_CHANGED } from '@storybook/core-events'; +import { EVENTS, HIGHLIGHT_STYLE_ID } from './constants'; + +import { higlightStyle } from './highlight'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} + +interface HighlightInfo { + /** html selector of the element */ + elements: string[]; + color: string; +} + +const channel = addons.getChannel(); + +const highlight = (infos: HighlightInfo) => { + const id = HIGHLIGHT_STYLE_ID; + resetHighlight(); + + const sheet = document.createElement('style'); + sheet.setAttribute('id', id); + sheet.innerHTML = infos.elements + .map( + (target) => + `${target}{ + ${higlightStyle(infos.color)} + }` + ) + .join(' '); + document.head.appendChild(sheet); +}; + +const resetHighlight = () => { + const id = HIGHLIGHT_STYLE_ID; + const sheetToBeRemoved = document.getElementById(id); + if (sheetToBeRemoved) { + sheetToBeRemoved.parentNode.removeChild(sheetToBeRemoved); + } +}; + +channel.on(STORY_CHANGED, resetHighlight); +channel.on(EVENTS.HIGHLIGHT, highlight); diff --git a/addons/a11y/src/a11yRunner.test.ts b/addons/a11y/src/a11yRunner.test.ts new file mode 100644 index 000000000000..2efb58b2f245 --- /dev/null +++ b/addons/a11y/src/a11yRunner.test.ts @@ -0,0 +1,25 @@ +import addons from '@storybook/addons'; +import { EVENTS } from './constants'; + +jest.mock('@storybook/addons'); +const mockedAddons = addons as jest.Mocked; + +describe('a11yRunner', () => { + let mockChannel: { on: jest.Mock; emit?: jest.Mock }; + + beforeEach(() => { + mockedAddons.getChannel.mockReset(); + + mockChannel = { on: jest.fn(), emit: jest.fn() }; + mockedAddons.getChannel.mockReturnValue(mockChannel as any); + }); + + it('should listen to events', () => { + // eslint-disable-next-line global-require + require('./a11yRunner'); + + expect(mockedAddons.getChannel).toHaveBeenCalled(); + expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.REQUEST, expect.any(Function)); + expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.MANUAL, expect.any(Function)); + }); +}); diff --git a/addons/a11y/src/a11yRunner.ts b/addons/a11y/src/a11yRunner.ts new file mode 100644 index 000000000000..ce0c900f1fde --- /dev/null +++ b/addons/a11y/src/a11yRunner.ts @@ -0,0 +1,58 @@ +import { document, window } from 'global'; +import axe from 'axe-core'; +import addons from '@storybook/addons'; +import { EVENTS } from './constants'; +import { Setup } from './params'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} + +const channel = addons.getChannel(); +let active = false; + +const getElement = () => { + const storyRoot = document.getElementById('story-root'); + return storyRoot ? storyRoot.children : document.getElementById('root'); +}; + +const run = async (storyId: string) => { + try { + const input = getParams(storyId); + + if (!active) { + active = true; + channel.emit(EVENTS.RUNNING); + + const { element = getElement(), config, options } = input; + axe.reset(); + if (config) { + axe.configure(config); + } + + const result = await axe.run(element, options); + channel.emit(EVENTS.RESULT, result); + } + } catch (error) { + channel.emit(EVENTS.ERROR, error); + } finally { + active = false; + } +}; + +/** Returns story parameters or default ones. */ +const getParams = (storyId: string): Setup => { + // eslint-disable-next-line no-underscore-dangle + const { parameters } = window.__STORYBOOK_STORY_STORE__._stories[storyId] || {}; + return ( + parameters.a11y || { + config: {}, + options: { + restoreScroll: true, + }, + } + ); +}; + +channel.on(EVENTS.REQUEST, run); +channel.on(EVENTS.MANUAL, run); diff --git a/addons/a11y/src/components/A11YPanel.test.js b/addons/a11y/src/components/A11YPanel.test.js deleted file mode 100644 index 2f84b31afd39..000000000000 --- a/addons/a11y/src/components/A11YPanel.test.js +++ /dev/null @@ -1,224 +0,0 @@ -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 ( - - - - ); -} - -describe('A11YPanel', () => { - it('should register STORY_RENDERED, RESULT and ERROR updater on mount', () => { - // given - const api = createApi(); - expect(api.on).not.toHaveBeenCalled(); - - // when - mount(); - - // then - expect(api.on.mock.calls.length).toBe(3); - expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED); - expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT); - expect(api.on.mock.calls[2][0]).toBe(EVENTS.ERROR); - }); - - it('should request a run on tab activation', () => { - // given - const api = createApi(); - - const wrapper = mount(); - 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, RESULT and ERROR updater on unmount', () => { - // given - const api = createApi(); - const wrapper = mount(); - expect(api.off).not.toHaveBeenCalled(); - - // when - wrapper.unmount(); - - // then - expect(api.off.mock.calls.length).toBe(3); - expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED); - expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT); - expect(api.off.mock.calls[2][0]).toBe(EVENTS.ERROR); - }); - - it('should update run result', () => { - // given - const api = createApi(); - const wrapper = mount(); - 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(); - 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(); - 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(); - 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(); - 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(); - - // then - expect(wrapper.find('ScrollArea').length).toBe(0); - expect(wrapper.find('ActionBar').length).toBe(0); - }); -}); diff --git a/addons/a11y/src/components/A11YPanel.test.tsx b/addons/a11y/src/components/A11YPanel.test.tsx new file mode 100644 index 000000000000..b7e77b1e51a3 --- /dev/null +++ b/addons/a11y/src/components/A11YPanel.test.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import { render, waitFor, fireEvent, act } from '@testing-library/react'; + +import { ThemeProvider, themes, convert } from '@storybook/theming'; +import * as api from '@storybook/api'; + +import { A11YPanel } from './A11YPanel'; +import { EVENTS } from '../constants'; + +jest.mock('@storybook/api'); +const mockedApi = api as jest.Mocked; + +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() { + return ( + + + + ); +} + +describe('A11YPanel', () => { + beforeEach(() => { + mockedApi.useChannel.mockReset(); + mockedApi.useParameter.mockReset(); + mockedApi.useStorybookState.mockReset(); + mockedApi.useAddonState.mockReset(); + + mockedApi.useChannel.mockReturnValue(jest.fn()); + mockedApi.useParameter.mockReturnValue({ manual: false }); + const state: Partial = { storyId: 'jest' }; + // Lazy to mock entire state + mockedApi.useStorybookState.mockReturnValue(state as any); + mockedApi.useAddonState.mockImplementation(React.useState); + }); + + it('should render', () => { + const { container } = render(); + expect(container.firstChild).toBeTruthy(); + }); + + it('should register event listener on mount', () => { + render(); + expect(mockedApi.useChannel).toHaveBeenCalledWith( + expect.objectContaining({ + [EVENTS.RESULT]: expect.any(Function), + [EVENTS.ERROR]: expect.any(Function), + }) + ); + }); + + it('should handle "initial" status', () => { + const { getByText } = render(); + expect(getByText(/Initializing/)).toBeTruthy(); + }); + + it('should handle "manual" status', async () => { + mockedApi.useParameter.mockReturnValue({ manual: true }); + const { getByText } = render(); + await waitFor(() => { + expect(getByText(/Manually run the accessibility scan/)).toBeTruthy(); + }); + }); + + describe('running', () => { + it('should handle "running" status', async () => { + const emit = jest.fn(); + mockedApi.useChannel.mockReturnValue(emit); + mockedApi.useParameter.mockReturnValue({ manual: true }); + const { getByRole, getByText } = render(); + await waitFor(() => { + const button = getByRole('button', { name: 'Run test' }); + fireEvent.click(button); + }); + await waitFor(() => { + expect(getByText(/Please wait while the accessibility scan is running/)).toBeTruthy(); + expect(emit).toHaveBeenCalledWith(EVENTS.MANUAL, 'jest'); + }); + }); + + it('should set running status on event', async () => { + const { getByText } = render(); + const useChannelArgs = mockedApi.useChannel.mock.calls[0][0]; + act(() => useChannelArgs[EVENTS.RUNNING]()); + await waitFor(() => { + expect(getByText(/Please wait while the accessibility scan is running/)).toBeTruthy(); + }); + }); + }); + + it('should handle "ran" status', async () => { + const { getByText } = render(); + const useChannelArgs = mockedApi.useChannel.mock.calls[0][0]; + act(() => useChannelArgs[EVENTS.RESULT](axeResult)); + await waitFor(() => { + expect(getByText(/Tests completed/)).toBeTruthy(); + expect(getByText(/Violations/)).toBeTruthy(); + expect(getByText(/Passes/)).toBeTruthy(); + expect(getByText(/Incomplete/)).toBeTruthy(); + }); + }); +}); diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index a2d7a7fc73d8..fc568cfc4e30 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -1,18 +1,17 @@ -import React, { Component, Fragment } from 'react'; +import React, { useCallback, useMemo } 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 { AxeResults } from 'axe-core'; +import { useChannel, useParameter, useStorybookState, useAddonState } from '@storybook/api'; import { Report } from './Report'; import { Tabs } from './Tabs'; -import { EVENTS } from '../constants'; -import store, { clearElements } from '../redux-config'; +import { useA11yContext } from './A11yContext'; +import { EVENTS, ADDON_ID } from '../constants'; +import { A11yParameters } from '../params'; export enum RuleType { VIOLATION, @@ -20,19 +19,15 @@ export enum RuleType { INCOMPLETION, } -const Icon = styled(Icons)( - { - height: 12, - width: 12, - marginRight: 4, - }, - ({ status, theme }: any) => - status === 'running' - ? { - animation: `${theme.animation.rotate360} 1s linear infinite;`, - } - : {} -); +const Icon = styled(Icons)({ + height: 12, + width: 12, + marginRight: 4, +}); + +const RotatingIcon = styled(Icon)<{}>(({ theme }) => ({ + animation: `${theme.animation.rotate360} 1s linear infinite;`, +})); const Passes = styled.span<{}>(({ theme }) => ({ color: theme.color.positive, @@ -46,210 +41,145 @@ const Incomplete = styled.span<{}>(({ theme }) => ({ color: theme.color.warning, })); -const centeredStyle = { +const Centered = styled.span<{}>({ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', -}; - -const Loader = styled(({ className }) => ( -
- Please wait while the accessibility scan is running - ... -
-))(centeredStyle); -Loader.displayName = 'Loader'; - -interface A11YPanelNormalState { - status: 'ready' | 'ran' | 'running'; - passes: Result[]; - violations: Result[]; - incomplete: Result[]; -} - -interface A11YPanelErrorState { - status: 'error'; - error: unknown; -} - -type A11YPanelState = A11YPanelNormalState | A11YPanelErrorState; - -interface A11YPanelProps { - active: boolean; - api: API; -} - -export class A11YPanel extends Component { - state: A11YPanelState = { - status: 'ready', - passes: [], - violations: [], - incomplete: [], +}); + +type Status = 'initial' | 'manual' | 'running' | 'error' | 'ran' | 'ready'; + +export const A11YPanel: React.FC = () => { + const [status, setStatus] = useAddonState(ADDON_ID, 'initial'); + const [error, setError] = React.useState(undefined); + const { setResults, results } = useA11yContext(); + const { storyId } = useStorybookState(); + const { manual } = useParameter>('a11y', { + manual: false, + }); + + React.useEffect(() => { + setStatus(manual ? 'manual' : 'initial'); + }, [manual]); + + const handleResult = (axeResults: AxeResults) => { + setStatus('ran'); + setResults(axeResults); + + setTimeout(() => { + if (status === 'ran') { + setStatus('ready'); + } + }, 900); }; - componentDidMount() { - const { api } = this.props; - - api.on(STORY_RENDERED, this.request); - api.on(EVENTS.RESULT, this.onUpdate); - api.on(EVENTS.ERROR, this.onError); - } - - 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); - api.off(EVENTS.ERROR, this.onError); - } - - onUpdate = ({ passes, violations, incomplete }: AxeResults) => { - this.setState( + const handleRun = useCallback(() => { + setStatus('running'); + }, []); + + const handleError = useCallback((err: unknown) => { + setStatus('error'); + setError(err); + }, []); + + const emit = useChannel({ + [EVENTS.RUNNING]: handleRun, + [EVENTS.RESULT]: handleResult, + [EVENTS.ERROR]: handleError, + }); + + const handleManual = useCallback(() => { + setStatus('running'); + emit(EVENTS.MANUAL, storyId); + }, [storyId]); + + const manualActionItems = useMemo(() => [{ title: 'Run test', onClick: handleManual }], [ + handleManual, + ]); + const readyActionItems = useMemo( + () => [ { - status: 'ran', - passes, - violations, - incomplete, + title: + status === 'ready' ? ( + 'Rerun tests' + ) : ( + <> + Tests completed + + ), + onClick: handleManual, }, - () => { - setTimeout(() => { - const { status } = this.state; - if (status === 'ran') { - this.setState({ - status: 'ready', - }); - } - }, 900); - } - ); - }; - - onError = (error: unknown) => { - this.setState({ - status: 'error', - error, - }); - }; - - 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 { active } = this.props; - if (!active) return null; - - // eslint-disable-next-line react/destructuring-assignment - if (this.state.status === 'error') { - const { error } = this.state; - return ( -
+ ], + [status, handleManual] + ); + const tabs = useMemo(() => { + const { passes, incomplete, violations } = results; + return [ + { + label: {violations.length} Violations, + panel: ( + + ), + items: violations, + type: RuleType.VIOLATION, + }, + { + label: {passes.length} Passes, + panel: ( + + ), + items: passes, + type: RuleType.PASS, + }, + { + label: {incomplete.length} Incomplete, + panel: ( + + ), + items: incomplete, + type: RuleType.INCOMPLETION, + }, + ]; + }, [results]); + return ( + <> + {status === 'initial' && Initializing...} + {status === 'manual' && ( + <> + Manually run the accessibility scan. + + + )} + {status === 'running' && ( + + Please wait while the accessibility scan is running + ... + + )} + {(status === 'ready' || status === 'ran') && ( + <> + + + + + + )} + {status === 'error' && ( + The accessibility scan encountered an error.
- {error} -
- ); - } - - const { passes, violations, incomplete, status } = this.state; - - let actionTitle; - if (status === 'ready') { - actionTitle = 'Rerun tests'; - } else if (status === 'running') { - actionTitle = ( - - Running test - - ); - } else if (status === 'ran') { - actionTitle = ( - - Tests completed - - ); - } - - return ( - - - {status === 'running' ? ( - - ) : ( - - {violations.length} Violations, - panel: ( - - ), - items: violations, - type: RuleType.VIOLATION, - }, - { - label: {passes.length} Passes, - panel: ( - - ), - items: passes, - type: RuleType.PASS, - }, - { - label: {incomplete.length} Incomplete, - panel: ( - - ), - items: incomplete, - type: RuleType.INCOMPLETION, - }, - ]} - /> - - )} - - - - ); - } -} + {typeof error === 'string' ? error : JSON.stringify(error)} + + )} + + ); +}; diff --git a/addons/a11y/src/components/A11yContext.test.tsx b/addons/a11y/src/components/A11yContext.test.tsx new file mode 100644 index 000000000000..5d4e7003f149 --- /dev/null +++ b/addons/a11y/src/components/A11yContext.test.tsx @@ -0,0 +1,133 @@ +import * as React from 'react'; +import { AxeResults } from 'axe-core'; +import { render, act } from '@testing-library/react'; +import * as api from '@storybook/api'; +import { STORY_CHANGED } from '@storybook/core-events'; + +import { A11yContextProvider, useA11yContext } from './A11yContext'; +import { EVENTS } from '../constants'; + +jest.mock('@storybook/api'); +const mockedApi = api as jest.Mocked; + +const storyId = 'jest'; +const axeResult: Partial = { + incomplete: [ + { + id: 'color-contrast', + impact: 'serious', + tags: [], + 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: undefined, + tags: [], + 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: [], + 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: [], + }, + ], +}; + +describe('A11YPanel', () => { + beforeEach(() => { + mockedApi.useChannel.mockReset(); + mockedApi.useStorybookState.mockReset(); + + mockedApi.useChannel.mockReturnValue(jest.fn()); + const state: Partial = { storyId }; + // Lazy to mock entire state + mockedApi.useStorybookState.mockReturnValue(state as any); + }); + + it('should render children', () => { + const { getByTestId } = render( + +
+ + ); + expect(getByTestId('child')).toBeTruthy(); + }); + + it('should not render when inactive', () => { + const emit = jest.fn(); + mockedApi.useChannel.mockReturnValue(emit); + const { queryByTestId } = render( + +
+ + ); + expect(queryByTestId('child')).toBeFalsy(); + expect(emit).not.toHaveBeenCalledWith(EVENTS.REQUEST); + }); + + it('should emit request when moving from inactive to active', () => { + const emit = jest.fn(); + mockedApi.useChannel.mockReturnValue(emit); + const { rerender } = render(); + rerender(); + expect(emit).toHaveBeenLastCalledWith(EVENTS.REQUEST, storyId); + }); + + it('should emit highlight with no values when inactive', () => { + const emit = jest.fn(); + mockedApi.useChannel.mockReturnValue(emit); + const { rerender } = render(); + rerender(); + expect(emit).toHaveBeenLastCalledWith( + EVENTS.HIGHLIGHT, + expect.objectContaining({ + color: expect.any(String), + elements: [], + }) + ); + }); + + it('should emit highlight with no values when story changed', () => { + const Component = () => { + const { results, setResults } = useA11yContext(); + // As any because of unit tests... + React.useEffect(() => setResults(axeResult as any), []); + return ( + <> + {!!results.passes.length &&
} + {!!results.incomplete.length &&
} + {!!results.violations.length &&
} + + ); + }; + const { queryByTestId } = render( + + + + ); + expect(queryByTestId('anyPassesResults')).toBeTruthy(); + expect(queryByTestId('anyIncompleteResults')).toBeTruthy(); + expect(queryByTestId('anyViolationsResults')).toBeTruthy(); + const useChannelArgs = mockedApi.useChannel.mock.calls[0][0]; + act(() => useChannelArgs[STORY_CHANGED]()); + expect(queryByTestId('anyPassesResults')).toBeFalsy(); + expect(queryByTestId('anyIncompleteResults')).toBeFalsy(); + expect(queryByTestId('anyViolationsResults')).toBeFalsy(); + }); +}); diff --git a/addons/a11y/src/components/A11yContext.tsx b/addons/a11y/src/components/A11yContext.tsx new file mode 100644 index 000000000000..458fa14f11b5 --- /dev/null +++ b/addons/a11y/src/components/A11yContext.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { themes, convert } from '@storybook/theming'; +import { Result } from 'axe-core'; +import { useChannel, useStorybookState } from '@storybook/api'; +import { STORY_CHANGED, STORY_RENDERED } from '@storybook/core-events'; +import { EVENTS } from '../constants'; + +interface Results { + passes: Result[]; + violations: Result[]; + incomplete: Result[]; +} + +interface A11yContextStore { + results: Results; + setResults: (results: Results) => void; + highlighted: string[]; + toggleHighlight: (target: string[], highlight: boolean) => void; + clearHighlights: () => void; + tab: number; + setTab: (index: number) => void; +} + +const colorsByType = [ + convert(themes.normal).color.negative, // VIOLATION, + convert(themes.normal).color.positive, // PASS, + convert(themes.normal).color.warning, // INCOMPLETION, +]; + +export const A11yContext = React.createContext({ + results: { + passes: [], + incomplete: [], + violations: [], + }, + setResults: () => {}, + highlighted: [], + toggleHighlight: () => {}, + clearHighlights: () => {}, + tab: 0, + setTab: () => {}, +}); + +interface A11yContextProviderProps { + active: boolean; +} + +const defaultResult = { + passes: [], + incomplete: [], + violations: [], +}; + +export const A11yContextProvider: React.FC = ({ active, ...props }) => { + const [results, setResults] = React.useState(defaultResult); + const [tab, setTab] = React.useState(0); + const [highlighted, setHighlighted] = React.useState([]); + const { storyId } = useStorybookState(); + + const handleToggleHighlight = React.useCallback((target: string[], highlight: boolean) => { + setHighlighted((prevHighlighted) => + highlight + ? [...prevHighlighted, ...target] + : prevHighlighted.filter((t) => !target.includes(t)) + ); + }, []); + const handleRun = React.useCallback(() => { + emit(EVENTS.REQUEST, storyId); + }, [storyId]); + const handleClearHighlights = React.useCallback(() => setHighlighted([]), []); + const handleSetTab = React.useCallback((index: number) => { + handleClearHighlights(); + setTab(index); + }, []); + + const handleReset = React.useCallback(() => { + setTab(0); + setResults(defaultResult); + // Highlights is cleared by a11yHighlights.ts + }, []); + + const emit = useChannel({ + [STORY_RENDERED]: handleRun, + [STORY_CHANGED]: handleReset, + }); + + React.useEffect(() => { + emit(EVENTS.HIGHLIGHT, { elements: highlighted, color: colorsByType[tab] }); + }, [highlighted, tab]); + + React.useEffect(() => { + if (active) { + handleRun(); + } else { + handleClearHighlights(); + } + }, [active, handleClearHighlights, emit, storyId]); + + if (!active) return null; + + return ( + + ); +}; + +export const useA11yContext = () => React.useContext(A11yContext); diff --git a/addons/a11y/src/components/ColorBlindness.tsx b/addons/a11y/src/components/ColorBlindness.tsx index 35edc2bd15c4..ae11f4860fe0 100644 --- a/addons/a11y/src/components/ColorBlindness.tsx +++ b/addons/a11y/src/components/ColorBlindness.tsx @@ -1,15 +1,27 @@ -import { document } from 'global'; import React, { FunctionComponent, ReactNode, useState } from 'react'; -import memoize from 'memoizerific'; -import { styled } from '@storybook/theming'; - -import { logger } from '@storybook/client-logger'; +import { Global, styled } from '@storybook/theming'; import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; -const getIframe = memoize(1)(() => document.getElementById('storybook-preview-iframe')); +import { Filters } from './ColorFilters'; + +const iframeId = 'storybook-preview-iframe'; -const getFilter = (filter: string | null) => { - if (filter === null) { +const baseList = [ + 'protanopia', + 'protanomaly', + 'deuteranopia', + 'deuteranomaly', + 'tritanopia', + 'tritanomaly', + 'achromatopsia', + 'achromatomaly', + 'mono', +] as const; + +type Filter = typeof baseList[number] | null; + +const getFilter = (filter: Filter) => { + if (!filter) { return 'none'; } if (filter === 'mono') { @@ -18,7 +30,15 @@ const getFilter = (filter: string | null) => { return `url('#${filter}')`; }; -const ColorIcon = styled.span( +const Hidden = styled.div(() => ({ + '&, & svg': { + position: 'absolute', + width: 0, + height: 0, + }, +})); + +const ColorIcon = styled.span<{ filter: Filter }>( { background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)', borderRadius: '1rem', @@ -26,7 +46,7 @@ const ColorIcon = styled.span( height: '1rem', width: '1rem', }, - ({ filter }: { filter: string | null }) => ({ + ({ filter }) => ({ filter: getFilter(filter), }), ({ theme }) => ({ @@ -34,18 +54,6 @@ const ColorIcon = styled.span( }) ); -const baseList = [ - 'protanopia', - 'protanomaly', - 'deuteranopia', - 'deuteranomaly', - 'tritanopia', - 'tritanomaly', - 'achromatopsia', - 'achromatomaly', - 'mono', -]; - export interface Link { id: string; title: ReactNode; @@ -54,7 +62,7 @@ export interface Link { onClick: () => void; } -const getColorList = (active: string | null, set: (i: string | null) => void): Link[] => [ +const getColorList = (active: Filter, set: (i: Filter) => void): Link[] => [ ...(active !== null ? [ { @@ -68,7 +76,7 @@ const getColorList = (active: string | null, set: (i: string | null) => void): L }, ] : []), - ...baseList.map(i => ({ + ...baseList.map((i) => ({ id: i, title: i.charAt(0).toUpperCase() + i.slice(1), onClick: () => { @@ -80,36 +88,39 @@ const getColorList = (active: string | null, set: (i: string | null) => void): L ]; export const ColorBlindness: FunctionComponent = () => { - const [active, setActiveState] = useState(null); - - const setActive = (activeState: string | null): void => { - const iframe = getIframe(); - - if (iframe) { - iframe.style.filter = getFilter(activeState); - setActiveState(activeState); - } else { - logger.error('Cannot find Storybook iframe'); - } - }; + const [filter, setFilter] = useState(null); return ( - { - const colorList = getColorList(active, i => { - setActive(i); - onHide(); - }); - return ; - }} - closeOnClick - onDoubleClick={() => setActive(null)} - > - - - - + <> + {filter && ( + + )} + { + const colorList = getColorList(filter, (i) => { + setFilter(i); + onHide(); + }); + return ; + }} + closeOnClick + onDoubleClick={() => setFilter(null)} + > + + + + + + + + ); }; diff --git a/addons/a11y/src/components/ColorFilters.tsx b/addons/a11y/src/components/ColorFilters.tsx new file mode 100644 index 000000000000..04c34088b7fd --- /dev/null +++ b/addons/a11y/src/components/ColorFilters.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; + +export const Filters: React.FC> = (props) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/addons/a11y/src/components/Report/Elements.tsx b/addons/a11y/src/components/Report/Elements.tsx index d2fc529b8d05..50e0d0779caf 100644 --- a/addons/a11y/src/components/Report/Elements.tsx +++ b/addons/a11y/src/components/Report/Elements.tsx @@ -36,19 +36,13 @@ const Element: FunctionComponent = ({ element, type }) => { const { any, all, none } = element; const rules = [...any, ...all, ...none]; const highlightToggleId = `${type}-${element.target[0]}`; - const highlightLabel = `Highlight`; return ( {element.target[0]} - + diff --git a/addons/a11y/src/components/Report/HighlightToggle.test.js b/addons/a11y/src/components/Report/HighlightToggle.test.js deleted file mode 100644 index 9659445c01bb..000000000000 --- a/addons/a11y/src/components/Report/HighlightToggle.test.js +++ /dev/null @@ -1,40 +0,0 @@ -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 ( - - - - ); -} - -describe('HighlightToggle component', () => { - test('should render', () => { - // given - const wrapper = mount( - - - - ); - - // then - expect(wrapper.exists()).toBe(true); - }); - - test('should match snapshot', () => { - // given - const wrapper = mount( - - - - ); - - // then - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/a11y/src/components/Report/HighlightToggle.test.tsx b/addons/a11y/src/components/Report/HighlightToggle.test.tsx new file mode 100644 index 000000000000..c9f3771d2ae3 --- /dev/null +++ b/addons/a11y/src/components/Report/HighlightToggle.test.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { NodeResult } from 'axe-core'; +import HighlightToggle from './HighlightToggle'; +import { A11yContext } from '../A11yContext'; + +const nodeResult = (target: string): NodeResult => ({ + html: '', + target: [target], + any: [], + all: [], + none: [], +}); + +const defaultProviderValue = { + results: { + passes: [], + incomplete: [], + violations: [], + }, + setResults: jest.fn(), + highlighted: [], + toggleHighlight: jest.fn(), + clearHighlights: jest.fn(), + tab: 0, + setTab: jest.fn(), +}; + +describe('', () => { + it('should render', () => { + const { container } = render(); + expect(container.firstChild).toBeTruthy(); + }); + + it('should be checked when all targets are highlighted', () => { + const { getByRole } = render( + + + + ); + const checkbox = getByRole('checkbox') as HTMLInputElement; + expect(checkbox.checked).toBeTruthy(); + }); + + it('should be mixed when some targets are highlighted', () => { + const { getByRole } = render( + + + + ); + const checkbox = getByRole('checkbox') as HTMLInputElement; + expect(checkbox.indeterminate).toBeTruthy(); + }); + + describe('toggleHighlight', () => { + it.each` + highlighted | elementsToHighlight | expected + ${[]} | ${['#root']} | ${true} + ${['#root']} | ${['#root']} | ${false} + ${['#root']} | ${['#root', '#root1']} | ${true} + `( + 'should be triggerd with $expected when highlighted is $highlighted and elementsToHighlight is $elementsToHighlight', + ({ highlighted, elementsToHighlight, expected }) => { + const { getByRole } = render( + + + + ); + const checkbox = getByRole('checkbox') as HTMLInputElement; + fireEvent.click(checkbox); + expect(defaultProviderValue.toggleHighlight).toHaveBeenCalledWith( + elementsToHighlight, + expected + ); + } + ); + }); +}); diff --git a/addons/a11y/src/components/Report/HighlightToggle.tsx b/addons/a11y/src/components/Report/HighlightToggle.tsx index 2aa554641e39..7697e0bce891 100644 --- a/addons/a11y/src/components/Report/HighlightToggle.tsx +++ b/addons/a11y/src/components/Report/HighlightToggle.tsx @@ -1,28 +1,12 @@ -import { document } from 'global'; -import React, { Component, createRef } from 'react'; -import { connect } from 'react-redux'; -import { styled, themes, convert } from '@storybook/theming'; -import memoize from 'memoizerific'; +import React from 'react'; +import { styled } from '@storybook/theming'; 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; -} +import { useA11yContext } from '../A11yContext'; interface ToggleProps { elementsToHighlight: NodeResult[]; - type: RuleType; - addElement?: (data: any) => void; - highlightedElementsMap?: Map; - isToggledOn?: boolean; toggleId?: string; - indeterminate?: boolean; } enum CheckBoxStates { @@ -35,38 +19,13 @@ const Checkbox = styled.input<{ disabled: boolean }>(({ 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 + highlighted: string[] ): CheckBoxStates { - const highlightedCount = elementsToHighlight.filter(item => { - const targetElement = getElementBySelectorPath(item.target[0]); - return ( - highlightedElementsMap.has(targetElement) && - highlightedElementsMap.get(targetElement).isHighlighted - ); - }).length; + const highlightedCount = elementsToHighlight.filter((item) => + highlighted.includes(item.target[0]) + ).length; // eslint-disable-next-line no-nested-ternary return highlightedCount === 0 @@ -76,113 +35,39 @@ function areAllRequiredElementsHighlighted( : 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 +const HighlightToggle: React.FC = ({ toggleId, elementsToHighlight = [] }) => { + const { toggleHighlight, highlighted } = useA11yContext(); + const checkBoxRef = React.useRef(null); + const [checkBoxState, setChecked] = React.useState( + areAllRequiredElementsHighlighted(elementsToHighlight, highlighted) ); - return { - highlightedElementsMap: state.highlightedElementsMap, - isToggledOn: checkBoxState === CheckBoxStates.CHECKED, - indeterminate: checkBoxState === CheckBoxStates.INDETERMINATE, - }; -}; - -class HighlightToggle extends Component { - static defaultProps: Partial = { - elementsToHighlight: [], - }; - private checkBoxRef = createRef(); - - 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): 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; + React.useEffect(() => { + const newState = areAllRequiredElementsHighlighted(elementsToHighlight, highlighted); + if (checkBoxRef.current) { + checkBoxRef.current.indeterminate = newState === CheckBoxStates.INDETERMINATE; } + setChecked(newState); + }, [elementsToHighlight, highlighted]); - 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 ( - + const handleToggle = React.useCallback((): void => { + toggleHighlight( + elementsToHighlight.map((e) => e.target[0]), + checkBoxState !== CheckBoxStates.CHECKED ); - } -} + }, [elementsToHighlight, checkBoxState, toggleHighlight]); + + return ( + + ); +}; -export default connect(mapStateToProps, mapDispatchToProps)(HighlightToggle); +export default HighlightToggle; diff --git a/addons/a11y/src/components/Report/Item.tsx b/addons/a11y/src/components/Report/Item.tsx index 2ece16f74a15..c33058b4b445 100644 --- a/addons/a11y/src/components/Report/Item.tsx +++ b/addons/a11y/src/components/Report/Item.tsx @@ -81,11 +81,7 @@ export const Item = (props: ItemProps) => { {item.description} - + {open ? ( diff --git a/addons/a11y/src/components/Report/Rules.tsx b/addons/a11y/src/components/Report/Rules.tsx index 48fe2dd924aa..25940ff9cae5 100644 --- a/addons/a11y/src/components/Report/Rules.tsx +++ b/addons/a11y/src/components/Report/Rules.tsx @@ -1,17 +1,8 @@ import React, { FunctionComponent } from 'react'; import { styled } from '@storybook/theming'; -import { Badge, Icons } from '@storybook/components'; +import { Badge } 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', @@ -22,7 +13,7 @@ const List = styled.div({ fontWeight: '400', } as any); -const Item = styled.div(({ elementWidth }: { elementWidth: number }) => { +const Item = styled.div<{ elementWidth: number }>(({ elementWidth }) => { const maxWidthBeforeBreak = 407; return { flexDirection: elementWidth > maxWidthBeforeBreak ? 'row' : 'inherit', @@ -31,31 +22,20 @@ const Item = styled.div(({ elementWidth }: { elementWidth: number }) => { }; }); -const StyledBadge = styled(Badge)(({ status }: { status: string }) => ({ +const StyledBadge = styled(Badge)({ padding: '2px 8px', marginBottom: 3, minWidth: 65, maxWidth: 'fit-content', width: '100%', textAlign: 'center', -})); +}); const Message = styled.div({ paddingLeft: 6, paddingRight: 23, }); -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: 16, - width: 16, - }, -})); - export enum ImpactValue { MINOR = 'minor', MODERATE = 'moderate', @@ -68,10 +48,7 @@ interface RuleProps { } const formatSeverityText = (severity: string) => { - return severity - .charAt(0) - .toUpperCase() - .concat(severity.slice(1)); + return severity.charAt(0).toUpperCase().concat(severity.slice(1)); }; const Rule: FunctionComponent = ({ rule }) => { @@ -94,7 +71,7 @@ const Rule: FunctionComponent = ({ rule }) => { } return ( - {({ size }: { size: any }) => ( + {({ size }: { size: { width: number; height: number } }) => ( {formatSeverityText(rule.impact)} {rule.message} diff --git a/addons/a11y/src/components/Report/Tags.tsx b/addons/a11y/src/components/Report/Tags.tsx index e11245f55620..1c26cd302f40 100644 --- a/addons/a11y/src/components/Report/Tags.tsx +++ b/addons/a11y/src/components/Report/Tags.tsx @@ -23,7 +23,7 @@ interface TagsProps { export const Tags: FunctionComponent = ({ tags }) => { return ( - {tags.map(tag => ( + {tags.map((tag) => ( {tag} ))} diff --git a/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap b/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap deleted file mode 100644 index e0c37bf518cd..000000000000 --- a/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap +++ /dev/null @@ -1,355 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HighlightToggle component should match snapshot 1`] = ` -.emotion-0 { - cursor: not-allowed; -} - - - - - - - - - - - - - - -`; diff --git a/addons/a11y/src/components/Report/index.tsx b/addons/a11y/src/components/Report/index.tsx index 62f080337eb3..849c9f7a4f84 100644 --- a/addons/a11y/src/components/Report/index.tsx +++ b/addons/a11y/src/components/Report/index.tsx @@ -13,7 +13,7 @@ export interface ReportProps { export const Report: FunctionComponent = ({ items, empty, type }) => ( {items && items.length ? ( - items.map(item => ) + items.map((item) => ) ) : ( {empty} )} diff --git a/addons/a11y/src/components/Tabs.tsx b/addons/a11y/src/components/Tabs.tsx index 26b6febaa040..acdc9c80e93a 100644 --- a/addons/a11y/src/components/Tabs.tsx +++ b/addons/a11y/src/components/Tabs.tsx @@ -1,11 +1,11 @@ -import React, { Component, SyntheticEvent } from 'react'; +import * as React from 'react'; -import { styled, themes } from '@storybook/theming'; +import { styled } 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'; +import { useA11yContext } from './A11yContext'; // TODO: reuse the Tabs component from @storybook/theming instead of re-building identical functionality @@ -23,7 +23,7 @@ const HighlightToggleLabel = styled.label<{}>(({ theme }) => ({ color: theme.color.dark, })); -const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => { +const GlobalToggle = styled.div<{ elementWidth: number }>(({ elementWidth }) => { const maxWidthBeforeBreak = 450; return { cursor: 'pointer', @@ -47,7 +47,7 @@ const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => }; }); -const Item = styled.button( +const Item = styled.button<{ active?: boolean }>( ({ theme }) => ({ textDecoration: 'none', padding: '10px 15px', @@ -66,7 +66,7 @@ const Item = styled.button( borderBottom: `3px solid ${theme.color.secondary}`, }, }), - ({ active, theme }: any) => + ({ active, theme }) => active ? { opacity: 1, @@ -94,68 +94,55 @@ interface TabsProps { }[]; } -interface TabsState { - active: number; -} - function retrieveAllNodesFromResults(items: Result[]): NodeResult[] { - return items.reduce((acc, item) => acc.concat(item.nodes), []); + return items.reduce((acc, item) => acc.concat(item.nodes), [] as NodeResult[]); } -export class Tabs extends Component { - state: TabsState = { - active: 0, - }; +export const Tabs: React.FC = ({ tabs }) => { + const { tab: activeTab, setTab } = useA11yContext(); - 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 ( - - {({ size }: { size: any }) => ( - - - - {tabs.map((tab, index) => ( - - {tab.label} - - ))} - - - {tabs[active].items.length > 0 ? ( - - - {highlightLabel} - - - - ) : null} - {tabs[active].panel} - - )} - - ); - } -} + const handleToggle = React.useCallback( + (event: React.SyntheticEvent) => { + setTab(parseInt(event.currentTarget.getAttribute('data-index') || '', 10)); + }, + [setTab] + ); + + const highlightToggleId = `${tabs[activeTab].type}-global-checkbox`; + const highlightLabel = `Highlight results`; + return ( + + {({ size }: { size: any }) => ( + + + + {tabs.map((tab, index) => ( + + {tab.label} + + ))} + + + {tabs[activeTab].items.length > 0 ? ( + + + {highlightLabel} + + + + ) : null} + {tabs[activeTab].panel} + + )} + + ); +}; diff --git a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap b/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap deleted file mode 100644 index db6f667c93a6..000000000000 --- a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap +++ /dev/null @@ -1,1126 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`A11YPanel should render loader when it's running 1`] = ` -@keyframes animation-0 { - from { - -webkit-transform: rotate(0deg); - -ms-transform: rotate(0deg); - transform: rotate(0deg); - } - - to { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.emotion-4 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - height: 100%; -} - -.emotion-2 { - height: 12px; - width: 12px; - margin-right: 4px; - -webkit-animation: animation-0 1s linear infinite; - animation: animation-0 1s linear infinite; -} - -.emotion-1 { - shape-rendering: inherit; - -webkit-transform: translate3d(0,0,0); - -ms-transform: translate3d(0,0,0); - transform: translate3d(0,0,0); - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - -webkit-animation: animation-0 1s linear infinite; - animation: animation-0 1s linear infinite; -} - -.emotion-0 { - fill: currentColor; -} - - - -
- - - - - - - - - - - - Please wait while the accessibility scan is running ... -
-
-
-`; - -exports[`A11YPanel should render report 1`] = ` -.emotion-0 { - overflow-y: auto; - height: 100%; - overflow-x: auto; - width: 100%; -} - -.emotion-4 { - position: absolute; - bottom: 0; - right: 0; - max-width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: #FFFFFF; - z-index: 1; -} - -.emotion-3 { - border: 0 none; - padding: 4px 10px; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - color: #333333; - background: #FFFFFF; - font-size: 12px; - line-height: 16px; - font-family: "Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: 700; - border-top: 1px solid rgba(0,0,0,.1); - border-left: 1px solid rgba(0,0,0,.1); - margin-left: -1px; - border-radius: 4px 0 0 0; -} - -.emotion-3:not(:last-child) { - border-right: 1px solid rgba(0,0,0,.1); -} - -.emotion-3 + * { - border-left: 1px solid rgba(0,0,0,.1); - border-radius: 0; -} - -.emotion-3:focus { - box-shadow: #1EA7FD 0 -3px 0 0 inset; - outline: 0 none; -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - , - "ctr": 35, - "isSpeedy": false, - "key": "css", - "nonce": undefined, - "tags": Array [ - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - , - ], - }, - } - } - serialized={ - Object { - "map": undefined, - "name": "nh5djz", - "next": undefined, - "styles": "[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;}.simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;}.simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;}.simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0;}.simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;}.simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;}.simplebar-scrollbar:before{position:absolute;content:\\"\\";border-radius:7px;left:0;right:0;opacity:0;transition:opacity 0.2s linear;background:#333333;}.simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;transition:opacity 0s linear;}.simplebar-track.simplebar-vertical{top:0;width:11px;}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;}.simplebar-track.simplebar-horizontal{left:0;height:11px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;}[data-simplebar-direction=\\"rtl\\"] .simplebar-track.simplebar-vertical{right:auto;left:0;}", - "toString": [Function], - } - } - /> - - - - -
-
-
-
-
-
-
-
-
- - 0 - Violations - , - "panel": , - "type": 0, - }, - Object { - "items": Array [], - "label": - 0 - Passes - , - "panel": , - "type": 1, - }, - Object { - "items": Array [], - "label": - 0 - Incomplete - , - "panel": , - "type": 2, - }, - ] - } - > - - - - - -
- - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - -
- - - -
-
-
- - -`; diff --git a/addons/a11y/src/constants.ts b/addons/a11y/src/constants.ts index efabac7a268b..3c96945b4dfe 100755 --- a/addons/a11y/src/constants.ts +++ b/addons/a11y/src/constants.ts @@ -1,11 +1,12 @@ 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'; +export const HIGHLIGHT_STYLE_ID = 'a11yHighlight'; const RESULT = `${ADDON_ID}/result`; const REQUEST = `${ADDON_ID}/request`; +const RUNNING = `${ADDON_ID}/running`; const ERROR = `${ADDON_ID}/error`; +const MANUAL = `${ADDON_ID}/manual`; +const HIGHLIGHT = `${ADDON_ID}/highlight`; -export const EVENTS = { RESULT, REQUEST, ERROR }; +export const EVENTS = { RESULT, REQUEST, RUNNING, ERROR, MANUAL, HIGHLIGHT }; diff --git a/addons/a11y/src/highlight.ts b/addons/a11y/src/highlight.ts new file mode 100644 index 000000000000..1d218362af2e --- /dev/null +++ b/addons/a11y/src/highlight.ts @@ -0,0 +1,12 @@ +export const higlightStyle = (color: string) => ` + outline: 2px dashed ${color}; + outline-offset: 2px; + box-shadow: 0 0 0 6px rgba(255,255,255,0.6); +} +`; + +export const highlightObject = (color: string) => ({ + outline: `2px dashed ${color}`, + outlineOffset: 2, + boxShadow: '0 0 0 6px rgba(255,255,255,0.6),', +}); diff --git a/addons/a11y/src/index.ts b/addons/a11y/src/index.ts index a7f53ec29a73..4728111bfae3 100644 --- a/addons/a11y/src/index.ts +++ b/addons/a11y/src/index.ts @@ -1,101 +1,25 @@ -import { document } from 'global'; -import axe, { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core'; +import { DecoratorFunction } from '@storybook/addons'; import deprecate from 'util-deprecate'; import dedent from 'ts-dedent'; -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) - .catch(error => addons.getChannel().emit(EVENTS.ERROR, String(error))); - }); -}; +export { PARAM_KEY } from './constants'; +export * from './highlight'; if (module && module.hot && module.hot.decline) { module.hot.decline(); } -let storedDefaultSetup: Setup | null = null; - -export const withA11y = makeDecorator({ - name: 'withA11Y', - parameterName: PARAM_KEY, - wrapper: (getStory, context, { parameters }) => { - if (parameters) { - if (storedDefaultSetup === null) { - storedDefaultSetup = { ...setup }; - } - Object.assign(setup, parameters as Setup); - } else if (storedDefaultSetup !== null) { - Object.assign(setup, storedDefaultSetup); - storedDefaultSetup = null; - } - 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; +export const withA11y: DecoratorFunction = deprecate( + (storyFn, storyContext) => { + return storyFn(storyContext); }, dedent` - configureA11y is deprecated, please configure addon-a11y using the addParameter api: - + withA11y(options) is deprecated, please configure addon-a11y using the addParameter api: + addParameters({ - a11y: { - // ... axe options - element: '#root', // optional selector which element to inspect - }, + a11y: options, }); + + More at: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#removed-witha11y-decorator ` ); diff --git a/addons/a11y/src/params.ts b/addons/a11y/src/params.ts new file mode 100644 index 000000000000..fb439ae3f342 --- /dev/null +++ b/addons/a11y/src/params.ts @@ -0,0 +1,14 @@ +import { ElementContext, Spec, RunOptions } from 'axe-core'; + +export interface Setup { + element?: ElementContext; + config: Spec; + options: RunOptions; +} + +export interface A11yParameters { + element?: ElementContext; + config?: Spec; + options?: RunOptions; + manual?: boolean; +} diff --git a/addons/a11y/src/preset/addDecorator.ts b/addons/a11y/src/preset/addDecorator.ts new file mode 100644 index 000000000000..be3b27b26f7a --- /dev/null +++ b/addons/a11y/src/preset/addDecorator.ts @@ -0,0 +1,3 @@ +import { withA11y } from '../index'; + +export const decorators = [withA11y]; diff --git a/addons/a11y/src/preset/index.ts b/addons/a11y/src/preset/index.ts new file mode 100644 index 000000000000..c02c6e282901 --- /dev/null +++ b/addons/a11y/src/preset/index.ts @@ -0,0 +1,7 @@ +export function managerEntries(entry: any[] = []) { + return [...entry, require.resolve('../register')]; +} + +export function config(entry: any[] = []) { + return [...entry, require.resolve('../a11yRunner'), require.resolve('../a11yHighlight')]; +} diff --git a/addons/a11y/src/redux-config.tsx b/addons/a11y/src/redux-config.tsx deleted file mode 100644 index 929f2d6aa055..000000000000 --- a/addons/a11y/src/redux-config.tsx +++ /dev/null @@ -1,44 +0,0 @@ -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; diff --git a/addons/a11y/src/register.tsx b/addons/a11y/src/register.tsx index 9b360ec97d4c..b5f285d1d625 100644 --- a/addons/a11y/src/register.tsx +++ b/addons/a11y/src/register.tsx @@ -1,88 +1,11 @@ -import React, { Fragment, FunctionComponent } from 'react'; -import { styled } from '@storybook/theming'; - +import React from 'react'; import { addons, types } from '@storybook/addons'; import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; import { ColorBlindness } from './components/ColorBlindness'; import { A11YPanel } from './components/A11YPanel'; +import { A11yContextProvider } from './components/A11yContext'; -const Hidden = styled.div(() => ({ - '&, & svg': { - position: 'absolute', - width: 0, - height: 0, - }, -})); - -const PreviewWrapper: FunctionComponent<{}> = p => ( - - {p.children} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, () => { addons.add(PANEL_ID, { title: '', type: types.TOOL, @@ -93,13 +16,11 @@ addons.register(ADDON_ID, api => { addons.add(PANEL_ID, { title: 'Accessibility', type: types.PANEL, - render: ({ active, key }) => , + render: ({ active = true, key }) => ( + + + + ), paramKey: PARAM_KEY, }); - - addons.add(PANEL_ID, { - title: '', - type: types.PREVIEW, - render: PreviewWrapper as any, - }); }); diff --git a/addons/a11y/tsconfig.json b/addons/a11y/tsconfig.json index 8876bb6737a1..a3eb85b9984f 100644 --- a/addons/a11y/tsconfig.json +++ b/addons/a11y/tsconfig.json @@ -2,12 +2,14 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env"] + "types": ["webpack-env", "jest"], + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] + "include": ["src/**/*"], + "exclude": ["src/__tests__/**/*"] } diff --git a/addons/actions/README.md b/addons/actions/README.md index d7cf0be887c3..ea170685383c 100644 --- a/addons/actions/README.md +++ b/addons/actions/README.md @@ -18,64 +18,67 @@ Then, add following content to `.storybook/main.js` ```js module.exports = { - addons: ['@storybook/addon-actions/register'] -} + addons: ['@storybook/addon-actions'], +}; ``` -Import the `action` function and use it to create actions handlers. When creating action handlers, provide a **name** to make it easier to identify. +## Actions args -> _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybookjs/storybook-addon-actions/issues/29#issuecomment-288274794)_ +Starting in SB6.0, we recommend using story parameters to specify actions which get passed into your story as [Args](https://docs.google.com/document/d/1Mhp1UFRCKCsN8pjlfPdz8ZdisgjNXeMXpXvGoALjxYM/edit?usp=sharing) (passed as the first argument when `passArgsFirst` is set to `true`). + +The first option is to specify `argTypes` for your story with an `action` field. Take the following example: ```js -import { action } from '@storybook/addon-actions'; import Button from './button'; export default { title: 'Button', - component: Button, + argTypes: { onClick: { action: 'clicked' } }, }; -export const defaultView = () => ( - -); +export const Basic = ({ onClick }) => ; ``` -## Multiple actions - -If your story requires multiple actions, it may be convenient to use `actions` to create many at once: +Alternatively, suppose you have a naming convention, like `onX` for event handlers. The following configuration automatically creates actions for each `onX` `argType` (which you can either specify manually or generate automatically using [Storybook Docs](https://www.npmjs.com/package/@storybook/addon-docs). ```js -import { actions } from '@storybook/addon-actions'; import Button from './button'; export default { title: 'Button', component: Button, + parameters: { actions: { argTypesRegex: '^on.*' } }, }; -// This will lead to { onClick: action('onClick'), ... } -const eventsFromNames = actions('onClick', 'onMouseOver'); +export const Basic = ({ onClick }) => ; +``` -// This will lead to { onClick: action('clicked'), ... } -const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' }); +> **NOTE:** If you're generating `argTypes` in using another addon (like Docs, which is the common behavior) you'll need to make sure that the actions addon loads **AFTER** the other addon. You can do this by listing it later in the `addons` registration code in `.storybook/main.js`. -export const first = () => ( - -); +## Manually-specified actions -export const second = () => ( - -); -``` +Import the `action` function and use it to create actions handlers. When creating action handlers, provide a **name** to make it easier to identify. -## Action Decorators +> _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybookjs/storybook-addon-actions/issues/29#issuecomment-288274794)_ -If you wish to process action data before sending them over to the logger, you can do it with action decorators. +```js +import { action } from '@storybook/addon-actions'; +import Button from './button'; + +export default { + title: 'Button', + component: Button, +}; -`decorate` takes an array of decorator functions. Each decorator function is passed an array of arguments, and should return a new arguments array to use. `decorate` returns a object with two functions: `action` and `actions`, that act like the above, except they log the modified arguments instead of the original arguments. +export const defaultView = () => ; +``` + +## Multiple actions + +If your story requires multiple actions, it may be convenient to use `actions` to create many at once: ```js -import { decorate } from '@storybook/addon-actions'; +import { actions } from '@storybook/addon-actions'; import Button from './button'; export default { @@ -83,24 +86,24 @@ export default { component: Button, }; -const firstArg = decorate([args => args.slice(0, 1)]); +// This will lead to { onClick: action('onClick'), ... } +const eventsFromNames = actions('onClick', 'onMouseOver'); -export const first = () => ( - -); +// This will lead to { onClick: action('clicked'), ... } +const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' }); + +export const first = () => ; + +export const second = () => ; ``` ## Configuration -Arguments which are passed to the action call will have to be serialized while be "transferred" -over the channel. +Arguments which are passed to the action call will have to be serialized while be "transferred" over the channel. -This is not very optimal and can cause lag when large objects are being logged, for this reason it is possible -to configure a maximum depth. +This is not very optimal and can cause lag when large objects are being logged, for this reason it is possible to configure a maximum depth. -The action logger, by default, will log all actions fired during the lifetime of the story. After a while -this can make the storybook laggy. As a workaround, you can configure an upper limit to how many actions should -be logged. +The action logger, by default, will log all actions fired during the lifetime of the story. After a while this can make the storybook laggy. As a workaround, you can configure an upper limit to how many actions should be logged. To apply the configuration globally use the `configureActions` function in your `preview.js` file. @@ -115,6 +118,7 @@ configureActions({ ``` To apply the configuration per action use: + ```js action('my-action', { depth: 5, @@ -123,27 +127,27 @@ action('my-action', { ### Available Options -|Name|Type|Description|Default| -|---|---|---|---| -|`depth`|Number|Configures the transferred depth of any logged objects.|`10`| -|`clearOnStoryChange`|Boolean|Flag whether to clear the action logger when switching away from the current story.|`true`| -|`limit`|Number|Limits the number of items logged in the action logger|`50`| +| Name | Type | Description | Default | +| -------------------- | ------- | ----------------------------------------------------------------------------------- | ------- | +| `depth` | Number | Configures the transferred depth of any logged objects. | `10` | +| `clearOnStoryChange` | Boolean | Flag whether to clear the action logger when switching away from the current story. | `true` | +| `limit` | Number | Limits the number of items logged in the action logger | `50` | -## withActions decorator +## Declarative Configuration via Parameters -You can define action handles in a declarative way using `withActions` decorators. It accepts the same arguments as [`actions`](#multiple-actions) +You can define action handles in a declarative way using parameters. They accepts the same arguments as [`actions`](#multiple-actions) Keys have `' '` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`. ```js -import { withActions } from '@storybook/addon-actions'; import Button from './button'; export default { title: 'Button', - decorators: [withActions('mouseover', 'click .btn')] + parameters: { + actions: { + handles: ['mouseover', 'click .btn'] + } }; -export const first = () => ( - -); +export const first = () => ; ``` diff --git a/addons/actions/package.json b/addons/actions/package.json index 4940d4b917e2..d56053c263f2 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Action Logger addon for storybook", "keywords": [ "storybook" @@ -15,40 +15,55 @@ "directory": "addons/actions" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "global": "^4.3.2", - "polished": "^3.3.1", + "lodash": "^4.17.15", + "polished": "^3.4.4", "prop-types": "^15.7.2", "react": "^16.8.3", - "react-inspector": "^4.0.0", - "uuid": "^3.3.2" + "react-inspector": "^5.0.1", + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1", + "util-deprecate": "^1.0.2", + "uuid": "^8.0.0" }, "devDependencies": { - "@types/lodash": "^4.14.149", - "@types/uuid": "^3.4.4" + "@types/lodash": "^4.14.150", + "@types/uuid": "^7.0.3", + "@types/webpack-env": "^1.15.2" + }, + "peerDependencies": { + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/actions/preset.js b/addons/actions/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/actions/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/actions/register.js b/addons/actions/register.js index 9232e6c069d5..cc38cb06f1f2 100644 --- a/addons/actions/register.js +++ b/addons/actions/register.js @@ -1 +1 @@ -require('./dist/manager').register(); +require('./dist/register'); diff --git a/addons/actions/src/components/ActionLogger/style.tsx b/addons/actions/src/components/ActionLogger/style.tsx index 450e407a697c..4ffe870286c8 100644 --- a/addons/actions/src/components/ActionLogger/style.tsx +++ b/addons/actions/src/components/ActionLogger/style.tsx @@ -8,6 +8,7 @@ export const Action = styled.div({ borderBottom: '1px solid transparent', transition: 'all 0.1s', alignItems: 'flex-start', + whiteSpace: 'pre', }); export const Counter = styled.div<{}>(({ theme }) => ({ diff --git a/addons/actions/src/manager.tsx b/addons/actions/src/manager.tsx deleted file mode 100644 index a7bfd9dbf596..000000000000 --- a/addons/actions/src/manager.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import addons from '@storybook/addons'; -import ActionLogger from './containers/ActionLogger'; -import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; - -export function register() { - addons.register(ADDON_ID, api => { - addons.addPanel(PANEL_ID, { - title: 'Actions', - render: ({ active, key }) => , - paramKey: PARAM_KEY, - }); - }); -} diff --git a/addons/actions/src/preset/addArgs.test.ts b/addons/actions/src/preset/addArgs.test.ts new file mode 100644 index 000000000000..ca72c7f4ad11 --- /dev/null +++ b/addons/actions/src/preset/addArgs.test.ts @@ -0,0 +1,76 @@ +import { StoryContext } from '@storybook/addons'; +import { inferActionsFromArgTypesRegex, addActionsFromArgTypes } from './addArgs'; + +const withDefaultValue = (argTypes) => + Object.keys(argTypes).filter((key) => !!argTypes[key].defaultValue); + +describe('actions parameter enhancers', () => { + describe('actions.argTypesRegex parameter', () => { + const baseParameters = { + argTypes: { onClick: {}, onFocus: {}, somethingElse: {} }, + actions: { argTypesRegex: '^on.*' }, + }; + + it('should add actions that match a pattern', () => { + const parameters = baseParameters; + const argTypes = inferActionsFromArgTypesRegex({ parameters } as StoryContext); + expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onFocus']); + }); + + it('should prioritize pre-existing argTypes unless they are null', () => { + const parameters = { + ...baseParameters, + argTypes: { + onClick: { defaultValue: 'pre-existing value' }, + onFocus: { defaultValue: null }, + }, + }; + const argTypes = inferActionsFromArgTypesRegex({ parameters } as StoryContext); + expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onFocus']); + expect(argTypes.onClick.defaultValue).toEqual('pre-existing value'); + expect(argTypes.onFocus.defaultValue).not.toBeNull(); + }); + + it('should do nothing if actions are disabled', () => { + const parameters = { + ...baseParameters, + actions: { ...baseParameters.actions, disable: true }, + }; + const result = inferActionsFromArgTypesRegex({ parameters } as StoryContext); + expect(result).toEqual(parameters.argTypes); + }); + }); + + describe('argTypes.action parameter', () => { + const baseParameters = { + argTypes: { + onClick: { action: 'clicked!' }, + onBlur: { action: 'blurred!' }, + }, + }; + it('should add actions based on action.args', () => { + const parameters = baseParameters; + const argTypes = addActionsFromArgTypes({ parameters } as StoryContext); + expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onBlur']); + }); + + it('should prioritize pre-existing args', () => { + const parameters = { + ...baseParameters, + argTypes: { + onClick: { defaultValue: 'pre-existing value', action: 'onClick' }, + onBlur: { action: 'onBlur' }, + }, + }; + const argTypes = addActionsFromArgTypes({ parameters } as StoryContext); + expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onBlur']); + expect(argTypes.onClick.defaultValue).toEqual('pre-existing value'); + }); + + it('should do nothing if actions are disabled', () => { + const parameters = { ...baseParameters, actions: { disable: true } }; + const result = addActionsFromArgTypes({ parameters } as StoryContext); + expect(result).toEqual(parameters.argTypes); + }); + }); +}); diff --git a/addons/actions/src/preset/addArgs.ts b/addons/actions/src/preset/addArgs.ts new file mode 100644 index 000000000000..91d567d768bb --- /dev/null +++ b/addons/actions/src/preset/addArgs.ts @@ -0,0 +1,48 @@ +import mapValues from 'lodash/mapValues'; +import { ArgTypesEnhancer } from '@storybook/client-api'; + +import { action } from '../index'; + +// interface ActionsParameter { +// disable?: boolean; +// argTypesRegex?: RegExp; +// } + +/** + * Automatically add action args for argTypes whose name + * matches a regex, such as `^on.*` for react-style `onClick` etc. + */ +export const inferActionsFromArgTypesRegex: ArgTypesEnhancer = (context) => { + const { actions, argTypes } = context.parameters; + if (!actions || actions.disable || !actions.argTypesRegex || !argTypes) { + return argTypes; + } + + const argTypesRegex = new RegExp(actions.argTypesRegex); + return mapValues(argTypes, (argType, name) => { + if (!argTypesRegex.test(name)) { + return argType; + } + return { ...argType, defaultValue: argType.defaultValue || action(name) }; + }); +}; + +/** + * Add action args for list of strings. + */ +export const addActionsFromArgTypes: ArgTypesEnhancer = (context) => { + const { argTypes, actions } = context.parameters; + if (actions?.disable || !argTypes) { + return argTypes; + } + + return mapValues(argTypes, (argType, name) => { + if (!argType.action) { + return argType; + } + const message = typeof argType.action === 'string' ? argType.action : name; + return { ...argType, defaultValue: argType.defaultValue || action(message) }; + }); +}; + +export const argTypesEnhancers = [addActionsFromArgTypes, inferActionsFromArgTypesRegex]; diff --git a/addons/actions/src/preset/addDecorator.ts b/addons/actions/src/preset/addDecorator.ts new file mode 100644 index 000000000000..f0044588cc2f --- /dev/null +++ b/addons/actions/src/preset/addDecorator.ts @@ -0,0 +1,3 @@ +import { withActions } from '../index'; + +export const decorators = [withActions]; diff --git a/addons/actions/src/preset/index.ts b/addons/actions/src/preset/index.ts new file mode 100644 index 000000000000..1589a4b0d4e7 --- /dev/null +++ b/addons/actions/src/preset/index.ts @@ -0,0 +1,15 @@ +interface ActionsOptions { + addDecorator?: boolean; +} + +export function managerEntries(entry: any[] = [], options: any) { + return [...entry, require.resolve('../register')]; +} + +export function config(entry: any[] = [], { addDecorator = true }: ActionsOptions = {}) { + const actionConfig = []; + if (addDecorator) { + actionConfig.push(require.resolve('./addDecorator')); + } + return [...entry, ...actionConfig, require.resolve('./addArgs')]; +} diff --git a/addons/actions/src/preview/__tests__/action.test.js b/addons/actions/src/preview/__tests__/action.test.js index 36a954efdb58..7c82a20b1e66 100644 --- a/addons/actions/src/preview/__tests__/action.test.js +++ b/addons/actions/src/preview/__tests__/action.test.js @@ -8,7 +8,7 @@ const createChannel = () => { addons.getChannel.mockReturnValue(channel); return channel; }; -const getChannelData = channel => channel.emit.mock.calls[0][1].data.args; +const getChannelData = (channel) => channel.emit.mock.calls[0][1].data.args; describe('Action', () => { it('with one argument', () => { diff --git a/addons/actions/src/preview/__tests__/actions.test.js b/addons/actions/src/preview/__tests__/actions.test.js new file mode 100644 index 000000000000..17b72a73b6d3 --- /dev/null +++ b/addons/actions/src/preview/__tests__/actions.test.js @@ -0,0 +1,90 @@ +import addons from '@storybook/addons'; +import { actions } from '../..'; + +jest.mock('@storybook/addons'); + +const createChannel = () => { + const channel = { emit: jest.fn() }; + addons.getChannel.mockReturnValue(channel); + return channel; +}; +const getChannelData = (channel, callIndex) => channel.emit.mock.calls[callIndex][1].data; +const getChannelOptions = (channel, callIndex) => channel.emit.mock.calls[callIndex][1].options; + +describe('Actions', () => { + it('with one argument', () => { + const channel = createChannel(); + + const actionsResult = actions('test-action'); + + expect(Object.keys(actionsResult)).toEqual(['test-action']); + actionsResult['test-action']('one'); + + expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] }); + }); + + it('with multiple arguments', () => { + const channel = createChannel(); + + const actionsResult = actions('test-action', 'test-action2'); + + expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']); + + actionsResult['test-action']('one'); + actionsResult['test-action2']('two'); + + expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] }); + expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] }); + }); + + it('with multiple arguments + config', () => { + const channel = createChannel(); + + const actionsResult = actions('test-action', 'test-action2', { some: 'config' }); + + expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']); + + actionsResult['test-action']('one'); + actionsResult['test-action2']('two'); + + expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] }); + expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] }); + + expect(getChannelOptions(channel, 0).some).toEqual('config'); + expect(getChannelOptions(channel, 1).some).toEqual('config'); + }); + + it('with multiple arguments as object', () => { + const channel = createChannel(); + + const actionsResult = actions({ + 'test-action': 'test action', + 'test-action2': 'test action two', + }); + + expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']); + + actionsResult['test-action']('one'); + actionsResult['test-action2']('two'); + + expect(getChannelData(channel, 0)).toEqual({ name: 'test action', args: ['one'] }); + expect(getChannelData(channel, 1)).toEqual({ name: 'test action two', args: ['two'] }); + }); + + it('with first argument as array of arguments + config', () => { + const channel = createChannel(); + + const actionsResult = actions(['test-action', 'test-action2', { some: 'config' }]); + + expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']); + + actionsResult['test-action']('one'); + actionsResult['test-action2']('two'); + + expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] }); + expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] }); + + expect(getChannelOptions(channel, 0).some).toEqual('config'); + expect(getChannelOptions(channel, 1).some).toEqual('config'); + }); +}); diff --git a/addons/actions/src/preview/action.ts b/addons/actions/src/preview/action.ts index d9a026472de8..d48257c1ee4d 100644 --- a/addons/actions/src/preview/action.ts +++ b/addons/actions/src/preview/action.ts @@ -1,4 +1,4 @@ -import uuid from 'uuid/v4'; +import { v4 as uuidv4 } from 'uuid'; import { addons } from '@storybook/addons'; import { EVENT_ID } from '../constants'; import { ActionDisplay, ActionOptions, HandlerFunction } from '../models'; @@ -12,7 +12,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti const handler = function actionHandler(...args: any[]) { const channel = addons.getChannel(); - const id = uuid(); + const id = uuidv4(); const minDepth = 5; // anything less is really just storybook internals const actionDisplayToEmit: ActionDisplay = { diff --git a/addons/actions/src/preview/actions.ts b/addons/actions/src/preview/actions.ts index 41c6d077a6a4..9e595ed7a151 100644 --- a/addons/actions/src/preview/actions.ts +++ b/addons/actions/src/preview/actions.ts @@ -4,9 +4,13 @@ import { config } from './configureActions'; export const actions: ActionsFunction = (...args: any[]) => { let options: ActionOptions = config; - const names = args; + let names = args; + // args argument can be a single argument as an array + if (names.length === 1 && Array.isArray(names[0])) { + [names] = names; + } // last argument can be options - if (names.length !== 1 && typeof args[args.length - 1] !== 'string') { + if (names.length !== 1 && typeof names[names.length - 1] !== 'string') { options = { ...config, ...names.pop(), @@ -16,13 +20,13 @@ export const actions: ActionsFunction = (...args: any[]) => { let namesObject = names[0]; if (names.length !== 1 || typeof namesObject === 'string') { namesObject = {}; - names.forEach(name => { + names.forEach((name) => { namesObject[name] = name; }); } const actionsObject: ActionsMap = {}; - Object.keys(namesObject).forEach(name => { + Object.keys(namesObject).forEach((name) => { actionsObject[name] = action(namesObject[name], options); }); return actionsObject; diff --git a/addons/actions/src/preview/decorateAction.ts b/addons/actions/src/preview/decorateAction.ts index 817e7af1f29e..b84f49bdb20a 100644 --- a/addons/actions/src/preview/decorateAction.ts +++ b/addons/actions/src/preview/decorateAction.ts @@ -1,37 +1,37 @@ -import { action } from './action'; -import { actions } from './actions'; -import { createDecorator } from './withActions'; -import { ActionOptions, DecoratorFunction, HandlerFunction } from '../models'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; -const applyDecorators = (decorators: DecoratorFunction[], actionCallback: HandlerFunction) => { - return (..._args: any[]) => { - const decorated = decorators.reduce((args, storyFn) => storyFn(args), _args); - actionCallback(...decorated); - }; -}; +import { DecoratorFunction } from '../models'; -export const decorateAction = ( - decorators: DecoratorFunction[] -): ((name: string, options?: ActionOptions) => HandlerFunction) => { - return (name: string, options?: ActionOptions) => { - const callAction = action(name, options); - return applyDecorators(decorators, callAction); - }; +export const decorateAction = (_decorators: DecoratorFunction[]) => { + return deprecate( + () => {}, + dedent` + decorateAction is no longer supported as of Storybook 6.0. + ` + ); }; -export const decorate = (decorators: DecoratorFunction[]) => { - const decorated = decorateAction(decorators); - const decoratedActions = (...args: any[]) => { - const rawActions = actions(...args); - const actionsObject = {} as any; - Object.keys(rawActions).forEach(name => { - actionsObject[name] = applyDecorators(decorators, rawActions[name]); - }); - return actionsObject; - }; - return { - action: decorated, - actions: decoratedActions, - withActions: createDecorator(decoratedActions), - }; +const deprecatedCallback = deprecate(() => {}, +'decorate.* is no longer supported as of Storybook 6.0.'); + +export const decorate = (_decorators: DecoratorFunction[]) => { + return deprecate( + () => { + return { + action: deprecatedCallback, + actions: deprecatedCallback, + withActions: deprecatedCallback, + }; + }, + dedent` + decorate is deprecated, please configure addon-actions using the addParameter api: + + addParameters({ + actions: { + handles: options + }, + }); + ` + ); }; diff --git a/addons/actions/src/preview/withActions.ts b/addons/actions/src/preview/withActions.ts index 5d4b620be743..2d78b764f2f1 100644 --- a/addons/actions/src/preview/withActions.ts +++ b/addons/actions/src/preview/withActions.ts @@ -1,9 +1,14 @@ // Based on http://backbonejs.org/docs/backbone.html#section-164 import { document, Element } from 'global'; import { useEffect } from '@storybook/client-api'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; +import { makeDecorator } from '@storybook/addons'; import { actions } from './actions'; +import { PARAM_KEY } from '../constants'; + const delegateEventSplitter = /^(\S+)\s*(.*)$/; const isIE = Element != null && !Element.prototype.matches; @@ -22,8 +27,8 @@ const hasMatchInAncestry = (element: any, selector: any): boolean => { return hasMatchInAncestry(parent, selector); }; -const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) => { - const actionsObject = actionsFn(...args); +const createHandlers = (actionsFn: (...arg: any[]) => object, ...handles: any[]) => { + const actionsObject = actionsFn(...handles); return Object.entries(actionsObject).map(([key, action]) => { const [_, eventName, selector] = key.match(delegateEventSplitter); return { @@ -37,18 +42,46 @@ const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) => }); }; -export const createDecorator = (actionsFn: any) => (...args: any[]) => (storyFn: () => any) => { - useEffect(() => { - if (root != null) { - const handlers = createHandlers(actionsFn, ...args); - handlers.forEach(({ eventName, handler }) => root.addEventListener(eventName, handler)); - return () => - handlers.forEach(({ eventName, handler }) => root.removeEventListener(eventName, handler)); - } - return undefined; - }, [root, actionsFn, args]); - - return storyFn(); +const applyEventHandlers = deprecate( + (actionsFn: any, ...handles: any[]) => { + useEffect(() => { + if (root != null) { + const handlers = createHandlers(actionsFn, ...handles); + handlers.forEach(({ eventName, handler }) => root.addEventListener(eventName, handler)); + return () => + handlers.forEach(({ eventName, handler }) => + root.removeEventListener(eventName, handler) + ); + } + return undefined; + }, [root, actionsFn, handles]); + }, + dedent` + withActions(options) is deprecated, please configure addon-actions using the addParameter api: + + addParameters({ + actions: { + handles: options + }, + }); + ` +); + +const applyDeprecatedOptions = (actionsFn: any, options: any[]) => { + if (options) { + applyEventHandlers(actionsFn, options); + } }; -export const withActions = createDecorator(actions); +export const withActions = makeDecorator({ + name: 'withActions', + parameterName: PARAM_KEY, + skipIfNoParametersOrOptions: true, + wrapper: (getStory, context, { parameters, options }) => { + applyDeprecatedOptions(actions, options as any[]); + + if (parameters && parameters.handles) applyEventHandlers(actions, ...parameters.handles); + + return getStory(context); + }, +}); diff --git a/addons/actions/src/register.tsx b/addons/actions/src/register.tsx new file mode 100644 index 000000000000..b4db8997c514 --- /dev/null +++ b/addons/actions/src/register.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { addons, types } from '@storybook/addons'; +import ActionLogger from './containers/ActionLogger'; +import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; + +addons.register(ADDON_ID, (api) => { + addons.addPanel(PANEL_ID, { + title: 'Actions', + type: types.PANEL, + render: ({ active, key }) => , + paramKey: PARAM_KEY, + }); +}); diff --git a/addons/actions/tsconfig.json b/addons/actions/tsconfig.json index 8876bb6737a1..d97959cff1e0 100644 --- a/addons/actions/tsconfig.json +++ b/addons/actions/tsconfig.json @@ -2,12 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env"] + "types": ["webpack-env", "jest"] }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] + "include": ["src/**/*"], + "exclude": ["src/__tests__/**/*", "src/**/*.test.ts"] } diff --git a/addons/backgrounds/README.md b/addons/backgrounds/README.md index fe8554429f64..666bb34d9068 100644 --- a/addons/backgrounds/README.md +++ b/addons/backgrounds/README.md @@ -9,101 +9,121 @@ Storybook Background Addon can be used to change background colors inside the pr ## Installation ```sh -npm i -D @storybook/addon-backgrounds +yarn add @storybook/addon-backgrounds --dev ``` ## Configuration -Then create a file called `main.js` in your storybook config. +If it doesn't exist yet, create a file called `main.js` in your storybook config. -Add following content to it: +Add the following content to it: ```js module.exports = { - addons: ['@storybook/addon-backgrounds/register'] -} + addons: ['@storybook/addon-backgrounds'], +}; ``` ## Usage -Then write your stories like this: +Backgrounds requires two parameters: -```js +- `default` - matches the **name** of the value which will be selected by default. +- `values` - an array of elements containing name and value (with a valid css color e.g. HEX, RGBA, etc.) + +Write your stories like this: + +```jsx import React from 'react'; +/* + * Button.stories.js + * Applies backgrounds to the Stories + */ export default { title: 'Button', parameters: { - backgrounds: [ - { name: 'twitter', value: '#00aced', default: true }, - { name: 'facebook', value: '#3b5998' }, - ] + backgrounds: { + default: 'twitter', + values: [ + { name: 'twitter', value: '#00aced' }, + { name: 'facebook', value: '#3b5998' }, + ], + }, }, }; -export const defaultView = () => ( - -); +export const defaultView = () => ; ``` -You can add the backgrounds to all stories with `addParameters` in `.storybook/preview.js`: +You can add the backgrounds to all stories by using `parameters` in `.storybook/preview.js`: ```js -import { addParameters } from '@storybook/react'; // <- or your storybook framework - -addParameters({ - backgrounds: [ - { name: 'twitter', value: '#00aced', default: true }, - { name: 'facebook', value: '#3b5998' }, - ], -}); +export const parameters = { + backgrounds: { + default: 'twitter', + values: [ + { name: 'twitter', value: '#00aced' }, + { name: 'facebook', value: '#3b5998' }, + ], + }, +}; ``` If you want to override backgrounds for a single story or group of stories, pass the `backgrounds` parameter: -```js +```jsx import React from 'react'; export default { title: 'Button', -} +}; -export const defaultView = () => ( - -); -defaultView.story = { - parameters: { - backgrounds: [ - { name: 'red', value: 'rgba(255, 0, 0)' }, - ], +export const defaultView = () => ; + +defaultView.parameters = { + backgrounds: { + default: 'red', + values: [{ name: 'red', value: 'rgba(255, 0, 0)' }], }, }; ``` -If you don't want to use backgrounds for a story, you can set the `backgrounds` parameter to `[]`, or use `{ disable: true }` to skip the addon: +Once you have defined backgrounds for your stories (as can be seen in the examples above), you can set a default background per story by passing the `default` property using a name from the available backgrounds: -```js +```jsx import React from 'react'; +/* + * Button.stories.js + * Applies default background to the Stories + */ export default { title: 'Button', -} - -export const noBackgrounds = () => ( - -); -noBackgrounds.story = { parameters: { - backgrounds: [], + backgrounds: { default: 'twitter' }, }, }; -export const disabledBackgrounds = () => ( - -); -disabledBackgrounds.story = { - parameters: { - backgrounds: { disabled: true }, - }, +export const twitterColorSelected = () => ; +``` + +If you don't want to use backgrounds for a story, you can set the `backgrounds` parameter to `{ disable: true }` to skip the addon: + +```jsx +import React from 'react'; + +/* + * Button.stories.js + * Disables backgrounds for one Story + */ +export default { + title: 'Button', +}; + +export const disabledBackgrounds = () => ; + +disabledBackgrounds.parameters = { + backgrounds: { disable: true }, }; ``` diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index 4ea1ad7a9042..bca0104b4870 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "A storybook addon to show different backgrounds for your preview", "keywords": [ "addon", @@ -19,35 +19,45 @@ }, "license": "MIT", "author": "jbaxleyiii", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "memoizerific": "^1.11.3", "react": "^16.8.3", - "util-deprecate": "^1.0.2" + "regenerator-runtime": "^0.13.3" }, "devDependencies": { - "@types/util-deprecate": "^1.0.0" + "@types/webpack-env": "^1.15.2" + }, + "peerDependencies": { + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/backgrounds/src/containers/BackgroundSelector.tsx b/addons/backgrounds/src/containers/BackgroundSelector.tsx index f2876d89cef1..69cc0ede8739 100644 --- a/addons/backgrounds/src/containers/BackgroundSelector.tsx +++ b/addons/backgrounds/src/containers/BackgroundSelector.tsx @@ -3,13 +3,23 @@ import memoize from 'memoizerific'; import { Combo, Consumer, API } from '@storybook/api'; import { Global, Theme } from '@storybook/theming'; +import { logger } from '@storybook/client-logger'; import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; -import { PARAM_KEY, EVENTS } from '../constants'; +import { PARAM_KEY as BACKGROUNDS_PARAM_KEY, EVENTS } from '../constants'; import { ColorIcon } from '../components/ColorIcon'; -interface Item { +interface GlobalState { + name: string | undefined; + selected: string | undefined; +} + +interface Props { + api: API; +} + +interface BackgroundSelectorItem { id: string; title: string; onClick: () => void; @@ -17,10 +27,22 @@ interface Item { right?: ReactElement; } -interface Input { +interface Background { name: string; value: string; - default?: boolean; +} + +interface BackgroundsParameter { + default?: string; + disable?: boolean; + values: Background[]; +} + +interface BackgroundsConfig { + backgrounds: Background[] | null; + selectedBackground: string | null; + defaultBackgroundName: string | null; + disable: boolean; } const iframeId = 'storybook-preview-iframe'; @@ -32,7 +54,7 @@ const createBackgroundSelectorItem = memoize(1000)( value: string, hasSwatch: boolean, change: (arg: { selected: string; name: string }) => void - ): Item => ({ + ): BackgroundSelectorItem => ({ id: id || name, title: name, onClick: () => { @@ -43,86 +65,114 @@ const createBackgroundSelectorItem = memoize(1000)( }) ); -const getSelectedBackgroundColor = (list: Input[], currentSelectedValue: string): string => { - if (!list.length) { - return 'transparent'; +const getDisplayedItems = memoize(10)( + ( + backgrounds: Background[], + selectedBackgroundColor: string | null, + change: (arg: { selected: string; name: string }) => void + ) => { + const backgroundSelectorItems = backgrounds.map(({ name, value }) => + createBackgroundSelectorItem(null, name, value, true, change) + ); + + if (selectedBackgroundColor !== 'transparent') { + return [ + createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change), + ...backgroundSelectorItems, + ]; + } + + return backgroundSelectorItems; } +); +const getSelectedBackgroundColor = ( + backgrounds: Background[] = [], + currentSelectedValue: string, + defaultName: string +): string => { if (currentSelectedValue === 'transparent') { - return currentSelectedValue; + return 'transparent'; } - if (list.find(i => i.value === currentSelectedValue)) { + if (backgrounds.find((background) => background.value === currentSelectedValue)) { return currentSelectedValue; } - if (list.find(i => i.default)) { - return list.find(i => i.default).value; + const defaultBackground = backgrounds.find((background) => background.name === defaultName); + if (defaultBackground) { + return defaultBackground.value; } - return 'transparent'; -}; - -const mapper = ({ api, state }: Combo): { items: Input[]; selected: string | null } => { - const story = state.storiesHash[state.storyId]; - const list = story ? api.getParameters(story.id, PARAM_KEY) : []; - const selected = state.addons[PARAM_KEY] || null; + if (defaultName) { + const availableColors = backgrounds.map((background) => background.name).join(', '); + logger.warn( + `Backgrounds Addon: could not find the default color "${defaultName}". + These are the available colors for your story based on your configuration: ${availableColors}` + ); + } - return { items: list || [], selected }; + return 'transparent'; }; -const getDisplayedItems = memoize(10)( - ( - list: Input[], - selected: string | null, - change: (arg: { selected: string; name: string }) => void - ) => { - let availableBackgroundSelectorItems: Item[] = []; +const getBackgroundsConfig = ({ api, state }: Combo): BackgroundsConfig => { + const backgroundsParameter = api.getCurrentParameter(BACKGROUNDS_PARAM_KEY); + const selectedBackgroundValue = state.addons[BACKGROUNDS_PARAM_KEY] || null; - if (selected !== 'transparent') { - availableBackgroundSelectorItems.push( - createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change) - ); - } - - if (list.length) { - availableBackgroundSelectorItems = [ - ...availableBackgroundSelectorItems, - ...list.map(({ name, value }) => - createBackgroundSelectorItem(null, name, value, true, change) - ), - ]; - } - - return availableBackgroundSelectorItems; + if (Array.isArray(backgroundsParameter)) { + logger.warn( + 'Addon Backgrounds api has changed in Storybook 6.0. Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md' + ); } -); -interface GlobalState { - name: string | undefined; - selected: string | undefined; -} + const isBackgroundsEmpty = !backgroundsParameter?.values?.length; + if (backgroundsParameter?.disable || isBackgroundsEmpty) { + // other null properties are necessary to keep the same return shape for Consumer memoization + return { + disable: true, + backgrounds: null, + selectedBackground: null, + defaultBackgroundName: null, + }; + } -interface Props { - api: API; -} + return { + disable: false, + backgrounds: backgroundsParameter?.values, + selectedBackground: selectedBackgroundValue, + defaultBackgroundName: backgroundsParameter?.default, + }; +}; export class BackgroundSelector extends Component { change = ({ selected, name }: GlobalState) => { const { api } = this.props; if (typeof selected === 'string') { - api.setAddonState(PARAM_KEY, selected); + api.setAddonState(BACKGROUNDS_PARAM_KEY, selected); } api.emit(EVENTS.UPDATE, { selected, name }); }; render() { return ( - - {({ items, selected }: ReturnType) => { - const selectedBackgroundColor = getSelectedBackgroundColor(items, selected); - - return items.length ? ( + + {({ + disable, + backgrounds, + selectedBackground, + defaultBackgroundName, + }: BackgroundsConfig) => { + if (disable) { + return null; + } + + const selectedBackgroundColor = getSelectedBackgroundColor( + backgrounds, + selectedBackground, + defaultBackgroundName + ); + + return ( {selectedBackgroundColor ? ( { ( { + links={getDisplayedItems(backgrounds, selectedBackgroundColor, (i) => { this.change(i); onHide(); })} /> )} - closeOnClick > - ) : null; + ); }} ); diff --git a/addons/backgrounds/src/index.ts b/addons/backgrounds/src/index.ts index 9352400fe35c..b9ba57b91818 100644 --- a/addons/backgrounds/src/index.ts +++ b/addons/backgrounds/src/index.ts @@ -1,20 +1,3 @@ -import { makeDecorator, StoryContext, StoryGetter } from '@storybook/addons'; -import deprecate from 'util-deprecate'; - -// This decorator is kept purely so we produce a decorator that is compatible with both -// `addDecorator(withBackgrounds(...))` and `addDecorator(withBackgrounds)` -export const withBackgrounds = deprecate( - makeDecorator({ - name: 'withBackgrounds', - parameterName: 'backgrounds', - wrapper: (getStory: StoryGetter, context: StoryContext) => { - return getStory(context); - }, - }), - `Note that withBackgrounds(options) has been replaced by addParameters({ backgrounds: options}) -Read more about it in the migration guide: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md` -); - if (module && module.hot && module.hot.decline) { module.hot.decline(); } diff --git a/addons/backgrounds/src/register.tsx b/addons/backgrounds/src/register.tsx index bb1aacd75485..dcdd31c6cf87 100644 --- a/addons/backgrounds/src/register.tsx +++ b/addons/backgrounds/src/register.tsx @@ -5,7 +5,7 @@ import { ADDON_ID } from './constants'; import { BackgroundSelector } from './containers/BackgroundSelector'; import { GridSelector } from './containers/GridSelector'; -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, (api) => { addons.add(ADDON_ID, { title: 'Backgrounds', type: types.TOOL, diff --git a/addons/centered/README.md b/addons/centered/README.md deleted file mode 100644 index 8eddd66bbe17..000000000000 --- a/addons/centered/README.md +++ /dev/null @@ -1,211 +0,0 @@ -# Storybook Centered Decorator - -Storybook Centered Decorator can be used to center components inside the preview in [Storybook](https://storybook.js.org). - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -⚠️ This addon applies styling to the view in order to center the component. This may impact the look and feel of story. - -### Usage - -```sh -yarn add @storybook/addon-centered --dev -``` - -You can set the decorator locally. - -example for React: - -```js -import { storiesOf } from '@storybook/react'; -import centered from '@storybook/addon-centered/react'; - -import MyComponent from '../Component'; - -storiesOf('MyComponent', module) - .addDecorator(centered) - .add('without props', () => ()) - .add('with some props', () => ()); -``` - -example for Vue: - -```js -import { storiesOf } from '@storybook/vue'; -import centered from '@storybook/addon-centered/vue'; - -import MyComponent from '../Component.vue'; -storiesOf('MyComponent', module) - .addDecorator(centered) - .add('without props', () => ({ - components: { MyComponent }, - template: '' - })) - .add('with some props', () => ({ - components: { MyComponent }, - template: '' - })); -``` - -example for Preact: - -```js -import { storiesOf } from '@storybook/preact'; -import centered from '@storybook/addon-centered/preact'; - -import MyComponent from '../Component'; - -storiesOf('MyComponent', module) - .addDecorator(centered) - .add('without props', () => ()) - .add('with some props', () => ()); -``` - -example for Svelte: - -```js -import { storiesOf } from '@storybook/svelte'; -import Centered from '@storybook/addon-centered/svelte'; - -import Component from '../Component.svelte'; - -storiesOf('Addon|Centered', module) - .addDecorator(Centered) - .add('rounded', () => ({ - Component, - data: { - rounded: true, - text: "Look, I'm centered!", - }, - })) -``` - -example for Mithril: - -```js -import { storiesOf } from '@storybook/mithril'; -import centered from '@storybook/addon-centered/mithril'; - -import MyComponent from '../Component'; - -storiesOf('MyComponent', module) - .addDecorator(centered) - .add('without props', () => ({ - view: () => - })) - .add('with some props', () => ({ - view: () => - })); -``` - -example for Angular with component: - -```ts -import { storiesOf } from '@storybook/angular'; -import { centered } from '@storybook/addon-centered/angular'; - -import { AppComponent } from '../app/app.component'; - -storiesOf('Addon|Centered', module) - .addDecorator(centered) - .add('centered component', () => ({ - component: AppComponent, - props: {}, - })); - -``` - -example for Angular with template: - -```ts -import { moduleMetadata, storiesOf } from '@storybook/angular'; -import { centered } from '@storybook/addon-centered/angular'; - -import { AppComponent } from '../app/app.component'; - -storiesOf('Addon|Centered', module) - .addDecorator( - moduleMetadata({ - declarations: [Button], - }) - ) - .addDecorator(centered) - .add('centered template', () => ({ - template: ` - `, - props: { - text: 'Hello Button', - onClick: event => { - console.log('some bindings work'); - console.log(event); - }, - }, - })); -``` - -Also, you can also add this decorator globally - -example for React: - -```js -import { configure, addDecorator } from '@storybook/react'; -import centered from '@storybook/addon-centered/react'; - -addDecorator(centered); - -configure(function () { - //... -}, module); -``` - -example for Vue: - -```js -import { configure, addDecorator } from '@storybook/vue'; -import centered from '@storybook/addon-centered/vue'; - -addDecorator(centered); - -configure(function () { - //... -}, module); -``` - -example for Svelte: - -```js -import { configure, addDecorator } from '@storybook/svelte'; -import Centered from '@storybook/addon-centered/svelte'; - -addDecorator(Centered); - -configure(function () { - //... -}, module); -``` - -example for Mithril: - -```js -import { configure, addDecorator } from '@storybook/mithril'; -import centered from '@storybook/addon-centered/mithril'; - -addDecorator(centered); - -configure(function () { - //... -}, module); -``` - -If you don't want to use centered for a story, you can disable it by using `{ disable: true }` to skip the addon: - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -storiesOf('Button', module) - .add('example', () => , { - centered: { disable: true }, - }); -``` diff --git a/addons/centered/angular.d.ts b/addons/centered/angular.d.ts deleted file mode 100644 index 9864b1d2b817..000000000000 --- a/addons/centered/angular.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { StoryFn } from "@storybook/addons"; - -export interface ICollection { - [p: string]: any; -} - -export interface NgModuleMetadata { - declarations?: any[]; - entryComponents?: any[]; - imports?: any[]; - schemas?: any[]; - providers?: any[]; -} - -export interface IStory { - component?: any; - props?: ICollection; - propsMeta?: ICollection; - moduleMetadata?: NgModuleMetadata; - template?: string; - styles?: string[]; -} -declare module '@storybook/addon-centered/angular' { - export function centered(story: StoryFn): IStory; -} diff --git a/addons/centered/angular.js b/addons/centered/angular.js deleted file mode 100644 index 186e1ad93b2a..000000000000 --- a/addons/centered/angular.js +++ /dev/null @@ -1,3 +0,0 @@ -import fromCentered from './dist/angular'; - -export const centered = fromCentered; diff --git a/addons/centered/ember.d.ts b/addons/centered/ember.d.ts deleted file mode 100644 index 21aa4cc429d6..000000000000 --- a/addons/centered/ember.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/ember'; -export default centered; diff --git a/addons/centered/ember.js b/addons/centered/ember.js deleted file mode 100644 index 84275eb46b4b..000000000000 --- a/addons/centered/ember.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/ember'); diff --git a/addons/centered/html.d.ts b/addons/centered/html.d.ts deleted file mode 100644 index d77b06e2f51c..000000000000 --- a/addons/centered/html.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/html'; -export default centered; diff --git a/addons/centered/html.js b/addons/centered/html.js deleted file mode 100644 index 4f7edc6bedba..000000000000 --- a/addons/centered/html.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/html'); diff --git a/addons/centered/mithril.d.ts b/addons/centered/mithril.d.ts deleted file mode 100644 index 1b0025189196..000000000000 --- a/addons/centered/mithril.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/mithril'; -export default centered; diff --git a/addons/centered/mithril.js b/addons/centered/mithril.js deleted file mode 100644 index 884a541476ec..000000000000 --- a/addons/centered/mithril.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/mithril'); diff --git a/addons/centered/package.json b/addons/centered/package.json deleted file mode 100644 index 1857e4873678..000000000000 --- a/addons/centered/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@storybook/addon-centered", - "version": "5.3.0-rc.0", - "description": "Storybook decorator to center components", - "keywords": [ - "addon", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/centered", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/centered" - }, - "license": "MIT", - "author": "Muhammed Thanish ", - "files": [ - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "core-js": "^3.0.1", - "global": "^4.3.2", - "util-deprecate": "^1.0.2" - }, - "devDependencies": { - "@types/mithril": "^1.1.16", - "mithril": "*", - "preact": "*", - "react": "*" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/centered/preact.d.ts b/addons/centered/preact.d.ts deleted file mode 100644 index cae1370b3003..000000000000 --- a/addons/centered/preact.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/preact'; -export default centered; diff --git a/addons/centered/preact.js b/addons/centered/preact.js deleted file mode 100644 index 818efe1776d2..000000000000 --- a/addons/centered/preact.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/preact'); diff --git a/addons/centered/rax.js b/addons/centered/rax.js deleted file mode 100644 index 232893d57a22..000000000000 --- a/addons/centered/rax.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/rax'); diff --git a/addons/centered/react.d.ts b/addons/centered/react.d.ts deleted file mode 100644 index e41e86729f25..000000000000 --- a/addons/centered/react.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/react'; -export default centered; diff --git a/addons/centered/react.js b/addons/centered/react.js deleted file mode 100644 index 70e1111ae070..000000000000 --- a/addons/centered/react.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/react'); diff --git a/addons/centered/src/angular.ts b/addons/centered/src/angular.ts deleted file mode 100644 index a30c418a5341..000000000000 --- a/addons/centered/src/angular.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { makeDecorator, StoryFn } from '@storybook/addons'; -import { IStory } from '../angular.d'; -import parameters from './parameters'; -import styles from './styles'; - -function getComponentSelector(component: any) { - // eslint-disable-next-line no-underscore-dangle - return component.__annotations__[0].selector; -} - -function getTemplate(metadata: any) { - let tpl = ''; - if (metadata.component) { - const selector = getComponentSelector(metadata.component); - tpl = `<${selector}>`; - } - - if (metadata.template) { - tpl = metadata.template; - } - - return ` -
-
- ${tpl} -
-
`; -} - -function getModuleMetadata(metadata: any) { - const { moduleMetadata, component } = metadata; - - if (component && !moduleMetadata) { - return { - declarations: [metadata.component], - }; - } - - if (component && moduleMetadata) { - return { - ...moduleMetadata, - declarations: [...moduleMetadata.declarations, metadata.component], - }; - } - - return moduleMetadata; -} - -function centered(metadataFn: StoryFn) { - const metadata = metadataFn(); - - return { - ...metadata, - template: getTemplate(metadata), - moduleMetadata: getModuleMetadata(metadata), - props: { - ...metadata.props, - styles, - }, - }; -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as StoryFn), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/components/Centered.svelte b/addons/centered/src/components/Centered.svelte deleted file mode 100644 index 287c83ecff34..000000000000 --- a/addons/centered/src/components/Centered.svelte +++ /dev/null @@ -1,10 +0,0 @@ -
-
- -
-
- - diff --git a/addons/centered/src/ember.ts b/addons/centered/src/ember.ts deleted file mode 100644 index 71b311b4d98b..000000000000 --- a/addons/centered/src/ember.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { document } from 'global'; -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered(storyFn: () => { template: any; context: any }) { - const { template, context } = storyFn(); - - const element = document.createElement('div'); - Object.assign(element.style, styles.style); - - const innerElement = document.createElement('div'); - Object.assign(innerElement.style, styles.innerStyle); - - element.appendChild(innerElement); - - // the inner element should append the parent - innerElement.appendTo = function appendTo(el: any) { - el.appendChild(element); - }; - - return { - template, - context, - element: innerElement, - }; -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as any), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/helpers/json2CSS.ts b/addons/centered/src/helpers/json2CSS.ts deleted file mode 100644 index 11e939b15962..000000000000 --- a/addons/centered/src/helpers/json2CSS.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { document } from 'global'; - -/** - * Not all frameworks support an object for the style attribute but we want all to - * consume `styles.json`. Since `styles.json` uses standard style properties for keys, - * we can just set them on an element and then get the string result of that element's - * `style` attribute. This also means that invalid styles are filtered out. - * - * @param {Object} jsonStyles - * @returns {string} - * @see https://stackoverflow.com/questions/38533544/jsx-css-to-inline-styles - */ -export default function jsonToCss(jsonStyles: Partial) { - const frag = document.createElement('div') as HTMLDivElement; - - Object.keys(jsonStyles).forEach(key => { - (frag.style as any)[key] = (jsonStyles as any)[key]; - }); - - return frag.getAttribute('style'); -} diff --git a/addons/centered/src/html.ts b/addons/centered/src/html.ts deleted file mode 100644 index a9eba8810679..000000000000 --- a/addons/centered/src/html.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { document, Node } from 'global'; -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -const INNER_ID = 'sb-addon-centered-inner'; -const WRAPPER_ID = 'sb-addon-centered-wrapper'; - -function getOrCreate(id: string, style: Partial): HTMLDivElement { - const elementOnDom = document.getElementById(id); - - if (elementOnDom) { - return elementOnDom; - } - - const element = document.createElement('div') as HTMLDivElement; - element.setAttribute('id', id); - Object.assign(element.style, style); - - return element; -} - -function getInnerDiv() { - return getOrCreate(INNER_ID, styles.innerStyle); -} - -function getWrapperDiv() { - return getOrCreate(WRAPPER_ID, styles.style); -} - -function centered(storyFn: () => any) { - const inner = getInnerDiv(); - const wrapper = getWrapperDiv(); - wrapper.appendChild(inner); - - const element = storyFn(); - - if (typeof element === 'string') { - inner.innerHTML = element; - } else if (element instanceof Node) { - inner.innerHTML = ''; - inner.appendChild(element); - } else { - return element; - } - - return wrapper; -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as any), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/index.ts b/addons/centered/src/index.ts deleted file mode 100644 index 1bb9f82933ae..000000000000 --- a/addons/centered/src/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { window } from 'global'; -import deprecate from 'util-deprecate'; - -import ReactCentered from './react'; -import VueCentered from './vue'; - -// TODO: REMOVE this entire file in V6.0.0 - -const Centered = deprecate( - () => (window.STORYBOOK_ENV === 'vue' ? VueCentered : ReactCentered), - ` - Using "import centered from '@storybook/addon-centered'" is deprecated. - Please use either: - "import centered from '@storybook/addon-centered/react'" - or - "import centered from '@storybook/addon-centered/vue'" -` -)(); - -export default Centered; - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/mithril.tsx b/addons/centered/src/mithril.tsx deleted file mode 100644 index 5398d7da5a68..000000000000 --- a/addons/centered/src/mithril.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/** @jsx m */ -/* eslint-disable import/no-extraneous-dependencies */ -import m, { ComponentTypes } from 'mithril'; -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered(storyFn: () => ComponentTypes) { - return { - view: () => ( -
-
{m(storyFn())}
-
- ), - }; -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as any), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/parameters.ts b/addons/centered/src/parameters.ts deleted file mode 100644 index 8baf40666645..000000000000 --- a/addons/centered/src/parameters.ts +++ /dev/null @@ -1,6 +0,0 @@ -const parameters = { - name: 'centered', - parameterName: 'centered', -} as const; - -export default parameters; diff --git a/addons/centered/src/preact.tsx b/addons/centered/src/preact.tsx deleted file mode 100644 index ed2175159472..000000000000 --- a/addons/centered/src/preact.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/** @jsx h */ -/* eslint-disable import/no-extraneous-dependencies */ -import { Component, h } from 'preact'; -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered(storyFn: () => Component) { - return ( -
-
{storyFn()}
-
- ); -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as any), -}); diff --git a/addons/centered/src/rax.js b/addons/centered/src/rax.js deleted file mode 100644 index ab3d0c3e2876..000000000000 --- a/addons/centered/src/rax.js +++ /dev/null @@ -1,24 +0,0 @@ -/** @jsx createElement */ -/* eslint-disable import/no-extraneous-dependencies */ -import { createElement } from 'rax'; -import View from 'rax-view'; -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered(storyFn) { - return ( - - {storyFn()} - - ); -} - -export default makeDecorator({ - ...parameters, - wrapper: centered, -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/react.tsx b/addons/centered/src/react.tsx deleted file mode 100644 index f914f3120c7b..000000000000 --- a/addons/centered/src/react.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import React, { ReactNode } from 'react'; -import { makeDecorator, StoryFn } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered(storyFn: () => ReactNode) { - return ( -
-
{storyFn()}
-
- ); -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as StoryFn), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/styles.ts b/addons/centered/src/styles.ts deleted file mode 100644 index 9a8dcf291da4..000000000000 --- a/addons/centered/src/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -const styles = { - style: { - position: 'fixed', - top: '0', - left: '0', - bottom: '0', - right: '0', - display: 'flex', - alignItems: 'center', - overflow: 'auto', - }, - innerStyle: { - margin: 'auto', - maxHeight: '100%', // Hack for centering correctly in IE11 - }, -} as const; - -export default styles; diff --git a/addons/centered/src/svelte.ts b/addons/centered/src/svelte.ts deleted file mode 100644 index 8445ce5a0d4d..000000000000 --- a/addons/centered/src/svelte.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { makeDecorator } from '@storybook/addons'; -import Centered from './components/Centered.svelte'; -import styles from './styles'; -import json2CSS from './helpers/json2CSS'; -import parameters from './parameters'; - -const centeredStyles = { - /** @type {string} */ - style: json2CSS(styles.style), - /** @type {string} */ - innerStyle: json2CSS(styles.innerStyle), -}; - -/** - * This functionality works by passing the svelte story component into another - * svelte component that has the single purpose of centering the story component - * using a wrapper and container. - * - * We use the special element to achieve this. - * - * @see https://svelte.technology/guide#svelte-component - */ -function centered(storyFn: () => any) { - const { Component: OriginalComponent, props, on } = storyFn(); - - return { - Component: OriginalComponent, - props, - on, - Wrapper: Centered, - WrapperData: centeredStyles, - }; -} - -export default makeDecorator({ - ...parameters, - wrapper: getStory => centered(getStory as any), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/src/typings.d.ts b/addons/centered/src/typings.d.ts deleted file mode 100644 index ad116294c0f6..000000000000 --- a/addons/centered/src/typings.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare module 'global'; -declare module '*.svelte'; diff --git a/addons/centered/src/vue.ts b/addons/centered/src/vue.ts deleted file mode 100644 index 180a270ca7af..000000000000 --- a/addons/centered/src/vue.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { makeDecorator } from '@storybook/addons'; -import parameters from './parameters'; -import styles from './styles'; - -function centered() { - return { - template: ` -
-
- -
-
- `, - data() { - return styles; - }, - }; -} - -export default makeDecorator({ - ...parameters, - wrapper: centered, -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/centered/svelte.d.ts b/addons/centered/svelte.d.ts deleted file mode 100644 index d87cbef37cbb..000000000000 --- a/addons/centered/svelte.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/svelte'; -export default centered; diff --git a/addons/centered/svelte.js b/addons/centered/svelte.js deleted file mode 100644 index a7f426b42da8..000000000000 --- a/addons/centered/svelte.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/svelte'); diff --git a/addons/centered/vue.d.ts b/addons/centered/vue.d.ts deleted file mode 100644 index a54367f70b46..000000000000 --- a/addons/centered/vue.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import centered from './dist/vue'; -export default centered; diff --git a/addons/centered/vue.js b/addons/centered/vue.js deleted file mode 100644 index 42311b17563f..000000000000 --- a/addons/centered/vue.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/vue'); diff --git a/addons/contexts/README.md b/addons/contexts/README.md deleted file mode 100644 index 34d98fa61e59..000000000000 --- a/addons/contexts/README.md +++ /dev/null @@ -1,235 +0,0 @@ -# Storybook Addon Contexts - -**Storybook Addon Contexts** is an addon for driving your components under dynamic contexts in -[Storybook](https://storybook.js.org/). - -## 💡 Why you need this? - -Real world users expects your application being customizable, that is why often your components are **polymorphic**: -they need to adapt themselves under different contextual environments. Imagine your components can speak -Chinese, English, or even French, and they change their skin tone under dark or light theme. Yeah, you want to make -sure a component looks great in all scenarios. - -A good practice to write maintainable components is separate the presentation and its business logic. Storybook is -a great place for exercising the visualization and interaction of your components, which may depend on some contexts. -Often enough, you will find it become very tedious to wrap each component deeply with its contextual environments -before you can really write the main story. You even start to write extra components or factory functions just to -make your life easier. How about changing the context of your story dynamically?! There was simply no good way so -you ended up writing stories like an accountant. - -That is why you need this. An elegant way to wrap your component stories and change their contextual environment -directly and dynamically in Storybook UI! Kind of like a dependency injection, eh! The best bit is **you define it -once then apply it everywhere**. - -## ✅ Features - -1. Define a single global file for managing contextual environments (a.k.a. containers) for all of your stories - declaratively. No more repetitive setups or noisy wrapping, making your stories more focused and readable. -2. Support dynamic contextual props switching from Storybook toolbar at runtime. You can slice into - different environments (e.g. languages or themes ) to understand how your component is going to respond. -3. Library agnostic: no presumption on what kind of components you want to wrap around your stories. You can even - use it to bridge with your favorite routing, state-management solutions, or even your own - [React Context](https://reactjs.org/docs/context.html) provider. -4. Offer chainable and granular configurations. It is even possible to fine-tune at per story level. -5. Visual regression friendly. You can use this addon to drive the same story under different contexts to smoke - test important visual states. - -## 🧰 Requirements - -Make sure the version of your Storybook is above v5. For the full list of the current supported frameworks, see -[Addon / Framework Support Table](../../ADDONS_SUPPORT.md). - -## 🎬 Getting started - -To get it started, add this package into your project: - -```bash -yarn add -D @storybook/addon-contexts -``` - -within `.storybook/main.js`: - -```js -module.exports = { - addons: ['@storybook/addon-contexts/register'] -} -``` - -To load your contextual setups for your stories globally, add the following lines into `preview.js` file (you should -see it near your `addon.js` file): - -```js -import { addDecorator } from '@storybook/[framework]'; -import { withContexts } from '@storybook/addon-contexts/[framework]'; -import { contexts } from './configs/contexts'; // we will define the contextual setups later in API section - -addDecorator(withContexts(contexts)); -``` - -Alternatively, like other addons, you can use this addon only for a given set of stories: - -```js -import { withContexts } from '@storybook/addon-contexts/[framework]'; -import { contexts } from './configs/contexts'; - -export default { - title: 'Component With Contexts', - decorators: [withContexts(contexts)], -}; -``` - -Finally, you may want to modify the default setups at per story level. Here is how you can do this: - -```js -export const defaultView = () =>
; -defaultView.story = { - parameters: { - context: [{}] - } -}; -``` - -## ⚙️ Setups - -### Overview - -It is recommended to have a separate file for managing your contextual environment setups. Let's add a file named -`contexts.js` first. Before diving into API details, here is an overview on the landscape. For example (in React), -to inject component theming contexts to both `styled-components` and `material-ui` theme providers in stories: - -```js -export const contexts = [ - { - icon: 'box', // a icon displayed in the Storybook toolbar to control contextual props - title: 'Themes', // an unique name of a contextual environment - components: [ - // an array of components that is going to be injected to wrap stories - /* Styled-components ThemeProvider, */ - /* Material-ui ThemeProvider, */ - ], - params: [ - // an array of params contains a set of predefined `props` for `components` - { name: 'Light Theme', props: { theme /* : your dark theme */ } }, - { name: 'Dark Theme', props: { theme /* : your light theme */ }, default: true }, - ], - options: { - deep: true, // pass the `props` deeply into all wrapping components - disable: false, // disable this contextual environment completely - cancelable: false, // allow this contextual environment to be opt-out optionally in toolbar - }, - }, - /* ... */ // multiple contexts setups are supported -]; -``` - ---- - -### APIs - -#### `withContexts(contexts) : function` - -A decorating function for wrapping your stories under your predefined `contexts`. This means multiple contextual -environments are supported. They are going to be loaded layer by layer and wrapped in a descending oder (top -> down --> story). The `contexts` is an array of objects that should have the following properties: - ---- - -#### `icon : string?` - -(default `undefined`) - -An icon displayed in the Storybook toolbar to control contextual props. This addon allows you to define an icon for -each contextual environment individually. Take a look at the currently supported -[icon lists](https://storybooks-official.netlify.com/?path=/story/basics-icon--labels) from the official Storybook -story. You must define an icon first if you want to take advantage of switching props dynamically in your Storybook -toolbar. - ---- - -#### `title : string` - -(required) - -A unique name of a contextual environment; if duplicate names are provided, the latter is going to be ignored. - ---- - -#### `components : (Component|string)[]` - -(required) - -An array of components that is going to be injected to wrap stories. This means this addon allows multiple wrapping -components to coexist. The wrapping sequence is from the left to right (parent -> children -> story). This nested -wrapping behaviour can be useful in some cases; for instance, in the above example, we are wrapping stories under -`styled-components` and `material-ui` theme providers. Also, you can use this addon to wrap any valid HTML tags. - ---- - -#### `params : object[] | undefined` - -(default: `undefined`) - -An array of params contains a set of predefined `props` for `components`. This object has the following properties: - -#### `params.name : string` - -(required) - -A unique name for representing the props. - -#### `params.props : object | null:` - -(required) - -The `props` that are accepted by the wrapping component(s). - -#### `params.default : true?` - -(default: `undefined`) - -Set to `true` if you want to use this param initially. Only the first one marked as default is identified. - ---- - -#### `options` - -A set of options offers more granular control over the defined contextual environment. These properties can be -overridden at the story level: - -#### `options.deep : boolean?` - -(default: `false`) - -Pass the `props` deeply into all wrapping components. Useful when you want them all to be passed with the same props. - -#### `options.disable : boolean?` - -(default: `false`) - -Disable this contextual environment completely. Useful when you want to opt-out this context from a given story. - -#### `options.cancelable : boolean?` - -(default: `false`) - -Allow this contextual environment to be opt-out optionally in toolbar. When set to `true`, an **Off** option will -be shown at first in the toolbar menu in your Storybook. - -## 📔 Notes - -1. You can use this addon to inject any valid components, that is why `icon` and `params` can be optional. -2. As mentioned, extra contextual environment setups can be added at the story level. Please make sure they are - passed via the second argument as `{ contexts: [{ /* extra contexts */ }}`. -3. Additional `params` can be "appended" into an existing setup at the story level too (make sure it goes with the - correct `title`); however, they are never be able to overridden the default setups. So it is important to have - non-colliding names. -4. The addon will persist the selected params (the addon state) between stories at run-time (similar to other - addons). If the active params were gone after story switching, it falls back to the default then the first. As a - rule of thumb, whenever collisions are possible, the first always wins. -5. Query parameters are supported for pre-selecting contexts param, which comes in handy for visual regression testing. - You can do this by appending `&contexts=[name of contexts]=[name of param]` in the URL under iframe mode. Use `,` - to separate multiple contexts (e.g. `&contexts=Theme=Forests,Language=Fr`). - -## 📖 License - -MIT diff --git a/addons/contexts/package.json b/addons/contexts/package.json deleted file mode 100644 index c9226e028d88..000000000000 --- a/addons/contexts/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@storybook/addon-contexts", - "version": "5.3.0-rc.0", - "description": "Storybook Addon Contexts", - "keywords": [ - "preact", - "react", - "storybook", - "vue" - ], - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/contexts" - }, - "license": "MIT", - "author": "Leo Y. Li", - "files": [ - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/register.js", - "scripts": { - "dev:check-types": "tsc --noEmit", - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "core-js": "^3.0.1", - "global": "^4.3.2", - "qs": "^6.6.0" - }, - "peerDependencies": { - "global": "*", - "preact": "*", - "qs": "*", - "react": "*", - "vue": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/contexts/preact.js b/addons/contexts/preact.js deleted file mode 100644 index 6faa6b4ca5e5..000000000000 --- a/addons/contexts/preact.js +++ /dev/null @@ -1,4 +0,0 @@ -import { withContexts } from './dist/preview/frameworks/preact'; - -export { withContexts }; -export default withContexts; diff --git a/addons/contexts/react.js b/addons/contexts/react.js deleted file mode 100644 index 896dd41d75b0..000000000000 --- a/addons/contexts/react.js +++ /dev/null @@ -1,4 +0,0 @@ -import { withContexts } from './dist/preview/frameworks/react'; - -export { withContexts }; -export default withContexts; diff --git a/addons/contexts/src/index.ts b/addons/contexts/src/index.ts deleted file mode 100644 index 8ad41621ff95..000000000000 --- a/addons/contexts/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { makeDecorator, StoryWrapper } from '@storybook/addons'; -import { ContextsPreviewAPI } from './preview/ContextsPreviewAPI'; -import { ID, PARAM } from './shared/constants'; -import { AddonSetting, AnyFunctionReturns, ContextNode, PropsMap } from './shared/types.d'; - -/** - * This file serves a idiomatic facade of a Storybook decorator. - * - * Wrapper function get called whenever the Storybook rerender the view. This reflow logic is - * framework agnostic; on the other hand, the framework specific bindings are the implementation - * details hidden behind the passed `render` function. - * - * Here, we need a dedicated singleton as a state manager for preview (the addon API, in vanilla) - * who is also knowing how to communicate with the Storybook manager (in React) via the Storybook - * event system. - * - * @param {Render} render - framework specific bindings - */ -export type Render = (...args: [ContextNode[], PropsMap, AnyFunctionReturns]) => T; -type CreateAddonDecorator = (render: Render) => (contexts: AddonSetting[]) => unknown; - -export const createAddonDecorator: CreateAddonDecorator = render => { - const wrapper: StoryWrapper = (getStory, context, settings: any) => { - const { getContextNodes, getSelectionState, getPropsMap } = ContextsPreviewAPI(); - const nodes = getContextNodes(settings); - const state = getSelectionState(); - const props = getPropsMap(nodes, state); - return render(nodes, props, () => getStory(context)); - }; - - return makeDecorator({ - name: ID, - parameterName: PARAM, - skipIfNoParametersOrOptions: true, - allowDeprecatedUsage: false, - wrapper, - }); -}; diff --git a/addons/contexts/src/manager/ContextsManager.tsx b/addons/contexts/src/manager/ContextsManager.tsx deleted file mode 100644 index 2af9ef30aa3e..000000000000 --- a/addons/contexts/src/manager/ContextsManager.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useEffect, useState, useCallback } from 'react'; -import { useChannel } from '@storybook/api'; -import { ToolBar } from './components/ToolBar'; -import { deserialize, serialize } from '../shared/serializers'; -import { PARAM, REBOOT_MANAGER, UPDATE_MANAGER, UPDATE_PREVIEW } from '../shared/constants'; -import { FCNoChildren, ManagerAPI } from '../shared/types.d'; - -/** - * A smart component for handling manager-preview interactions. - */ -type ContextsManager = FCNoChildren<{ - api: ManagerAPI; -}>; - -export const ContextsManager: ContextsManager = ({ api }) => { - const [nodes, setNodes] = useState([]); - const [state, setState] = useState(deserialize(api.getQueryParam(PARAM))); - const setSelected = useCallback( - (nodeId, name) => setState(obj => ({ ...obj, [nodeId]: name })), - [] - ); - - // from preview - const emit = useChannel({ - [UPDATE_MANAGER]: newNodes => setNodes(newNodes || []), - }); - - // to preview - useEffect(() => emit(REBOOT_MANAGER), []); - useEffect(() => emit(UPDATE_PREVIEW, state), [state]); - useEffect(() => api.setQueryParams({ [PARAM]: serialize(state) }), [state]); - - return ; -}; diff --git a/addons/contexts/src/manager/components/ToolBar.test.tsx b/addons/contexts/src/manager/components/ToolBar.test.tsx deleted file mode 100644 index 35caba83f2c6..000000000000 --- a/addons/contexts/src/manager/components/ToolBar.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { ToolBar } from './ToolBar'; - -describe('Tests on addon-contexts component: ToolBar', () => { - it('should render nothing if receive an empty contextNodes', () => { - // when - const result = shallow(); - - // then - expect(result).toMatchInlineSnapshot(`""`); - }); - - it('should spawn ToolBarControl based on the given contextNodes', () => { - // given - const someContextNodes = [ - { - components: ['span'], - icon: 'box' as const, - nodeId: 'Some Context A', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: '', props: {} }], - title: 'Some Context A', - }, - { - components: ['div'], - icon: 'box' as const, - nodeId: 'Some Context B', - options: { cancelable: true, deep: false, disable: false }, - params: [ - { name: 'Some Param X', props: {} }, - { name: 'Some Param Y', props: {} }, - ], - title: 'Some Context B', - }, - ]; - const someSelectionState = { - 'Some Context B': 'Some Param Y', - }; - - // when - const result = shallow( - - ); - - // then - expect(result).toMatchInlineSnapshot(` - - - - - - `); - }); -}); diff --git a/addons/contexts/src/manager/components/ToolBar.tsx b/addons/contexts/src/manager/components/ToolBar.tsx deleted file mode 100644 index 863d36aa9903..000000000000 --- a/addons/contexts/src/manager/components/ToolBar.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { ComponentProps, memo } from 'react'; -import { Separator } from '@storybook/components'; -import { ToolBarControl } from './ToolBarControl'; -import { ContextNode, FCNoChildren, SelectionState } from '../../shared/types.d'; - -type ToolBar = FCNoChildren<{ - nodes: ContextNode[]; - state: SelectionState; - setSelected: ComponentProps['setSelected']; -}>; - -export const ToolBar: ToolBar = memo(({ nodes, state, setSelected }) => - nodes.length ? ( - <> - - {nodes.map(({ components, ...forwardProps }) => ( - - ))} - - ) : null -); diff --git a/addons/contexts/src/manager/components/ToolBarControl.test.tsx b/addons/contexts/src/manager/components/ToolBarControl.test.tsx deleted file mode 100644 index 975f420dc27f..000000000000 --- a/addons/contexts/src/manager/components/ToolBarControl.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { ToolBarControl } from './ToolBarControl'; -import { OPT_OUT } from '../../shared/constants'; - -describe('Tests on addon-contexts component: ToolBarControl', () => { - // given - const someBasicProps = { - icon: 'box' as const, - nodeId: 'Some Context', - options: { cancelable: true, deep: false, disable: false }, - params: [ - { name: 'A', props: {} }, - { name: 'B', props: {} }, - ], - title: 'Some Context', - selected: '', - setSelected: jest.fn, - }; - - it('should control menu: set as inactive if being out-out (if cancelable)', () => { - // when - const result = shallow(); - - // then - expect(result.props().active).toBe(false); - }); - - it('should control menu: valid "selected" to give "activeName"', () => { - // given - const selected = 'C'; - const anotherSelected = 'B'; - - // when - const result = shallow(); - const anotherResult = shallow( - - ); - - // then - expect(result.props().optionsProps.activeName).not.toBe(selected); - expect(anotherResult.props().optionsProps.activeName).toBe(anotherSelected); - }); - - it('should control menu: fallback "activeName" to the default param', () => { - // given - const name = 'C'; - const params = [...someBasicProps.params, { name, props: {}, default: true }]; - - // when - const result = shallow(); - - // then - expect(result.props().optionsProps.activeName).toBe(name); - }); - - it('should control menu: fallback "activeName" to the first (if default not found)', () => { - // when - const result = shallow(); - - // then - expect(result.props().optionsProps.activeName).toBe(someBasicProps.params[0].name); - }); - - it('should render nothing if being disabled', () => { - // given - const options = { ...someBasicProps.options, disable: true }; - - // when - const result = shallow(); - - // then - expect(result).toMatchInlineSnapshot(`""`); - }); - - it('should document the shallowly rendered result', () => { - // when - const result = shallow(); - - // then - expect(result).toMatchInlineSnapshot(` - - `); - }); -}); diff --git a/addons/contexts/src/manager/components/ToolBarControl.tsx b/addons/contexts/src/manager/components/ToolBarControl.tsx deleted file mode 100644 index c6c42b761300..000000000000 --- a/addons/contexts/src/manager/components/ToolBarControl.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react'; -import { ToolBarMenu } from './ToolBarMenu'; -import { OPT_OUT } from '../../shared/constants'; -import { ContextNode, FCNoChildren } from '../../shared/types.d'; - -type ToolBarControl = FCNoChildren< - Omit< - ContextNode & { - selected: string; - setSelected: (nodeId: string, name: string) => void; - }, - 'components' - > ->; - -export const ToolBarControl: ToolBarControl = ({ - nodeId, - icon, - title, - params, - options, - selected, - setSelected, -}) => { - const [expanded, setExpanded] = useState(false); - const paramNames = params.map(({ name }) => name); - const activeName = - // validate the integrity of the selected name - ([...paramNames, options.cancelable && OPT_OUT].includes(selected) && selected) || - // fallback to default - (params.find(param => !!param.default) || { name: null }).name || - // fallback to the first - params[0].name; - const list = options.cancelable ? [OPT_OUT, ...paramNames] : paramNames; - const props = { - title, - active: activeName !== OPT_OUT, - expanded, - setExpanded, - optionsProps: { - activeName, - list, - onSelectOption: (name: string) => () => { - setExpanded(false); - setSelected(nodeId, name); - }, - }, - }; - - return Array.isArray(list) && list.length && !options.disable ? ( - - ) : null; -}; diff --git a/addons/contexts/src/manager/components/ToolBarMenu.test.tsx b/addons/contexts/src/manager/components/ToolBarMenu.test.tsx deleted file mode 100644 index 8eb15db3f65d..000000000000 --- a/addons/contexts/src/manager/components/ToolBarMenu.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { ToolBarMenu } from './ToolBarMenu'; - -describe('Tests on addon-contexts component: ToolBarMenu', () => { - it('should glue `@storybook/ui` components to produce a context menu', () => { - // given - const someProps = { - icon: 'globe' as const, - title: 'Some Context', - active: true, - expanded: false, - setExpanded: jest.fn, - optionsProps: { - activeName: 'A', - list: ['A', 'B'], - onSelectOption: jest.fn, - }, - }; - - // when - const result = shallow(); - - // then - expect(result).toMatchInlineSnapshot(` - - } - tooltipShown={false} - trigger="click" - > - - - - - `); - }); - - it('should render TabButton with title if the icon is given', () => { - // given - const someProps = { - title: 'Some Context', - active: true, - expanded: false, - setExpanded: jest.fn, - optionsProps: { - activeName: 'A', - list: ['A', 'B'], - onSelectOption: jest.fn, - }, - }; - - // when - const result = shallow(); - - // then - expect(result).toMatchInlineSnapshot(` - - } - tooltipShown={false} - trigger="click" - > - - Some Context - - - `); - }); -}); diff --git a/addons/contexts/src/manager/components/ToolBarMenu.tsx b/addons/contexts/src/manager/components/ToolBarMenu.tsx deleted file mode 100644 index dc19a6fccf72..000000000000 --- a/addons/contexts/src/manager/components/ToolBarMenu.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { ComponentProps } from 'react'; -import { Icons, IconButton, WithTooltipPure, TabButton } from '@storybook/components'; -import { ToolBarMenuOptions } from './ToolBarMenuOptions'; -import { ContextNode, FCNoChildren } from '../../shared/types.d'; - -type ToolBarMenu = FCNoChildren<{ - icon?: ComponentProps['icon'] | '' | void; - title: ContextNode['title']; - active: boolean; - expanded: boolean; - setExpanded: (state: boolean) => void; - optionsProps: ComponentProps; -}>; - -export const ToolBarMenu: ToolBarMenu = ({ - icon, - title, - active, - expanded, - setExpanded, - optionsProps, -}) => ( - } - > - {icon ? ( - - - - ) : ( - {title} - )} - -); diff --git a/addons/contexts/src/manager/components/ToolBarMenuOptions.test.tsx b/addons/contexts/src/manager/components/ToolBarMenuOptions.test.tsx deleted file mode 100644 index d22a88153572..000000000000 --- a/addons/contexts/src/manager/components/ToolBarMenuOptions.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { ToolBarMenuOptions } from './ToolBarMenuOptions'; -import { OPT_OUT } from '../../shared/constants'; - -describe('Tests on addon-contexts component: ToolBarMenuOptions', () => { - it('should glue TooltipLinkList and set the active item correspondingly', () => { - // given - const list = [OPT_OUT, 'A', 'B']; - const activeName = 'B'; - - // when - const result = shallow( - - ); - - // then - expect(result.props().links.length).toBe(list.length); - expect(result.props().links.find((link: any) => link.title === activeName).active).toBe(true); - expect(result).toMatchInlineSnapshot(` - - `); - }); -}); diff --git a/addons/contexts/src/manager/components/ToolBarMenuOptions.tsx b/addons/contexts/src/manager/components/ToolBarMenuOptions.tsx deleted file mode 100644 index 1e1f0f3ed378..000000000000 --- a/addons/contexts/src/manager/components/ToolBarMenuOptions.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import { TooltipLinkList } from '@storybook/components'; -import { OPT_OUT } from '../../shared/constants'; -import { FCNoChildren } from '../../shared/types.d'; - -type ToolBarMenuOptions = FCNoChildren<{ - activeName: string; - list: string[]; - onSelectOption: (name: string) => () => void; -}>; - -export const ToolBarMenuOptions: ToolBarMenuOptions = ({ activeName, list, onSelectOption }) => ( - ({ - key: name, - id: name, - title: name !== OPT_OUT ? name : 'Off', - active: name === activeName, - onClick: onSelectOption(name), - }))} - /> -); diff --git a/addons/contexts/src/preview/ContextsPreviewAPI.ts b/addons/contexts/src/preview/ContextsPreviewAPI.ts deleted file mode 100644 index e76639f8323f..000000000000 --- a/addons/contexts/src/preview/ContextsPreviewAPI.ts +++ /dev/null @@ -1,81 +0,0 @@ -import addons from '@storybook/addons'; -import { window } from 'global'; -import { parse } from 'qs'; -import { getContextNodes, getPropsMap, getRendererFrom, singleton } from './libs'; -import { deserialize } from '../shared/serializers'; -import { - PARAM, - REBOOT_MANAGER, - UPDATE_PREVIEW, - UPDATE_MANAGER, - FORCE_RE_RENDER, - SET_CURRENT_STORY, -} from '../shared/constants'; -import { ContextNode, PropsMap, SelectionState } from '../shared/types.d'; - -/** - * A singleton for handling preview-manager and one-time-only side-effects. - */ -export const ContextsPreviewAPI = singleton(() => { - const channel = addons.getChannel(); - let contextsNodesMemo: ContextNode[] | null = null; - let selectionState: SelectionState = {}; - - /** - * URL query param can be used to predetermine the contexts a story should render, - * which is useful for performing image snapshot testing or URL sharing. - */ - if (window && window.location) { - selectionState = deserialize(parse(window.location.search)[PARAM]) || {}; - } - - /** - * (Vue specific) - * Vue will inject getter/setter watchers on the first rendering of the addon, - * which is why we have to keep an internal reference and use `Object.assign` to notify the watcher. - */ - const reactivePropsMap = {}; - const updateReactiveSystem = (propsMap: PropsMap) => Object.assign(reactivePropsMap, propsMap); - - /** - * Preview-manager communications. - */ - // from manager - channel.on(UPDATE_PREVIEW, state => { - if (state) { - selectionState = state; - channel.emit(FORCE_RE_RENDER); - } - }); - channel.on(REBOOT_MANAGER, () => { - channel.emit(UPDATE_MANAGER, contextsNodesMemo); - }); - channel.on(SET_CURRENT_STORY, () => { - // trash the memorization since the story-level setting may change (diffing it is much expensive) - contextsNodesMemo = null; - }); - - // to manager - const getContextNodesWithSideEffects: typeof getContextNodes = (...arg) => { - if (contextsNodesMemo === null) { - contextsNodesMemo = getContextNodes(...arg); - channel.emit(UPDATE_MANAGER, contextsNodesMemo); - } - return contextsNodesMemo; - }; - - /** - * @Public - * Exposed interfaces - */ - return { - // methods get called on Storybook event lifecycle - getContextNodes: getContextNodesWithSideEffects, - getSelectionState: () => selectionState, - getPropsMap, - - // methods for processing framework specific bindings - getRendererFrom, - updateReactiveSystem, - }; -}); diff --git a/addons/contexts/src/preview/frameworks/preact.ts b/addons/contexts/src/preview/frameworks/preact.ts deleted file mode 100644 index ece1de938e6b..000000000000 --- a/addons/contexts/src/preview/frameworks/preact.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { h, VNode } from 'preact'; -import { createAddonDecorator, Render } from '../../index'; -import { ContextsPreviewAPI } from '../ContextsPreviewAPI'; - -/** - * This is the framework specific bindings for Preact. - * '@storybook/preact' expects the returning object from a decorator to be a 'Preact vNode'. - */ -export const renderPreact: Render = (contextNodes, propsMap, getStoryVNode) => { - const { getRendererFrom } = ContextsPreviewAPI(); - return getRendererFrom(h)(contextNodes, propsMap, getStoryVNode); -}; - -export const withContexts = createAddonDecorator(renderPreact); diff --git a/addons/contexts/src/preview/frameworks/react.ts b/addons/contexts/src/preview/frameworks/react.ts deleted file mode 100644 index d2a0458bbccb..000000000000 --- a/addons/contexts/src/preview/frameworks/react.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createElement, ReactElement } from 'react'; -import { createAddonDecorator, Render } from '../../index'; -import { ContextsPreviewAPI } from '../ContextsPreviewAPI'; - -/** - * This is the framework specific bindings for React. - * '@storybook/react' expects the returning object from a decorator to be a 'React Element' (vNode). - */ -export const renderReact: Render = (contextNodes, propsMap, getStoryVNode) => { - const { getRendererFrom } = ContextsPreviewAPI(); - return getRendererFrom(createElement)(contextNodes, propsMap, getStoryVNode); -}; - -export const withContexts = createAddonDecorator(renderReact); diff --git a/addons/contexts/src/preview/frameworks/vue.ts b/addons/contexts/src/preview/frameworks/vue.ts deleted file mode 100644 index 0e5513f85f9b..000000000000 --- a/addons/contexts/src/preview/frameworks/vue.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Vue from 'vue'; -import { createAddonDecorator, Render } from '../../index'; -import { ContextsPreviewAPI } from '../ContextsPreviewAPI'; -import { ID } from '../../shared/constants'; - -/** - * This is the framework specific bindings for Vue. - * '@storybook/vue' expects the returning object from a decorator to be a 'VueComponent'. - */ -export const renderVue: Render = (contextNodes, propsMap, getStoryComponent) => { - const { getRendererFrom, updateReactiveSystem } = ContextsPreviewAPI(); - const reactiveProps = updateReactiveSystem(propsMap); - return Vue.extend({ - name: ID, - data: () => reactiveProps, - render: createElement => - getRendererFrom((Component, props, children) => { - const { key, ref, style, classNames, ...rest } = props || Object(); - const contextData = - Component instanceof Object - ? { key, ref, style, class: classNames, props: rest } // component as a Vue object - : { key, ref, style, class: classNames, attrs: rest }; // component as a HTML tag string - return createElement(Component, contextData, [children]); - })(contextNodes, reactiveProps, () => createElement(getStoryComponent())), - }); -}; - -export const withContexts = createAddonDecorator(renderVue); diff --git a/addons/contexts/src/preview/libs/decorators.test.ts b/addons/contexts/src/preview/libs/decorators.test.ts deleted file mode 100644 index 81250ec6897c..000000000000 --- a/addons/contexts/src/preview/libs/decorators.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { memorize, singleton } from './decorators'; - -describe('Test on functional helpers: memorize', () => { - it('should memorize the calculated result', () => { - // given - const someFn = jest.fn(x => [x]); - const someFnMemo = memorize(someFn); - - // when - const resultA = someFnMemo(1); - const resultB = someFnMemo(2); - const resultC = someFnMemo(1); - - // then - expect(someFn).toHaveBeenCalledTimes(2); - expect(resultA).toEqual(someFn(1)); - expect(resultA).not.toEqual(resultB); - expect(resultA).toBe(resultC); - expect(resultB).not.toEqual(resultC); - }); - - it('should memorize based on the second argument', () => { - // given - const someFn = jest.fn((x, y) => [x, y]); - const someFnMemo = memorize(someFn, (x, y) => y); - - // when - const resultA = someFnMemo(1, 2); - const resultB = someFnMemo(2, 2); - const resultC = someFnMemo(1, 3); - - // then - expect(someFn).toHaveBeenCalledTimes(2); - expect(resultA).toEqual(someFn(1, 2)); - expect(resultA).toBe(resultB); - expect(resultA).not.toEqual(resultC); - expect(resultB).not.toEqual(resultC); - }); -}); - -describe('Test on functional helpers: singleton', () => { - it('should make a function singleton', () => { - // given - const someFn = jest.fn((x, y, z) => [x, y, z]); - const someFnSingleton = singleton(someFn); - - // when - const resultA = someFnSingleton(1, 2, 3); - const resultB = someFnSingleton(4, 5, 6); - const resultC = someFnSingleton(7, 8, 9); - - // then - expect(someFn).toHaveBeenCalledTimes(1); - expect(resultA).toEqual(someFn(1, 2, 3)); - expect(resultA).toBe(resultB); - expect(resultA).toBe(resultC); - expect(resultB).toBe(resultC); - }); -}); diff --git a/addons/contexts/src/preview/libs/decorators.ts b/addons/contexts/src/preview/libs/decorators.ts deleted file mode 100644 index 914e0a8eeacd..000000000000 --- a/addons/contexts/src/preview/libs/decorators.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Memorize the calculated result of a function by an ES6 Map; - * the default is to memorize its the first argument; - * @return the memorized version of a function. - */ -type memorize = ( - fn: (...args: U) => T, - resolver?: (...args: U) => unknown -) => (...args: U) => T; - -export const memorize: memorize = (fn, resolver) => { - const memo = new Map(); - return (...arg) => { - const key = resolver ? resolver(...arg) : arg[0]; - return memo.get(key) || memo.set(key, fn(...arg)).get(key); - }; -}; - -/** - * Enforce a given function can only be executed once; - * the returned value is cached for resolving the subsequent calls. - * @return the singleton version of a function. - */ -type singleton = (fn: (...args: U) => T) => (...args: U) => T; - -export const singleton: singleton = fn => memorize(fn, () => 'singleton'); diff --git a/addons/contexts/src/preview/libs/getContextNodes.test.ts b/addons/contexts/src/preview/libs/getContextNodes.test.ts deleted file mode 100644 index d6bbb23c24e6..000000000000 --- a/addons/contexts/src/preview/libs/getContextNodes.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { _getMergedSettings, getContextNodes } from './getContextNodes'; - -describe('Test on the merging result of a pair of settings', () => { - it('should retain the basic structure even receiving empty objects', () => { - // when - const result = _getMergedSettings({}, {}); - - // then - expect(result).toEqual({ - components: [], - icon: '', - nodeId: '', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: '', props: {} }], - title: '', - }); - }); - - it('should correctly merge two settings', () => { - // given - const someTopLevelSettings = { - icon: 'box' as const, - title: 'Some Context', - components: ['div'], - params: [ - { name: 'T1', props: {} }, - { name: 'T2', props: {} }, - ], - options: { - cancelable: true, - disable: true, - }, - }; - const someStoryLevelSettings = { - icon: 'box' as const, - title: 'Some Context', - components: ['span'], - params: [ - { name: 'S1', props: {} }, - { name: 'S2', props: {} }, - ], - options: { - deep: true, - disable: false, - }, - }; - - // when - const result = _getMergedSettings(someTopLevelSettings, someStoryLevelSettings); - - // then - expect(result).toEqual({ - // topLevel over storyLevel - nodeId: someTopLevelSettings.title, - icon: someTopLevelSettings.icon, - title: someTopLevelSettings.title, - components: someTopLevelSettings.components, - - // storyLevel appends to topLevel - params: [...someTopLevelSettings.params, ...someStoryLevelSettings.params], - - // storyLevel over topLevel - options: { - cancelable: someTopLevelSettings.options.cancelable, - deep: someStoryLevelSettings.options.deep, - disable: someStoryLevelSettings.options.disable, - }, - }); - }); -}); - -describe('Test on reconciliation of settings', () => { - it('should have a stable array ordering after normalization', () => { - // when - const result = getContextNodes({ - // from the topLevel - options: [ - { - icon: 'box', - title: 'Some Context', - components: ['div'], - params: [{ name: 'T1', props: {} }], - }, - { - icon: 'box', - title: 'Another Context', - components: ['div'], - params: [{ name: 'T2', props: {} }], - }, - ], - // from the storyLevel - parameters: [ - { - icon: 'box', - title: 'Other Contexts', - components: ['span'], - params: [{ name: 'S1', props: {} }], - }, - { - icon: 'box', - title: 'Some Context', - components: ['p'], - params: [{ name: 'S2', props: {}, default: true }], - }, - ], - }); - - // then - expect(result).toEqual([ - { - components: ['div'], - icon: 'box', - nodeId: 'Some Context', - options: { cancelable: false, deep: false, disable: false }, - params: [ - { name: 'T1', props: {} }, - { name: 'S2', props: {}, default: true }, - ], - title: 'Some Context', - }, - { - components: ['div'], - icon: 'box', - nodeId: 'Another Context', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'T2', props: {} }], - title: 'Another Context', - }, - { - components: ['span'], - icon: 'box', - nodeId: 'Other Contexts', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'S1', props: {} }], - title: 'Other Contexts', - }, - ]); - }); -}); diff --git a/addons/contexts/src/preview/libs/getContextNodes.ts b/addons/contexts/src/preview/libs/getContextNodes.ts deleted file mode 100644 index 3fa311a81849..000000000000 --- a/addons/contexts/src/preview/libs/getContextNodes.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { AddonSetting, ContextNode, WrapperSettings } from '../../shared/types.d'; - -/** - * @private - * Merge the top-level (global options) and the story-level (parameters) from a pair of setting; - * @return the normalized definition for a contextual environment (i.e. a contextNode). - */ -type _getMergedSettings = ( - topLevel: Partial, - storyLevel: Partial -) => ContextNode; - -export const _getMergedSettings: _getMergedSettings = (topLevel, storyLevel) => ({ - // strip out special characters reserved for serializing - nodeId: (topLevel.title || storyLevel.title || '').replace(/[,+]/g, ''), - icon: topLevel.icon || storyLevel.icon || '', - title: topLevel.title || storyLevel.title || '', - components: topLevel.components || storyLevel.components || [], - params: - topLevel.params || storyLevel.params - ? [...(topLevel.params || []), ...(storyLevel.params || [])].filter(Boolean) - : [{ name: '', props: {} }], - options: { - deep: false, - disable: false, - cancelable: false, - ...topLevel.options, - ...storyLevel.options, - }, -}); - -/** - * @nosideeffects - * Pair up settings for merging normalizations to produce the contextual definitions (i.e. contextNodes); - * it guarantee the adding order can be respected but not duplicated. - */ -type getContextNodes = (settings: WrapperSettings) => ContextNode[]; - -export const getContextNodes: getContextNodes = ({ options, parameters }) => { - const titles = [...(options || []), ...(parameters || [])] - .filter(Boolean) - .map(({ title }) => title); - - return Array.from(new Set(titles)) - .filter(Boolean) - .map(title => - _getMergedSettings( - (options && options.find(option => option.title === title)) || {}, - (parameters && parameters.find(param => param.title === title)) || {} - ) - ); -}; diff --git a/addons/contexts/src/preview/libs/getPropsMap.test.ts b/addons/contexts/src/preview/libs/getPropsMap.test.ts deleted file mode 100644 index 321d926e38b8..000000000000 --- a/addons/contexts/src/preview/libs/getPropsMap.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { _getPropsByParamName, getPropsMap } from './getPropsMap'; -import { OPT_OUT } from '../../shared/constants'; - -describe('Test on behaviors from collecting the propsMap', () => { - const someParams = [ - { name: 'A', props: {} }, - { name: 'B', props: {} }, - ]; - - it('should return "null" when params in 0 length', () => { - const result = _getPropsByParamName([]); - expect(result).toBe(null); - }); - - it('should return "OPT_OUT" token when the context being opted out', () => { - const result = _getPropsByParamName(someParams, OPT_OUT, { cancelable: true }); - expect(result).toBe(OPT_OUT); - }); - - it('should return the props from params when the name existed', () => { - const target = {}; - const result = _getPropsByParamName([...someParams, { name: 'C', props: target }], 'C'); - expect(result).toBe(target); - }); - - it('should otherwise fallback to default props in params for a bad name', () => { - const target = {}; - const result = _getPropsByParamName( - [...someParams, { name: 'C', props: target, default: true }], - 'X' - ); - expect(result).toBe(target); - }); - - it('should otherwise fallback to the first props in params for a bad name, if no marked default props', () => { - const result = _getPropsByParamName(someParams, 'A'); - expect(result).toBe(someParams[0].props); - }); -}); - -describe('Test on the integrity of the method to get the propMaps', () => { - it('should return the correct propsMap from the specified selectionState', () => { - // given - const someContextNodes = [ - { - components: ['div'], - icon: 'box' as const, - nodeId: 'Some Context', - options: { cancelable: false, deep: false, disable: false }, - params: [ - { name: 'A1', props: { a: 1 } }, - { name: 'A2', props: { a: 2 }, default: true }, - ], - title: 'Some Context', - }, - { - components: ['div'], - icon: 'box' as const, - nodeId: 'Another Context', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'B', props: { b: 1 } }], - title: 'Another Context', - }, - { - components: ['span'], - icon: 'box' as const, - nodeId: 'Other Contexts', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'C', props: { c: 1 } }], - title: 'Other Contexts', - }, - ]; - const someSelectionState = { - 'Some Context': 'A1', - 'Another Context': OPT_OUT, // an inconsistent but possible state being introduced via query param - }; - - // when - const result = getPropsMap(someContextNodes, someSelectionState); - - // then - expect(result).toEqual({ - 'Some Context': { a: 1 }, - 'Another Context': { b: 1 }, // not equal to `OPT_OUT` due to the context is not cancelable - 'Other Contexts': { c: 1 }, - }); - }); -}); diff --git a/addons/contexts/src/preview/libs/getPropsMap.ts b/addons/contexts/src/preview/libs/getPropsMap.ts deleted file mode 100644 index 2b3427502cdc..000000000000 --- a/addons/contexts/src/preview/libs/getPropsMap.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { OPT_OUT } from '../../shared/constants'; -import { ContextNode, GenericProp, PropsMap, SelectionState } from '../../shared/types.d'; - -/** - * @private - * Extract the activated props by name from a given contextual params. - */ -type _getPropsByParamName = ( - params: ContextNode['params'], - name?: string, - options?: Partial -) => GenericProp | typeof OPT_OUT; - -export const _getPropsByParamName: _getPropsByParamName = (params, name = '', options = {}) => { - const { props = null } = - // when opt-out context - (options.cancelable && name === OPT_OUT && { props: OPT_OUT }) || - // when menu option get selected - (name && params.find(param => param.name === name)) || - // when being initialized - params.find(param => !!param.default) || - // fallback to the first - params[0] || - // fallback for destructuring - {}; - return props; -}; - -/** - * @nosideeffects - * Collect the propsMap from Nodes based on a controlled state tracker. - */ -type getPropsMap = (contextNodes: ContextNode[], selectionState: SelectionState) => PropsMap; - -export const getPropsMap: getPropsMap = (contextNodes, selectionState) => - contextNodes.reduce((agg, { nodeId, params, options }) => { - // eslint-disable-next-line no-param-reassign - agg[nodeId] = _getPropsByParamName(params, selectionState[nodeId], options); - return agg; - }, Object()); diff --git a/addons/contexts/src/preview/libs/getRendererFrom.test.ts b/addons/contexts/src/preview/libs/getRendererFrom.test.ts deleted file mode 100644 index 210b54c7733d..000000000000 --- a/addons/contexts/src/preview/libs/getRendererFrom.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { _getAggregatedWrap, getRendererFrom } from './getRendererFrom'; -import { OPT_OUT } from '../../shared/constants'; - -// mocks -const h = jest.fn(); -const spiedAggregator = _getAggregatedWrap(h); - -beforeEach(() => { - h.mockReset(); -}); - -// tests -describe('Test on aggregation of a single context', () => { - const fakeTag = 'fakeTag'; - const fakeComponent = () => ''; - - it('should skip wrapping when being set to disable', () => { - // given - const testedProps = {}; - const testedOption = { disable: true }; - - // when - spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)(); - - // then - expect(h).toHaveBeenCalledTimes(0); - }); - - it('should skip wrapping when props is marked as "OPT_OUT"', () => { - // given - const testedProps = OPT_OUT; - const testedOption = { cancelable: true }; - - // when - spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)(); - - // then - expect(h).toHaveBeenCalledTimes(0); - }); - - it('should wrap components in the stacking order', () => { - // given - const testedProps = {}; - const testedOption = {}; - - // when - spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)(); - - // then - expect(h).toHaveBeenCalledTimes(2); - expect(h.mock.calls[0][0]).toBe(fakeComponent); - expect(h.mock.calls[1][0]).toBe(fakeTag); - }); - - it('should NOT pass props deeply by default', () => { - // given - const testedProps = {}; - const testedOption = {}; - - // when - spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)(); - - // then - expect(h.mock.calls[0][1]).toBe(null); - expect(h.mock.calls[1][1]).toBe(testedProps); - }); - - it('should pass props deeply', () => { - const testedProps = {}; - const testedOption = { deep: true }; - spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)(); - expect(h.mock.calls[0][1]).toBe(testedProps); - expect(h.mock.calls[1][1]).toBe(testedProps); - }); -}); - -describe('Test on aggregation of contexts', () => { - it('should aggregate contexts in the stacking order', () => { - // given - const someContextNodes = [ - { - components: ['div'], - icon: 'box' as const, - nodeId: 'Some Context', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'A', props: {} }], - title: 'Some Context', - }, - { - components: ['span'], - icon: 'box' as const, - nodeId: 'Another Context', - options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'B', props: {} }], - title: 'Another Context', - }, - ]; - const propsMap = { - 'Some Context': {}, - 'Another Context': {}, - }; - - // when - getRendererFrom(h)(someContextNodes, propsMap, () => {}); - - // then - expect(h.mock.calls[0][0]).toBe(someContextNodes[1].components[0]); - expect(h.mock.calls[1][0]).toBe(someContextNodes[0].components[0]); - }); -}); diff --git a/addons/contexts/src/preview/libs/getRendererFrom.ts b/addons/contexts/src/preview/libs/getRendererFrom.ts deleted file mode 100644 index 586de6ff19c6..000000000000 --- a/addons/contexts/src/preview/libs/getRendererFrom.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { OPT_OUT } from '../../shared/constants'; -import { - AddonOptions, - AnyFunctionReturns, - ContextNode, - GenericProp, - PropsMap, -} from '../../shared/types.d'; - -/** - * @private - * Aggregate component vNodes with activated props in a descending order, - * based on the given options in the contextual environment setup. - * - * @param {function} h - the associated `createElement` vNode creator from the framework - */ -type _getAggregatedWrap = ( - h: AnyFunctionReturns -) => ( - components: ContextNode['components'], - props: GenericProp | typeof OPT_OUT, - options: AddonOptions -) => AnyFunctionReturns; - -export const _getAggregatedWrap: _getAggregatedWrap = h => ( - components, - props, - options -) => vNode => { - const last = components.length - 1; - const isSkipped = - // when set to disable - options.disable || - // when opt-out context - (options.cancelable && props === OPT_OUT); - - return isSkipped - ? vNode - : components - // shallow clone the array since .reverse() is not pure - .concat() - // reverse the array to get the correct wrapping sequence (i.e. left(right)) - .reverse() - .reduce((acc, C, index) => h(C, options.deep || index === last ? props : null, acc), vNode); -}; - -/** - * @nosideeffects - * Aggregate aggregated-components among all contextual nodes in a descending order; - * this is the core of this addon, which is based on the general virtual DOM implementation. - * - * @param {function} h - the associated `createElement` vNode creator from the framework - */ -type getRendererFrom = ( - h: AnyFunctionReturns -) => (contextNodes: ContextNode[], propsMap: PropsMap, getStoryVNode: AnyFunctionReturns) => T; - -export const getRendererFrom: getRendererFrom = h => (contextNodes, propsMap, getStoryVNode) => - contextNodes - // map over contextual nodes to get the wrapping function - .map(({ nodeId, components, options }) => - _getAggregatedWrap(h)(components, propsMap[nodeId], options) - ) - // reverse the array to get the correct wrapping sequence (i.e. top(down)) - .reverse() - // stitch everything to get the final vNode - .reduce((vNode, wrap) => wrap(vNode), getStoryVNode()); diff --git a/addons/contexts/src/preview/libs/index.ts b/addons/contexts/src/preview/libs/index.ts deleted file mode 100644 index 95230084eeb7..000000000000 --- a/addons/contexts/src/preview/libs/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { memorize, singleton } from './decorators'; -export { getContextNodes } from './getContextNodes'; -export { getPropsMap } from './getPropsMap'; -export { getRendererFrom } from './getRendererFrom'; diff --git a/addons/contexts/src/register.ts b/addons/contexts/src/register.ts deleted file mode 100644 index 69f785b10277..000000000000 --- a/addons/contexts/src/register.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createElement } from 'react'; -import addons, { types } from '@storybook/addons'; -import { ContextsManager } from './manager/ContextsManager'; -import { ID } from './shared/constants'; - -addons.register(ID, api => - addons.add(ID, { - title: ID, - type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', - render: () => createElement(ContextsManager, { api }), - }) -); diff --git a/addons/contexts/src/shared/@mock-types/_preact.d.ts b/addons/contexts/src/shared/@mock-types/_preact.d.ts deleted file mode 100644 index 2b90ddf377ac..000000000000 --- a/addons/contexts/src/shared/@mock-types/_preact.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Preact v8.4.2 shipped with global polluted JSX typing, which breaks the React components typing under Manager - */ -declare module 'preact' { - declare type VNode = any; - declare const h: any = () => {}; -} diff --git a/addons/contexts/src/shared/constants.ts b/addons/contexts/src/shared/constants.ts deleted file mode 100644 index 0e90045a06f6..000000000000 --- a/addons/contexts/src/shared/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -export { FORCE_RE_RENDER, SET_CURRENT_STORY } from '@storybook/core-events'; - -// configs -export const ID = 'addon-contexts' as const; -export const PARAM = 'contexts' as const; - -// tokens -/** - * OPT_OUT is a token for skipping a context, dundering the string to avoid name collisions; - * ES6 Symbol is not available due to stringify used in Storybook event system via the channel. - */ -export const OPT_OUT = '__OPT_OUT__' as const; - -// events -export const REBOOT_MANAGER = `${ID}/REBOOT_MANAGER`; -export const UPDATE_MANAGER = `${ID}/UPDATE_MANAGER`; -export const UPDATE_PREVIEW = `${ID}/UPDATE_PREVIEW`; diff --git a/addons/contexts/src/shared/serializers.test.ts b/addons/contexts/src/shared/serializers.test.ts deleted file mode 100644 index b4cc47a2648f..000000000000 --- a/addons/contexts/src/shared/serializers.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { deserialize, serialize } from './serializers'; - -describe('Test on serializers', () => { - // given - const someContextsQueryParam = 'CSS Themes=Forests,Languages=Fr'; - const someSelectionState = { - 'CSS Themes': 'Forests', - Languages: 'Fr', - }; - - it('Should deserialize a string representation into the represented selection state', () => { - expect(deserialize('')).toEqual(null); - expect(deserialize('An invalid string=')).toEqual(null); - expect(deserialize(someContextsQueryParam)).toEqual(someSelectionState); - }); - - it('Should serialize selection state into its string representation', () => { - expect(serialize(null)).toEqual(null); - expect(serialize(someSelectionState)).toEqual(someContextsQueryParam); - }); -}); diff --git a/addons/contexts/src/shared/serializers.ts b/addons/contexts/src/shared/serializers.ts deleted file mode 100644 index 54b26dde4272..000000000000 --- a/addons/contexts/src/shared/serializers.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { SelectionState } from './types.d'; - -/** - * Deserialize URL query param into the specified selection state. - */ -type deserialize = (param?: string) => SelectionState | null; - -export const deserialize: deserialize = param => - !param - ? null - : param - .split(/,+/g) - .map(str => str.split(/=+/g)) - .reduce( - (acc, [nodeId, name]) => (nodeId && name ? { ...acc, [nodeId]: name } : acc), - null - ); - -/** - * Serialize the selection state in its string representation. - */ -type serialize = (state: ReturnType) => string | null; - -export const serialize: serialize = state => - !state - ? null - : Object.entries(state) - .map(tuple => tuple.join('=')) - .join(','); diff --git a/addons/contexts/src/shared/types.d.ts b/addons/contexts/src/shared/types.d.ts deleted file mode 100644 index a60159fcb1a8..000000000000 --- a/addons/contexts/src/shared/types.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ComponentProps, FunctionComponent } from 'react'; -import { Icons } from '@storybook/components'; - -export { API as ManagerAPI } from '@storybook/api'; - -// helpers -export declare type AnyFunctionReturns = (...arg: any[]) => T; -export declare type FCNoChildren

= FunctionComponent<{ children?: never } & P>; -export declare type GenericProp = null | { - readonly [key: string]: unknown; -}; - -// interfaces -export declare interface AddonOptions { - deep?: boolean; - disable?: boolean; - cancelable?: boolean; -} - -export declare interface AddonSetting { - icon?: ComponentProps['icon'] | ''; - title: string; - components?: unknown[]; - params?: { - name: string; - props: GenericProp; - default?: boolean; - }[]; - options?: AddonOptions; -} - -export declare interface ContextNode extends Required { - nodeId: string; - options: Required; -} - -export declare interface SelectionState { - readonly [key: string]: string | undefined; -} - -export declare interface PropsMap { - readonly [key: string]: GenericProp; -} - -export declare interface WrapperSettings { - options?: AddonSetting[]; - // `parameters` can be set to `false` to disable the addon - parameters?: AddonSetting[] | false; -} diff --git a/addons/contexts/tsconfig.json b/addons/contexts/tsconfig.json deleted file mode 100644 index 9e57fa03aa0e..000000000000 --- a/addons/contexts/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "preact": ["src/shared/@mock-types/_preact.d.ts"] - }, - "strictNullChecks": true, - "removeComments": true, - "rootDir": "./src", - "types": ["webpack-env", "jest"] - }, - "include": ["src/**/*"], - "exclude": ["src/register.ts", "src/**/*.test.ts"] -} diff --git a/addons/contexts/vue.js b/addons/contexts/vue.js deleted file mode 100644 index 474e9fb610c5..000000000000 --- a/addons/contexts/vue.js +++ /dev/null @@ -1,4 +0,0 @@ -import { withContexts } from './dist/preview/frameworks/vue'; - -export { withContexts }; -export default withContexts; diff --git a/addons/controls/README.md b/addons/controls/README.md new file mode 100644 index 000000000000..ef7bd31bfaf7 --- /dev/null +++ b/addons/controls/README.md @@ -0,0 +1,465 @@ +

+ +
+ +

Storybook Controls

+ +Storybook Controls gives you UI to interact with a component's inputs dynamically, without needing to code. It creates an addon panel next to your component examples ("stories"), so you can edit them live. + +It does not require any modification to your components, and stories for controls are: + +- **Convenient.** Auto-generate controls based on [React/Vue/Angular/etc.](#framework-support) components. +- **Portable.** Reuse your interactive stories in documentation, tests, and even in designs. +- **Rich.** Customize the controls and interactive data to suit your exact needs. + +Controls are built on top of [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs), which is an open, standards-based format that enable stories to be reused in a variety of contexts. + +- **Documentation.** 100% compatible with [Storybook Docs](https://github.com/storybookjs/storybook/tree/next/addons/docs). +- **Testing.** Import stories directly into your [Jest](https://jestjs.io/) tests. +- **Ecosystem.** Reuse stories in design/development tools that support it. + +Controls replaces [Storybook Knobs](https://github.com/storybookjs/storybook/tree/master/addons/knobs). It incorporates lessons from years of supporting Knobs on tens of thousands of projects and dozens of different frameworks. We couldn't incrementally fix knobs, so we built a better version. + +

Contents

+ +- [Installation](#installation) +- [Writing stories](#writing-stories) + - [Getting started](#getting-started) + - [Auto-generated args](#auto-generated-args) + - [Custom controls args](#custom-controls-args) + - [Fully custom args](#fully-custom-args) + - [Template stories](#template-stories) +- [Configuration](#configuration) + - [Control annotations](#control-annotations) + - [Parameters](#parameters) + - [Expanded: show property documentation](#expanded-show-property-documentation) +- [Framework support](#framework-support) +- [FAQs](#faqs) + - [How will this replace addon-knobs?](#how-will-this-replace-addon-knobs) + - [How do I migrate from addon-knobs?](#how-do-i-migrate-from-addon-knobs) + +## Installation + +Controls requires [Storybook Docs](https://github.com/storybookjs/storybook/tree/next/addons/docs). If you're not using it already, please install that first. + +Next, install the package: + +```sh +npm install @storybook/addon-controls -D # or yarn +``` + +And add it to your `.storybook/main.js` config: + +```js +module.exports = { + addons: [ + '@storybook/addon-docs' + '@storybook/addon-controls' + ], +}; +``` + +## Writing stories + +Let's see how to write stories that automatically generate controls based on your component properties. + +Controls is built on [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs), which is a small, backwards-compatible change to Storybook's [Component Story Format](https://medium.com/storybookjs/component-story-format-66f4c32366df). + +This section is a step-by-step walkthrough for how to upgrade your stories. It takes you from a starting point of the traditional "no args" stories, to auto-generated args, to auto-generated args with custom controls, to fully custom args if you need them. + +### Getting started + +Let's start with the following component/story combination, which should look familiar if you're coming from an older version of Storybook. + +```tsx +import React from 'react'; +interface ButtonProps { + /** The main label of the button */ + label?: string; +} +export const Button = ({ label = 'FIXME' }: ButtonProps) => ; +``` + +And here's a story that shows that Button component: + +```jsx +import React from 'react'; +import { Button } from './Button'; +export default { title: 'Button', component: Button }; + +export const Basic = () => +); +``` + +And the slightly expanded story: + +```jsx +export const Basic = (args) => ` + } +); +Reflow.args = { count: 3, label: 'reflow' }; +``` + +### Template stories + +Suppose you've created the `Basic` story from above, but now we want to create a second story with a different state, such as how the button renders with the label is really long. + +The simplest thing would be to create a second story: + +```jsx +export const VeryLongLabel = (args) => -basic.story = { - parameters: { docs: { page: null } } +basic.parameters = { + docs: { page: null } } ``` +### Remixing DocsPage using doc blocks + +Here's an example of rebuilding `DocsPage` out of doc blocks: + +```js +import React from 'react'; +import { + Title, + Subtitle, + Description, + Primary, + Props, + Stories, +} from '@storybook/addon-docs/blocks'; +import { DocgenButton } from '../../components/DocgenButton'; + +export default { + title: 'Addons/Docs/stories docs blocks', + component: DocgenButton, + parameters: { + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Props /> + <Stories /> + </> + ), + }, + }, +}; +``` + +You can interleave your own components to customize the auto-generated contents of the page, or pass in different options to the blocks to customize their appearance. For more info see the examples in [official-storybook](https://github.com/storybookjs/storybook/blob/next/examples/official-storybook/stories/addon-docs/addon-docs-blocks.stories.js). + ## Story file names -Unless you use a custom webpack configuration, all of your story files should have the suffix `*.stories.[jt]sx?`, e.g. `"Badge.stories.js"`, `"Badge.stories.tsx"`, etc. +Unless you use a custom webpack configuration, all of your story files should have the suffix `*.stories.@(j|t)sx?`, e.g. `"Badge.stories.js"`, `"Badge.stories.tsx"`, etc. The docs preset assumes this naming convention for its `source-loader` setup. If you want to use a different naming convention, you'll need a [manual configuration](../README.md#manual-configuration). @@ -274,7 +173,7 @@ import { addParameters } from '@storybook/vue'; addParameters({ docs: { - prepareForInline: storyFn => { + prepareForInline: (storyFn) => { const Story = toReact(storyFn()); return <Story />; }, @@ -286,10 +185,7 @@ With that function, anyone using the docs addon for `@storybook/vue` can make th ## More resources -Want to learn more? Here are some more articles on Storybook Docs: - -- References: [README](../README.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) -- [Technical preview guide](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) diff --git a/addons/docs/docs/faq.md b/addons/docs/docs/faq.md index 871d80643a01..8030502baf12 100644 --- a/addons/docs/docs/faq.md +++ b/addons/docs/docs/faq.md @@ -1,16 +1,15 @@ -# Storybook Docs FAQs +<h1>Storybook Docs FAQs</h1> You've read the [Storybook Docs README](../README.md). You're already familiar with both [DocsPage](./docspage.md) and [MDX](./mdx.md). You've even browsed our [Docs recipes](/./recipes.md). But Docs is a big project and you've still got questions! Maybe you'll find your answer here: -- [Storybook Docs FAQs](#storybook-docs-faqs) - - [Does Docs support framework X?](#does-docs-support-framework-x) - - [How does Docs interact with existing addons?](#how-does-docs-interact-with-existing-addons) - - [How do I debug my MDX story?](#how-do-i-debug-my-mdx-story) - - [More resources](#more-resources) +- [Does Docs support framework X?](#does-docs-support-framework-x) +- [How does Docs interact with existing addons?](#how-does-docs-interact-with-existing-addons) +- [How do I debug my MDX story?](#how-do-i-debug-my-mdx-story) +- [More resources](#more-resources) ## Does Docs support framework X? -Docs does not currently support [React Native](https://github.com/storybooks/storybook/tree/next/app/react-native). Otherwise, [it supports all frameworks that Storybook supports](../README.md#framework-support), including React, Vue, Angular, Ember, Svelte, Polymer, and others. +Docs does not currently support [React Native](https://github.com/storybooks/storybook/tree/next/app/react-native). Otherwise, [it supports all frameworks that Storybook supports](../README.md#framework-support), including React, Vue, Angular, Ember, Svelte, and others. ## How does Docs interact with existing addons? @@ -19,7 +18,7 @@ Currently we hide the addons panel when docs is visible. It's tricky because all ## How do I debug my MDX story? <center> - <img src="./media/faq-debug.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/faq-debug.png" width="100%" /> </center> > "My story renders in docs, but doesn’t show up the way I’d expect in the Canvas” @@ -37,17 +36,14 @@ For example, the following MDX story: Shows up in the dev tools as follows: <center> - <img src="./media/faq-devtools.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/faq-devtools.png" width="100%" /> </center> This is [Component Story Format (CSF)](https://medium.com/storybookjs/component-story-format-66f4c32366df), so there are ways to debug. You can copy and paste this code into a new `.stories.js` file and play around with it at a lower level to understand what's going wrong. ## More resources -Want to learn more? Here are some more articles on Storybook Docs: - -- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [Recipes](recipes.md) / [Theming](theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) -- [Technical preview guide](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) diff --git a/addons/docs/docs/mdx.md b/addons/docs/docs/mdx.md index c3dc2c570015..dc19051f3b33 100644 --- a/addons/docs/docs/mdx.md +++ b/addons/docs/docs/mdx.md @@ -1,10 +1,8 @@ <center> - <img src="./media/mdx-hero.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-hero.png" width="100%" /> </center> -# Storybook Docs MDX - -> ⚠️ MDX support is an experimental feature in Storybook 5.2. The API may change in 5.3 outside of the normal semver rules. Be forewarned! +<h1>Storybook Docs MDX</h1> `MDX` is the syntax [Storybook Docs](../README.md) uses to capture long-form markdown documentation and stories in one file. You can also write pure documentation pages in `MDX` and add them to Storybook alongside your stories. @@ -46,7 +44,7 @@ markdown documentation. And here's how that's rendered in Storybook: <center> - <img src="./media/mdx-simple.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-simple.png" width="100%" /> </center> As you can see there's a lot going on here. We're writing Markdown, we're writing JSX, and somehow we're also defining Storybook stories that are drop-in compatible with the entire Storybook ecosystem. @@ -130,7 +128,7 @@ with unique URLs and isolated snapshot tests. And here's how that gets rendered in Storybook: <center> - <img src="./media/mdx-page.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-page.png" width="100%" /> </center> ## Embedding stories @@ -185,7 +183,7 @@ If you don't define a `Meta`, you can write Markdown and associate with an exist To get a "documentation-only story", in your UI, define a `<Meta>` as you normally would, but don't define any stories. It will show up in your UI as a documentation node: <center> - <img src="./media/mdx-documentation-only.png" width="100%" /> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-documentation-only.png" width="100%" /> </center> ## MDX file names @@ -196,10 +194,7 @@ Be sure to update your Storybook config file to load `.stories.mdx` stories, as ## More resources -`MDX` is an experimental feature and there's a lot more that hasn't been documented yet. Here are some more articles on Storybook Docs that contain more information: - -- References: [README](../README.md) / [DocsPage](docspage.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) -- [Technical preview guide](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) diff --git a/addons/docs/docs/media/args-controls.gif b/addons/docs/docs/media/args-controls.gif new file mode 100644 index 000000000000..c60fa68c745f Binary files /dev/null and b/addons/docs/docs/media/args-controls.gif differ diff --git a/addons/docs/docs/media/props-tables-hero.png b/addons/docs/docs/media/props-tables-hero.png new file mode 100644 index 000000000000..e1ac1decb36d Binary files /dev/null and b/addons/docs/docs/media/props-tables-hero.png differ diff --git a/addons/docs/docs/multiframework.md b/addons/docs/docs/multiframework.md index 074aac0b1106..75c56e2a7114 100644 --- a/addons/docs/docs/multiframework.md +++ b/addons/docs/docs/multiframework.md @@ -1,4 +1,4 @@ -# Storybook Docs framework dev guide +<h1>Storybook Docs framework dev guide</h1> Storybook Docs [provides basic support for all non-RN Storybook view layers](../README.md#framework-support) out of the box. However, some frameworks have been docs-optimized, adding features like automatic props table generation and inline story rendering. This document is a dev guide for how to optimize a new framework in docs. @@ -6,6 +6,7 @@ Storybook Docs [provides basic support for all non-RN Storybook view layers](../ - [Props tables](#props-tables) - [Component descriptions](#component-descriptions) - [Inline story rendering](#inline-story-rendering) +- [More resources](#more-resources) ## Framework-specific configuration @@ -87,7 +88,7 @@ import toReact from '@egoist/vue-to-react'; addParameters({ docs: { // `container`, `page`, etc. here - prepareForInline: storyFn => { + prepareForInline: (storyFn) => { const Story = toReact(storyFn()); return <Story />; }, @@ -96,3 +97,10 @@ addParameters({ ``` The input is the story function, and the output is a React element, because we render docs pages in react. In the case of Vue, all of the work is done by the `@egoist/vue-to-react` library. If there's no analogous library for your framework, you may need to figure it out yourself! + +## More resources + +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) +- Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/docs/props-tables.md b/addons/docs/docs/props-tables.md new file mode 100644 index 000000000000..4bd204befa44 --- /dev/null +++ b/addons/docs/docs/props-tables.md @@ -0,0 +1,236 @@ +<center> + <img src="./media/props-tables-hero.png" width="100%" /> +</center> + +<h1>Storybook Docs Props Tables</h1> + +Storybook Docs automatically generates props tables for components in supported frameworks. This document is a consolidated summary of prop tables, provides instructions for reporting bugs, and list known limitations for each framework. + +- [Usage](#usage) + - [DocsPage](#docspage) + - [MDX](#mdx) +- [Controls](#controls) +- [Customization](#customization) + - [Customizing ArgTypes](#customizing-argtypes) +- [Reporting a bug](#reporting-a-bug) +- [Known limitations](#known-limitations) +- [More resources](#more-resources) + +## Usage + +For framework-specific setup instructions, see the framework's README: [React](../react/README.md), [Vue](../vue/README.md), [Angular](../angular/README.md), [Web Components](../web-components/README.md), [Ember](../ember/README.md). + +### DocsPage + +To use the props table in [DocsPage](./docspage.md), simply export a component property on your stories metadata: + +```js +// MyComponent.stories.js +import { MyComponent } from './MyComponent'; + +export default { + title: 'MyComponent', + component: MyComponent, +}; +// stories etc... +``` + +### MDX + +To use the props table in [MDX](./mdx.md), use the `Props` block: + +```js +// MyComponent.stories.mdx +import { Props } from '@storybook/addon-docs/blocks'; +import { MyComponent } from './MyComponent'; + +# My Component! + +<Props of={MyComponent} /> +``` + +## Controls + +Starting in SB 6.0, the `Props` block has built-in `Controls` (formerly known as "knobs") for editing stories dynamically. + +<center> + <img src="./media/args-controls.gif" width="80%" /> +</center> + +<br/> + +These controls are implemented appear automatically in the props table when your story accepts [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs) as its input. This is done slightly differently depending on whether you're using `DocsPage` or `MDX`. + +**DocsPage.** In [DocsPage](./docspage.md), simply write your story to consume args and the auto-generated props table will display controls in the right-most column: + +```js +export default { + title: 'MyComponent', + component: MyComponent, +}; + +export const WithControls = (args) => <MyComponent {...args} />; +``` + +**MDX.** In [MDX](./mdx.md), the `Props` controls are more configurable than in DocsPage. In order to show controls, `Props` must be a function of a story, not a component: + +```js +<Story name="WithControls"> + {args => <MyComponent {...args} />} +</Story> + +<Props story="Controls" /> +``` + +For a very detailed walkthrough of how to write stories that use controls, see the [addon-controls README](https://github.com/storybookjs/storybook/blob/next/addons/controls/README.md#writing-stories). + +## Customization + +Props tables are automatically inferred from your components and stories, but sometimes it's useful to customize the results. + +Props tables are rendered from an internal data structure called `ArgTypes`. When you declare a story's `component` metadata, Docs automatically extracts `ArgTypes` based on the component's properties. + +You can can customize what's shown in the props table by [customizing the `ArgTypes` data](#customizing-argtypes). This is currently available for `DocsPage` and `<Props story="xxx">` construct, but not for the `<Props of={component} />` construct, + +### Customizing ArgTypes + +> **NOTE:** This API is experimental and may change outside of the typical semver release cycle + +When you declare a `component` in for your `DocsPage` [as described above](#docspage) or use the `<Props story="xxx" />` construct [in MDX](#controls), the props table shows the `story.argTypes` that gets extracted by Storybook. + +Consider the following input: + +```js +// Button.js +import React from 'react'; +import PropTypes from 'prop-types'; +export const Button = ({ label }) => <button>{label}</button>; +Button.propTypes = { + /** demo description */ + label: PropTypes.string, +}; +Button.defaultProps = { + label: 'Hello', +}; + +// Button.stories.js +export default { title: 'Button', component: Button }; +``` + +This generates the equivalent of following in-memory data structure for the `Button` component: + +```js +const argTypes = { + label: { + name: 'label', + type: { name: 'string', required: false }, + defaultValue: 'Hello', + description: 'demo description', + table: { + type: { summary: 'string' }, + defaultValue: { summary: 'Hello' }, + } + control: { + type: 'text' + } + } +} +``` + +In this `ArgTypes` data structure, `name`, `type`, `defaultValue`, and `description` are standard fields in all `ArgTypes` (analogous to `PropTypes` in React). The `table` and `control` fields are addon-specific annotations. So, for example, the `table` annotation provides extra information to customize how `label` gets rendered, and the `control` annotation provides extra information for the control for editing the property. + +As a user, you can customize the prop table by selectively overriding these values. Consider the following modification to `Button.stories.js` from above: + +```js +export default { + title: 'Button', + component: Button, + argTypes: { + label: { + description: 'overwritten description', + table: { + type: { summary: 'something short' detail: 'something really really long' }, + }, + control: { + type: null + } + } + } +}; +``` + +These values--`description`, `table.type`, and `controls.type`--get merged over the defaults that are extracted by Storybook. The final merged values would be: + +```js +const argTypes = { + label: { + name: 'label', + type: { name: 'string', required: false }, + defaultValue: 'Hello', + description: 'overwritten description', + table: { + type: { summary: 'something short' detail: 'something really really long' }, + defaultValue: { summary: 'Hello' }, + } + control: { + type: null + } + } +} +``` + +This would render a row with a modified description, a type display with a dropdown that shows the detail, and no control. + +Controls customization has an entire section in the [`addon-controls` README](https://github.com/storybookjs/storybook/blob/next/addons/controls/README.md#configuration). + +Here are the possible customizations for the rest of the prop table: + +| Field | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `name` | The name of the property | +| `type.required` | Whether or not the property is required | +| `description` | A markdown description for the property | +| `table.type.summary` | A short version of the type | +| `table.type.detail` | A longer version of the type (if it's a complex type) | +| `table.defaultValue.summary` | A short version of the default value | +| `table.defaultValue.detail` | A longer version of the default value (if it's a complex value) | +| `control` | See [`addon-controls` README](https://github.com/storybookjs/storybook/blob/next/addons/controls/README.md#configuration) | + +## Reporting a bug + +Extracting component properties from source is a tricky problem with thousands of corner cases. We've designed this package and its tests to accurately isolate problems, since the cause could either be in this package or (likely) one of the packages it depends on. + +If you're seeing a problem with your prop table, here's what to do. + +First, look to see if there's already a test case that corresponds to your situation. If there is, it should be documented in the [Known Limitations](#known-limitations) section below. There should also be one or more corresponding test fixtures contained in this package. For example, if you are using React, look under the directory `./src/frameworks/react/__testfixtures__`. + +If your problem is not already represented here, do the following: + +1. Create a **MINIMAL** repro for your problem. Each case should be just a few lines of code. +2. Place it in the appropriate directory `./src/frameworks/<framework>/__testfixtures__/`, e.g. `./src/frameworks/react/__testfixtures__/XXXX-some-description`, where `XXXX` is the corresponding github issue. +3. Run the tests for your `<framework>`, e.g. `yarn jest --testPathPattern=react-properties.test.ts --watch` +4. Inspect the output files for your test case. +5. Add the example to the appropriate stories file, e.g. `react-properties.stories.ts` for `react`, for a visual repro + +If the problem appears to be an issue with this library, file an issue and include a PR that includes your repro. + +If the problem appears to be an issue with the sub-package, please file an issue on the appropriate sub-package, document the limitation in [Known Limitations](#known-limitations) below, link to that issue, and submit a PR including the updated documentation and fixtures/snapshots. + +## Known limitations + +This package relies on a variety of sub-packages to extract property information from components. Many of the bugs in this package correspond to bugs in a sub-package. Since we don't maintain the sub-packages, the best we can do for now is (1) document these limitations, (2) provide clean reproductions to the sub-package, (3) optionally provide PRs to those packages to fix the problems. + +| Framework | Underlying library | Docs | Open issues | +| -------------- | ---------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| React | `react-docgen` `react-docgen-typescript` | [Docs](../react/README.md#props-tables) | [Open issues](https://github.com/storybookjs/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22block%3A+props%22+label%3Abug+label%3A%22app%3A+react%22) | +| Vue | `vue-docgen-api` | [Docs](../vue/README.md#props-tables) | [Open issues](https://github.com/storybookjs/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22block%3A+props%22+label%3Abug+label%3A%22app%3A+vue%22) | +| Angular | `compodoc` | [Docs](../angular/README.md#props-tables) | [Open issues](https://github.com/storybookjs/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22block%3A+props%22+label%3Abug+label%3A%22app%3A+angular%22) | +| Web-components | `custom-elements.json` | [Docs](../web-components/README.md#props-tables) | [Open issues](https://github.com/storybookjs/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22block%3A+props%22+label%3Abug+label%3A%22app%3A+web-components%22) | +| Ember | `yui-doc` | [Docs](../ember/README.md#props-tables) | [Open issues](https://github.com/storybookjs/storybook/issues?q=is%3Aopen+is%3Aissue+label%3A%22block%3A+props%22+label%3Abug+label%3A%22app%3A+ember%22) | + +## More resources + +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) +- Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/docs/recipes.md b/addons/docs/docs/recipes.md index 427b11a5eb91..5ee6a4ee1c5d 100644 --- a/addons/docs/docs/recipes.md +++ b/addons/docs/docs/recipes.md @@ -1,4 +1,4 @@ -# Storybook Docs Recipes +<h1>Storybook Docs Recipes</h1> [Storybook Docs](../README.md) consists of two basic mechanisms, [DocsPage](docspage.md) and [MDX](mdx.md). But how should you use them in your project? @@ -13,6 +13,9 @@ - [Disabling docs stories](#disabling-docs-stories) - [DocsPage](#docspage) - [MDX Stories](#mdx-stories) +- [Controlling a story's view mode](#controlling-a-storys-view-mode) +- [Customizing source snippets](#customizing-source-snippets) +- [Overwriting docs container](#overwriting-docs-container) - [More resources](#more-resources) ## Component Story Format (CSF) with DocsPage @@ -48,8 +51,8 @@ export default { }; export const basic = () => <Button>Basic</Button>; -basic.story = { - parameters: { foo: 'bar' }, +basic.parameters = { + foo: 'bar', }; ``` @@ -57,7 +60,7 @@ basic.story = { ```md import { Meta, Story } from '@storybook/addon-docs/blocks'; -import * as stories from './Button.stories.js'; +import \* as stories from './Button.stories.js'; import { SomeComponent } from 'path/to/SomeComponent'; <Meta title="Demo/Button" component={Button} /> @@ -76,9 +79,7 @@ And I can also embed arbitrary markdown & JSX in this file. What's happening here: - Your stories are defined in CSF, but because of `includeStories: []`, they are not actually added to Storybook. -- The MDX file is adding the stories to Storybook, and using the story function defined in CSF. -- The MDX loader is using story metadata from CSF, such as name, decorators, parameters, but will give giving preference to anything defined in the MDX file. -- The MDX file is using the Meta `default` defined in the CSF. +- The MDX file is simply importing stories as functions in the MDX, and other aspects of the CSF file, such as decorators, parameters, and any other metadata should be applied as needed in the MDX from the import. ## CSF Stories with arbitrary MDX @@ -138,8 +139,8 @@ const loadFn = () => { const req = require.context('../src', true, /\.stories\.js$/); return req .keys() - .map(fname => req(fname)) - .filter(exp => !!exp.default); + .map((fname) => req(fname)) + .filter((exp) => !!exp.default); }; configure(loadFn, module); @@ -198,7 +199,7 @@ User defines stories in CSF and renders docs using DocsPage, but wishes to exclu ```js export const foo = () => <Button>foo</Button>; -foo.story = { parameters: { docs: { disable: true } } }; +foo.parameters = { docs: { disable: true } }; ``` ### MDX Stories @@ -206,17 +207,122 @@ foo.story = { parameters: { docs: { disable: true } } }; User writes documentation & stories side-by-side in a single MDX file, and wants those stories to show up in the canvas but not in the docs themselves. They want something similar to the recipe "CSF stories with MDX docs" but want to do everything in MDX: ```js -<Story name="foo" parameters={{ docs: { disable: true }} > +<Story name="foo" parameters={{ docs: { disable: true } }}> <Button>foo</Button> </Story> ``` -## More resources +## Controlling a story's view mode + +Storybook's default story navigation behavior is to preserve the existing view mode. In other words, if a user is viewing a story in "docs" mode, and clicks on another story, they will navigate to the other story in "docs" mode. If they are viewing a story in "story" mode (i.e. "canvas" in the UI) they will navigate to another story in "story" mode (with the exception of "docs-only" pages, which are always shown in "docs" mode). + +Based on user feedback, it's also possible to control the view mode for an individual story using the `viewMode` story parameter. In the following example, the nav link will always set the view mode to story: + +```js +export const Foo = () => <Component />; +Foo.parameters = { + // reset the view mode to "story" whenever the user navigates to this story + viewMode: 'story', +}; +``` + +This can also be applied globally in `preview.js`: + +```js +// always reset the view mode to "docs" whenever the user navigates +addParameters({ + viewMode: 'docs', +}); +``` -Want to learn more? Here are some more articles on Storybook Docs: +## Customizing source snippets + +As of SB 6.0, there are two ways to customize how Docs renders source code, via story parameter or via a formatting function. + +If you override the `docs.source.code` parameter, the `Source` block will render whatever string is added: + +```js +const Example = () => <Button />; +Example.parameters = { + docs: { source: { code: 'some arbitrary string' } }, +}; +``` + +Alternatively, you can provide a function in the `docs.transformSource` parameter. For example, the following snippet in `.storybook/preview.js` globally removes the arrow at the beginning of a function that returns a string: + +```js +const SOURCE_REGEX = /^\(\) => `(.*)`$/; +export const parameters = { + docs: { + transformSource: (src, storyId) => { + const match = SOURCE_REGEX.exec(src); + return match ? match[1] : src; + }, + }, +}; +``` + +These two methods are complementary. The former is useful for story-specific, and the latter is useful for global formatting. + +## Overwriting docs container + +What happens if you want to add some wrapper for your MDX page, or add some other kind of React context? + +When you're writing stories you can do this by adding a [decorator](https://storybook.js.org/docs/basics/writing-stories/#decorators), but when you're adding arbitrary JSX to your MDX documentation outside of a `<Story>` block, decorators no longer apply, and you need to use the `docs.container` parameter. + +The closest Docs equivalent of a decorator is the `container`, a wrapper element that is rendered around the page that is being rendered. Here's an example of adding a solid red border around the page. It uses Storybook's default page container (that sets up various contexts and other magic) and then inserts its own logic between that container and the contents of the page: + +```js +import { Meta, DocsContainer } from '@storybook/addon-docs/blocks'; + +<Meta + title="Addons/Docs/container-override" + parameters={{ + docs: { + container: ({ children, context }) => ( + <DocsContainer context={context}> + <div style={{ border: '5px solid red' }}>{children}</div> + </DocsContainer> + ), + }, + }} +/> + +# Title + +Rest of your file... +``` + +This is especially useful if you are using `styled-components` and need to wrap your JSX with a `ThemeProvider` to have access to your theme: + +```js +import { Meta, DocsContainer } from '@storybook/addon-docs/blocks'; +import { ThemeProvider } from 'styled-components' +import { theme } from '../path/to/theme' + +<Meta + title="Addons/Docs/container-override" + parameters={{ + docs: { + container: ({ children, context }) => ( + <DocsContainer context={context}> + <ThemeProvider theme={theme}> + {children} + </ThemeProvider> + </DocsContainer> + ), + }, + }} +/> + +# Title + +Rest of your file... +``` + +## More resources -- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Theming](theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) -- [Technical preview guide](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) diff --git a/addons/docs/docs/theming.md b/addons/docs/docs/theming.md index 23c12ce159bd..c691726d8e26 100644 --- a/addons/docs/docs/theming.md +++ b/addons/docs/docs/theming.md @@ -1,26 +1,39 @@ -# Storybook Docs Theming +<h1>Storybook Docs Theming</h1> [Storybook Docs](../README.md) is themable! There are three different levels of theming, just to keep things interesting: - [Storybook theming](#storybook-theming) - [CSS escape hatches](#css-escape-hatches) - [MDX component overrides](#mdx-component-overrides) +- [More resources](#more-resources) ## Storybook theming -Storybook theming is the **recommended way** to theme your docs. If you update your storybook theme according to [the documentation](https://storybook.js.org/docs/configurations/theming/), Storybook Docs should adapt in reasonable ways. +Storybook theming is the **recommended way** to theme your docs. Docs uses the same theme system as [Storybook UI](https://storybook.js.org/docs/configurations/theming/), but is themed independently from the main UI. -For example, here's how to change your docs (and Storybook) to the dark theme, by modifying `.storybook/preview.js`: +Supposing you have a Storybook theme defined for the main UI in `.storybook/manager.js`: ```js -import { addParameters } from '@storybook/react'; +import { addons } from '@storybook/addons'; +// or a custom theme import { themes } from '@storybook/theming'; -addParameters({ - options: { +addons.setConfig({ + theme: themes.dark, +}); +``` + +Here's how you'd specify the same theme for docs in `.storybook/preview.js`: + +```js +import { themes } from '@storybook/theming'; + +// or global addParameters +export const parameters = { + docs: { theme: themes.dark, }, -}); +}; ``` ## CSS escape hatches @@ -60,7 +73,7 @@ addParameters({ }); ``` -You can even override a Storybook *block* component. +You can even override a Storybook _block_ component. Here's how you might insert a custom `<Preview />` block: @@ -78,10 +91,7 @@ addParameters({ ## More resources -Want to learn more? Here are some more articles on Storybook Docs: - -- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md) +- Framework-specific docs: [React](../react/README.md) / [Vue](../vue/README.md) / [Angular](../angular/README.md) / [Web components](../web-components/README.md) / [Ember](../ember/README.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) -- [Technical preview guide](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) diff --git a/addons/docs/ember/README.md b/addons/docs/ember/README.md index 6c3b110cb81f..ad49342a228e 100644 --- a/addons/docs/ember/README.md +++ b/addons/docs/ember/README.md @@ -1,4 +1,6 @@ -# Storybook Docs for Ember +<h1>Storybook Docs for Ember</h1> + +> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. Storybook Docs transforms your Storybook stories into world-class component documentation. Storybook Docs for Ember supports [DocsPage](../docs/docspage.md) for auto-generated docs, and [MDX](../docs/mdx.md) for rich long-form docs. @@ -6,6 +8,7 @@ To learn more about Storybook Docs, read the [general documentation](../README.m - [Installation](#installation) - [DocsPage](#docspage) +- [Props tables](#props-tables) - [MDX](#mdx) - [IFrame height](#iframe-height) - [More resources](#more-resources) @@ -18,11 +21,11 @@ First add the package. Make sure that the versions for your `@storybook/*` packa yarn add -D @storybook/addon-docs@next ``` -Then add the following to your `.storybook/main.js` presets: +Then add the following to your `.storybook/main.js` addons: ```js module.exports = { - presets: ['@storybook/addon-docs/preset'], + addons: ['@storybook/addon-docs'], }; ``` @@ -30,7 +33,9 @@ module.exports = { When you [install docs](#installation) you should get basic [DocsPage](../docs/docspage.md) documentation automagically for all your stories, available in the `Docs` tab of the Storybook UI. -Props tables for your components requires a few more steps. Docs for Ember relies on [@storybook/ember-cli-storybook addon](https://github.com/storybookjs/ember-cli-storybook), to extract documentation comments from your component source files. If you're using Storybook with Ember, you should already have this addon installed, you will just need to enable it by adding the following config block in your `ember-cli-build.js` file: +## Props tables + +Getting [Props tables](../docs/props-tables.md) for your components requires a few more steps. Docs for Ember relies on [@storybook/ember-cli-storybook addon](https://github.com/storybookjs/ember-cli-storybook), to extract documentation comments from your component source files. If you're using Storybook with Ember, you should already have this addon installed, you will just need to enable it by adding the following config block in your `ember-cli-build.js` file: ```js let app = new EmberApp(defaults, { @@ -83,7 +88,7 @@ Then update your `.storybook/main.js` to make sure you load MDX files: ```js module.exports = { - stories: ['../src/stories/**/*.stories.(js|mdx)'], + stories: ['../src/stories/**/*.stories.@(js|mdx)'], }; ``` @@ -91,7 +96,7 @@ Finally, you can create MDX files like this: ```md import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; -import hbs from 'htmlbars-inline-precompile' +import { hbs } from 'ember-cli-htmlbars'; <Meta title='App Component' component='AppComponent' /> @@ -129,8 +134,8 @@ For `DocsPage`, you need to update the parameter locally in a story: ```ts export const basic = () => ... -basic.story = { - parameters: { docs: { iframeHeight: 400 } } +basic.parameters = { + docs: { iframeHeight: 400 } } ``` @@ -144,7 +149,6 @@ And for `MDX` you can modify it as an attribute on the `Story` element: Want to learn more? Here are some more articles on Storybook Docs: -- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) / [Props](../docs/props-tables.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/html/preset.js b/addons/docs/html/preset.js deleted file mode 100644 index c8e7b0564571..000000000000 --- a/addons/docs/html/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/common/makePreset').default('html'); diff --git a/addons/docs/jest-transform-mdx.js b/addons/docs/jest-transform-mdx.js index 9ce7e098d8b1..9f62e0dcc562 100644 --- a/addons/docs/jest-transform-mdx.js +++ b/addons/docs/jest-transform-mdx.js @@ -7,20 +7,6 @@ const createCompiler = require('./mdx-compiler-plugin'); const compilers = [createCompiler({})]; -const getNextTransformer = (filename, config) => { - const extension = path.extname(filename); - const jsFileName = `${filename.slice(0, -extension.length)}.js`; - const self = config.transform.find(([pattern]) => new RegExp(pattern).test(filename)); - const jsTransforms = config.transform.filter(([pattern]) => new RegExp(pattern).test(jsFileName)); - return new ScriptTransformer({ - ...config, - transform: [ - ...config.transform.filter(entry => entry !== self), - ...jsTransforms.map(([pattern, ...rest]) => [self[0], ...rest]), - ], - }); -}; - module.exports = { process(src, filename, config, { instrument }) { const result = dedent` @@ -29,6 +15,10 @@ module.exports = { import { mdx } from '@mdx-js/react' ${mdx.sync(src, { compilers, filepath: filename })} `; - return getNextTransformer(filename, config).transformSource(filename, result, instrument); + + const extension = path.extname(filename); + const jsFileName = `${filename.slice(0, -extension.length)}.js`; + + return new ScriptTransformer(config).transformSource(jsFileName, result, instrument); }, }; diff --git a/addons/docs/package.json b/addons/docs/package.json index 9923324c623d..4011839393ad 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Superior documentation for your components", "keywords": [ "addon", @@ -17,11 +17,13 @@ "directory": "addons/docs" }, "license": "MIT", + "main": "dist/public_api.js", + "types": "dist/public_api.d.ts", "files": [ "dist/**/*", - "docs/**/*", "angular/**/*", "common/**/*", + "ember/**/*", "html/**/*", "postinstall/**/*", "react/**/*", @@ -29,29 +31,35 @@ "web-components/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/public_api.js", - "types": "dist/public_api.d.ts", "scripts": { + "createDlls": "node -r esm ./scripts/createDlls.js", "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.2", + "@babel/generator": "^7.9.6", + "@babel/parser": "^7.9.6", "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/preset-env": "^7.9.6", "@egoist/vue-to-react": "^1.1.0", - "@jest/transform": "^24.9.0", + "@jest/transform": "^26.0.0", "@mdx-js/loader": "^1.5.1", "@mdx-js/mdx": "^1.5.1", "@mdx-js/react": "^1.5.1", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", "@storybook/csf": "0.0.1", - "@storybook/postinstall": "5.3.0-rc.0", - "@storybook/source-loader": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/node-logger": "6.0.0-beta.21", + "@storybook/postinstall": "6.0.0-beta.21", + "@storybook/source-loader": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "acorn": "^7.1.0", "acorn-jsx": "^5.1.0", "acorn-walk": "^7.0.0", @@ -64,29 +72,78 @@ "lodash": "^4.17.15", "prop-types": "^15.7.2", "react-element-to-jsx-string": "^14.1.0", - "remark-external-links": "^5.0.0", - "remark-slug": "^5.1.2", - "ts-dedent": "^1.1.0", + "regenerator-runtime": "^0.13.3", + "remark-external-links": "^6.0.0", + "remark-slug": "^6.0.0", + "ts-dedent": "^1.1.1", "util-deprecate": "^1.0.2", - "vue-docgen-api": "^4.1.0", - "vue-docgen-loader": "^1.3.0-beta.0" + "vue-docgen-api": "^4.7.0", + "vue-docgen-loader": "^1.4.0" }, "devDependencies": { + "@angular/core": "^9.1.0", + "@babel/core": "^7.9.6", + "@emotion/core": "^10.0.20", + "@emotion/styled": "^10.0.17", + "@storybook/react": "6.0.0-beta.21", + "@storybook/web-components": "6.0.0-beta.21", + "@types/cross-spawn": "^6.0.1", "@types/doctrine": "^0.0.3", "@types/enzyme": "^3.10.3", - "@types/jest": "^24.0.11", + "@types/estree": "^0.0.44", + "@types/jest": "^25.1.1", "@types/prop-types": "^15.5.9", + "@types/react-is": "^16.7.1", + "@types/tmp": "^0.1.0", "@types/util-deprecate": "^1.0.0", - "@types/webpack-env": "^1.14.0", - "jest-specific-snapshot": "^2.0.0" + "babel-loader": "^8.0.6", + "babel-plugin-react-docgen": "^4.1.0", + "cross-spawn": "^7.0.1", + "fs-extra": "^9.0.0", + "jest": "^26.0.0", + "jest-specific-snapshot": "^3.0.0", + "lit-element": "^2.2.1", + "lit-html": "^1.0.0", + "prettier": "^2.0.5", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "react-is": "^16.12.0", + "require-from-string": "^2.0.2", + "rxjs": "^6.5.4", + "styled-components": "^5.0.1", + "terser-webpack-plugin": "^3.0.0", + "tmp": "^0.2.1", + "tslib": "^1.11.1", + "web-component-analyzer": "^1.0.3", + "webpack": "^4.33.0", + "zone.js": "^0.10.2" }, "peerDependencies": { + "@babel/core": "^7.0.0-0", "babel-loader": "^8.0.0", - "react": "^16.8.0", - "react-is": "^16.8.0" + "react": ">=16.3.0", + "react-dom": "*", + "react-is": "^16.8.0", + "vue": "^2.6.10", + "webpack": ">=4" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + }, + "webpack": { + "optional": true + } }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/docs/postinstall/presets.js b/addons/docs/postinstall/presets.js index a8feae9101e4..e503cacc8391 100644 --- a/addons/docs/postinstall/presets.js +++ b/addons/docs/postinstall/presets.js @@ -1,6 +1,5 @@ import fs from 'fs'; import { presetsAddPreset, getFrameworks } from '@storybook/postinstall'; -// eslint-disable-next-line import/no-extraneous-dependencies import { logger } from '@storybook/node-logger'; export default function transformer(file, api) { diff --git a/addons/docs/react/README.md b/addons/docs/react/README.md index afd9ea0cc20c..ec15e32e39df 100644 --- a/addons/docs/react/README.md +++ b/addons/docs/react/README.md @@ -2,7 +2,9 @@ <img src="../docs/media/docspage-hero.png" width="100%" /> </center> -# Storybook Docs for React +<h1>Storybook Docs for React</h1> + +> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. Storybook Docs transforms your Storybook stories into world-class component documentation. Storybook Docs for React supports [DocsPage](../docs/docspage.md) for auto-generated docs, and [MDX](../docs/mdx.md) for rich long-form docs. @@ -10,8 +12,10 @@ To learn more about Storybook Docs, read the [general documentation](../README.m - [Installation](#installation) - [DocsPage](#docspage) +- [Props tables](#props-tables) - [MDX](#mdx) - [Inline stories](#inline-stories) +- [TypeScript props with `react-docgen`](#typescript-props-with-react-docgen) - [More resources](#more-resources) ## Installation @@ -22,12 +26,12 @@ First add the package. Make sure that the versions for your `@storybook/*` packa yarn add -D @storybook/addon-docs@next ``` -Then add the following to your `.storybook/main.js` list of `presets`: +Then add the following to your `.storybook/main.js` list of `addons`: ```js module.exports = { // other settings - presets: ['@storybook/addon-docs/preset']; + addons: ['@storybook/addon-docs']; } ``` @@ -35,7 +39,9 @@ module.exports = { When you [install docs](#installation) you should get basic [DocsPage](../docs/docspage.md) documentation automagically for all your stories, available in the `Docs` tab of the Storybook UI. -To show the props table for your component, be sure to fill in the `component` field in your story metadata: +## Props tables + +Storybook Docs automatically generates [Props tables](../docs/props-tables.md) for your components based on either `PropTypes` or `TypeScript` types. To show the props table for your component, be sure to fill in the `component` field in your story metadata: ```ts import { Button } from './Button'; @@ -71,7 +77,7 @@ Then update your `.storybook/main.js` to make sure you load MDX files: ```js module.exports = { - stories: ['../src/stories/**/*.stories.(js|mdx)'], + stories: ['../src/stories/**/*.stories.@(js|mdx)'], }; ``` @@ -96,7 +102,7 @@ Some **markdown** description, or whatever you want. <Props of={Button} /> ``` -## Inline Stories +## Inline stories Storybook Docs renders all React stories inline on the page by default. If you want to render stories in an `iframe` so that they are better isolated. To do this, update `.storybook/preview.js`: @@ -110,11 +116,70 @@ addParameters({ }); ``` +## TypeScript props with `react-docgen` + +If you're using TypeScript, there are two different options for generating props: `react-docgen-typescript` (default) or `react-docgen`. + +You can add the following lines to your `.storybook/main.js` to switch between the two (or disable docgen): + +```js +module.exports = { + typescript: { + // also valid 'react-docgen-typescript' | false + reactDocgen: 'react-docgen', + }, +}; +``` + +Neither option is perfect, so here's everything you should know if you're thinking about using `react-docgen` for TypeScript. + +| | `react-docgen-typescript` | `react-docgen` | +| --------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **Features** | **Great**. The analysis produces great results which gives the best props table experience. | **OK**. React-docgen produces basic results that are fine for most use cases. | +| **Performance** | **Slow**. It's doing a lot more work to produce those results, and may also have an inefficient implementation. | **Blazing fast**. Adding it to your project increases build time negligibly. | +| **Bugs** | **Many**. There are a lot of corner cases that are not handled properly, and are annoying for developers. | **Few**. But there's a dealbreaker, which is lack for imported types (see below). | +| **SB docs** | **Good**. Our prop tables have supported `react-docgen-typescript` results from the beginning, so it's relatively stable. | **OK**. There are some obvious improvements to fully support `react-docgen`, and they're coming soon. | + +**Performance** is a common question, so here are build times from a random project to quantify. Your mileage may vary: + +| Docgen | Build time | +| ----------------------- | ---------- | +| react-docgen-typescript | 59s | +| react-docgen | 29s | +| none | 28s | + +The biggest limitation of `react-docgen` is lack of support for imported types. What that means is that when a component uses a type defined in another file or package, `react-docgen` is unable to extract props information for that type. + +```tsx +import React, { FC } from 'react'; +import SomeType from './someFile'; + +type NewType = SomeType & { foo: string }; +const MyComponent: FC<NewType> = ... +``` + +So in the previous example, `SomeType` would simply be ignored! There's an [open PR for this in the `react-docgen` repo](https://github.com/reactjs/react-docgen/pull/352) which you can upvote if it affects you. + +Another common pitfall when switching to `react-docgen` is [lack of support for `React.FC`](https://github.com/reactjs/react-docgen/issues/387). This means that the following common pattern **DOESN'T WORK**: + +```tsx +import React, { FC } from 'react'; +interface IProps { ... }; +const MyComponent: FC<IProps> = ({ ... }) => ... +``` + +Fortunately, the following workaround works: + +```tsx +const MyComponent: FC<IProps> = ({ ... }: IProps) => ... +``` + +Please upvote [the issue](https://github.com/reactjs/react-docgen/issues/387) if this is affecting your productivity, or better yet, submit a fix! + ## More resources Want to learn more? Here are some more articles on Storybook Docs: -- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) / [Props](../docs/props-tables.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/react/config.js b/addons/docs/react/config.js deleted file mode 100644 index 501faa1d087c..000000000000 --- a/addons/docs/react/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/react/config'); diff --git a/addons/docs/react/preset.js b/addons/docs/react/preset.js deleted file mode 100644 index c92d21ffca5c..000000000000 --- a/addons/docs/react/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/common/makePreset').default('react'); diff --git a/addons/docs/scripts/createDlls.js b/addons/docs/scripts/createDlls.js new file mode 100644 index 000000000000..202b368b03d3 --- /dev/null +++ b/addons/docs/scripts/createDlls.js @@ -0,0 +1,52 @@ +import path from 'path'; +import webpack from 'webpack'; + +import config from './webpackDllsConfig'; + +const resolveLocal = (dir) => path.join(__dirname, dir); +const webpackAsPromised = (c) => + new Promise((res, rej) => { + webpack(c).run((err, stats) => { + if (err || stats.hasErrors() || stats.hasWarnings()) { + rej(stats); + return; + } + res(stats); + }); + }); + +const run = () => + webpackAsPromised( + config({ + entry: { + storybook_docs: [ + '@emotion/core', + '@emotion/styled', + '@storybook/addons', + '@storybook/api', + '@storybook/components', + '@storybook/core-events', + '@storybook/theming', + 'airbnb-js-shims', + 'emotion-theming', + 'react', + 'react-dom', + 'regenerator-runtime/runtime', + resolveLocal('../dist/public_api.js'), + ], + }, + }) + ); + +run().then( + (s) => { + // eslint-disable-next-line no-console + console.log('success: ', s.toString()); + process.exitCode = 0; + }, + (s) => { + // eslint-disable-next-line no-console + console.error('failed: ', s.toString()); + process.exitCode = 1; + } +); diff --git a/addons/docs/scripts/webpackDllsConfig.js b/addons/docs/scripts/webpackDllsConfig.js new file mode 100644 index 000000000000..66bfe0af0bc8 --- /dev/null +++ b/addons/docs/scripts/webpackDllsConfig.js @@ -0,0 +1,89 @@ +import path from 'path'; +import { ProgressPlugin, DllPlugin } from 'webpack'; +import TerserPlugin from 'terser-webpack-plugin'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import uiPaths from '@storybook/ui/paths'; +import themingPaths from '@storybook/theming/paths'; + +const resolveLocal = (dir) => path.join(__dirname, dir); + +const r = resolveLocal('../../../node_modules'); +const out = resolveLocal('../../../lib/core/dll'); + +export default ({ entry, provided = [] }) => ({ + name: 'storybook-docs', + mode: 'development', + + entry, + output: { + path: out, + filename: 'storybook_docs_dll.js', + library: 'storybook_docs_dll', + }, + externals: provided, + + module: { + rules: [ + { + test: /\.js$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: { + sourceType: 'unambiguous', + plugins: [ + require.resolve('@babel/plugin-transform-shorthand-properties'), + require.resolve('@babel/plugin-proposal-object-rest-spread'), + require.resolve('@babel/plugin-transform-template-literals'), + require.resolve('@babel/plugin-transform-block-scoping'), + require.resolve('@babel/plugin-transform-classes'), + require.resolve('@babel/plugin-transform-arrow-functions'), + require.resolve('@babel/plugin-transform-parameters'), + require.resolve('@babel/plugin-transform-destructuring'), + require.resolve('@babel/plugin-transform-spread'), + require.resolve('@babel/plugin-transform-for-of'), + ], + }, + }, + ], + }, + ], + }, + + resolve: { + extensions: ['.mjs', '.js', '.jsx', '.json'], + modules: [path.join(__dirname, '../../../node_modules')], + alias: { + ...themingPaths, + ...uiPaths, + semver: require.resolve('@storybook/semver'), + }, + }, + + plugins: [ + new ProgressPlugin(), + new DllPlugin({ + context: r, + path: `${out}/storybook_docs-manifest.json`, + name: 'storybook_docs_dll', + }), + ], + optimization: { + concatenateModules: true, + portableRecords: true, + moduleIds: 'hashed', + minimizer: [ + new TerserPlugin({ + extractComments: { + condition: /^\**!|@preserve|@license|@cc_on/i, + filename: (file) => file.replace('.js', '.LICENCE'), + banner: (licenseFile) => `License information can be found in ${licenseFile}`, + }, + }), + ], + }, + performance: { + hints: false, + }, +}); diff --git a/addons/docs/src/blocks/Description.tsx b/addons/docs/src/blocks/Description.tsx index ce34756634c3..520b62632364 100644 --- a/addons/docs/src/blocks/Description.tsx +++ b/addons/docs/src/blocks/Description.tsx @@ -1,7 +1,7 @@ import React, { FunctionComponent, useContext } from 'react'; import { Description, DescriptionProps as PureDescriptionProps } from '@storybook/components'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { Component, CURRENT_SELECTION, DescriptionSlot } from './shared'; +import { Component, CURRENT_SELECTION } from './types'; import { str } from '../lib/docgen'; export enum DescriptionType { @@ -16,7 +16,6 @@ type Notes = string | any; type Info = string | any; interface DescriptionProps { - slot?: DescriptionSlot; of?: '.' | Component; type?: DescriptionType; markdown?: string; @@ -61,13 +60,9 @@ ${extractComponentDescription(target) || ''} } }; -const DescriptionContainer: FunctionComponent<DescriptionProps> = props => { +const DescriptionContainer: FunctionComponent<DescriptionProps> = (props) => { const context = useContext(DocsContext); - const { slot } = props; - let { markdown } = getDescriptionProps(props, context); - if (slot) { - markdown = slot(markdown, context); - } + const { markdown } = getDescriptionProps(props, context); return markdown ? <Description markdown={markdown} /> : null; }; diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index cedf8e18f4de..2a89ede5832c 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -1,5 +1,7 @@ import React, { FunctionComponent, useEffect } from 'react'; import { document, window } from 'global'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; import { MDXProvider } from '@mdx-js/react'; import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming'; import { DocsWrapper, DocsContent } from '@storybook/components'; @@ -10,7 +12,7 @@ import { storyBlockIdFromId } from './Story'; import { CodeOrSourceMdx, AnchorMdx, HeadersMdx } from './mdx'; import { scrollToElement } from './utils'; -interface DocsContainerProps { +export interface DocsContainerProps { context: DocsContextProps; } @@ -23,13 +25,28 @@ const defaultComponents = { export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context, children }) => { const { id: storyId = null, parameters = {} } = context || {}; - const options = parameters.options || {}; - const theme = ensureTheme(options.theme); - const { components: userComponents = null } = parameters.docs || {}; - const allComponents = { ...defaultComponents, ...userComponents }; + const { options = {}, docs = {} } = parameters; + let themeVars = docs.theme; + if (!themeVars && options.theme) { + deprecate( + () => {}, + dedent` + options.theme => Deprecated: use story.parameters.docs.theme instead. + See https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/theming.md#storybook-theming for details. + ` + )(); + themeVars = options.theme; + } + const theme = ensureTheme(themeVars); + const allComponents = { ...defaultComponents, ...docs.components }; useEffect(() => { - const url = new URL(window.parent.location); + let url; + try { + url = new URL(window.parent.location); + } catch (err) { + return; + } if (url.hash) { const element = document.getElementById(url.hash.substring(1)); if (element) { diff --git a/addons/docs/src/blocks/DocsContext.ts b/addons/docs/src/blocks/DocsContext.ts index a900ed260773..0556b6304e39 100644 --- a/addons/docs/src/blocks/DocsContext.ts +++ b/addons/docs/src/blocks/DocsContext.ts @@ -2,15 +2,16 @@ import { Context, createContext } from 'react'; export interface DocsContextProps { id?: string; - selectedKind?: string; - selectedStory?: string; + kind?: string; + name?: string; /** - * mdxStoryNameToId is an MDX-compiler-generated mapping of an MDX story's - * display name to its storyId. It's used internally by the `<Story>` - * doc block. + * mdxStoryNameToKey is an MDX-compiler-generated mapping of an MDX story's + * display name to its story key for ID generation. It's used internally by the `<Story>` + * and `Preview` doc blocks. */ - mdxStoryNameToId?: Record<string, string>; + mdxStoryNameToKey?: Record<string, string>; + mdxComponentMeta?: any; parameters?: any; storyStore?: any; forceRender?: () => void; diff --git a/addons/docs/src/blocks/DocsPage.test.ts b/addons/docs/src/blocks/DocsPage.test.ts index 92e7264a40cf..2562fb2b5151 100644 --- a/addons/docs/src/blocks/DocsPage.test.ts +++ b/addons/docs/src/blocks/DocsPage.test.ts @@ -1,24 +1,24 @@ -import { defaultTitleSlot } from './Title'; +import { extractTitle } from './Title'; describe('defaultTitleSlot', () => { it('showRoots', () => { const parameters = { options: { showRoots: true }, }; - expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c'); - expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('a|b'); - expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('c.d'); + expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); + expect(extractTitle({ kind: 'a|b', parameters })).toBe('a|b'); + expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('c.d'); }); it('no showRoots', () => { const parameters = {}; - expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c'); - expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('b'); - expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('d'); + expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); + expect(extractTitle({ kind: 'a|b', parameters })).toBe('b'); + expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('d'); }); it('empty options', () => { const parameters = { options: {} }; - expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c'); - expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('b'); - expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('d'); + expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); + expect(extractTitle({ kind: 'a|b', parameters })).toBe('b'); + expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('d'); }); }); diff --git a/addons/docs/src/blocks/DocsPage.tsx b/addons/docs/src/blocks/DocsPage.tsx index a19a7038867a..f5f69a69933e 100644 --- a/addons/docs/src/blocks/DocsPage.tsx +++ b/addons/docs/src/blocks/DocsPage.tsx @@ -1,26 +1,19 @@ -import React, { FunctionComponent } from 'react'; -import { DocsPageProps } from './shared'; +import React, { FC } from 'react'; import { Title } from './Title'; import { Subtitle } from './Subtitle'; import { Description } from './Description'; import { Primary } from './Primary'; import { Props } from './Props'; import { Stories } from './Stories'; +import { PRIMARY_STORY } from './types'; -export const DocsPage: FunctionComponent<DocsPageProps> = ({ - titleSlot, - subtitleSlot, - descriptionSlot, - primarySlot, - propsSlot, - storiesSlot, -}) => ( +export const DocsPage: FC = () => ( <> - <Title slot={titleSlot} /> - <Subtitle slot={subtitleSlot} /> - <Description slot={descriptionSlot} /> - <Primary slot={primarySlot} /> - <Props slot={propsSlot} /> - <Stories slot={storiesSlot} /> + <Title /> + <Subtitle /> + <Description /> + <Primary /> + <Props story={PRIMARY_STORY} /> + <Stories /> </> ); diff --git a/addons/docs/src/blocks/DocsStory.tsx b/addons/docs/src/blocks/DocsStory.tsx index ca6640ad1832..f522175c5ea3 100644 --- a/addons/docs/src/blocks/DocsStory.tsx +++ b/addons/docs/src/blocks/DocsStory.tsx @@ -1,6 +1,6 @@ import React, { FunctionComponent } from 'react'; import { Subheading } from './Subheading'; -import { DocsStoryProps } from './shared'; +import { DocsStoryProps } from './types'; import { Anchor } from './Anchor'; import { Description } from './Description'; import { Story } from './Story'; diff --git a/addons/docs/src/blocks/Meta.tsx b/addons/docs/src/blocks/Meta.tsx index 49d43ae83b0a..f3f5f49a3a03 100644 --- a/addons/docs/src/blocks/Meta.tsx +++ b/addons/docs/src/blocks/Meta.tsx @@ -1,17 +1,41 @@ -import { FunctionComponent } from 'react'; +import React, { FC, useContext } from 'react'; +import { document } from 'global'; +import { Anchor } from './Anchor'; +import { DocsContext, DocsContextProps } from './DocsContext'; +import { getDocsStories } from './utils'; +import { Component } from './types'; type Decorator = (...args: any) => any; interface MetaProps { title: string; - component?: any; + component?: Component; + subcomponents: Record<string, Component>; decorators?: [Decorator]; parameters?: any; } +function getFirstStoryId(docsContext: DocsContextProps): string { + const stories = getDocsStories(docsContext); + + return stories.length > 0 ? stories[0].id : null; +} + +function renderAnchor() { + const context = useContext(DocsContext); + // eslint-disable-next-line react/destructuring-assignment + const anchorId = getFirstStoryId(context) || context.id; + + return <Anchor storyId={anchorId} />; +} + /** * This component is used to declare component metadata in docs * and gets transformed into a default export underneath the hood. - * It doesn't actually render anything. */ -export const Meta: FunctionComponent<MetaProps> = props => null; +export const Meta: FC<MetaProps> = () => { + const params = new URL(document.location).searchParams; + const isDocs = params.get('viewMode') === 'docs'; + + return isDocs ? renderAnchor() : null; +}; diff --git a/addons/docs/src/blocks/Preview.tsx b/addons/docs/src/blocks/Preview.tsx index e108414ea738..a9008b5aaf3f 100644 --- a/addons/docs/src/blocks/Preview.tsx +++ b/addons/docs/src/blocks/Preview.tsx @@ -1,4 +1,7 @@ import React, { FunctionComponent, ReactElement, ReactNode, ReactNodeArray } from 'react'; +import { MDXProvider } from '@mdx-js/react'; +import { toId, storyNameFromExport } from '@storybook/csf'; +import { resetComponents } from '@storybook/components/html'; import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components'; import { getSourceProps } from './Source'; import { DocsContext, DocsContextProps } from './DocsContext'; @@ -21,7 +24,7 @@ const getPreviewProps = ( children, ...props }: PreviewProps & { children?: ReactNode }, - { mdxStoryNameToId, storyStore }: DocsContextProps + { mdxStoryNameToKey, mdxComponentMeta, storyStore }: DocsContextProps ): PurePreviewProps => { if (withSource === SourceState.NONE) { return props; @@ -36,7 +39,14 @@ const getPreviewProps = ( const stories = childArray.filter( (c: ReactElement) => c.props && (c.props.id || c.props.name) ) as ReactElement[]; - const targetIds = stories.map(s => s.props.id || mdxStoryNameToId[s.props.name]); + const targetIds = stories.map( + (s) => + s.props.id || + toId( + mdxComponentMeta.id || mdxComponentMeta.title, + storyNameFromExport(mdxStoryNameToKey[s.props.name]) + ) + ); const sourceProps = getSourceProps({ ids: targetIds }, { storyStore }); return { ...props, // pass through columns etc. @@ -45,11 +55,15 @@ const getPreviewProps = ( }; }; -export const Preview: FunctionComponent<PreviewProps> = props => ( +export const Preview: FunctionComponent<PreviewProps> = (props) => ( <DocsContext.Consumer> - {context => { + {(context) => { const previewProps = getPreviewProps(props, context); - return <PurePreview {...previewProps}>{props.children}</PurePreview>; + return ( + <MDXProvider components={resetComponents}> + <PurePreview {...previewProps}>{props.children}</PurePreview> + </MDXProvider> + ); }} </DocsContext.Consumer> ); diff --git a/addons/docs/src/blocks/Primary.tsx b/addons/docs/src/blocks/Primary.tsx index fe2b764fcaa6..242e17794876 100644 --- a/addons/docs/src/blocks/Primary.tsx +++ b/addons/docs/src/blocks/Primary.tsx @@ -1,16 +1,18 @@ -import React, { useContext, FunctionComponent } from 'react'; +import React, { useContext, FC } from 'react'; import { DocsContext } from './DocsContext'; import { DocsStory } from './DocsStory'; import { getDocsStories } from './utils'; -import { StorySlot } from './shared'; interface PrimaryProps { - slot?: StorySlot; + name?: string; } -export const Primary: FunctionComponent<PrimaryProps> = ({ slot }) => { +export const Primary: FC<PrimaryProps> = ({ name }) => { const context = useContext(DocsContext); const componentStories = getDocsStories(context); - const story = slot ? slot(componentStories, context) : componentStories && componentStories[0]; + let story; + if (componentStories) { + story = name ? componentStories.find((s) => s.name === name) : componentStories[0]; + } return story ? <DocsStory {...story} expanded={false} withToolbar /> : null; }; diff --git a/addons/docs/src/blocks/Props.tsx b/addons/docs/src/blocks/Props.tsx index 21a4d573852a..2bb365863acb 100644 --- a/addons/docs/src/blocks/Props.tsx +++ b/addons/docs/src/blocks/Props.tsx @@ -1,85 +1,106 @@ -import React, { FunctionComponent, useContext } from 'react'; -import { isNil } from 'lodash'; - +/* eslint-disable no-underscore-dangle */ +import React, { FC, useContext, useEffect, useState, useCallback } from 'react'; +import mapValues from 'lodash/mapValues'; +import pickBy from 'lodash/pickBy'; import { - PropsTable, - PropsTableError, - PropsTableProps, - PropsTableRowsProps, - PropsTableSectionsProps, - PropDef, - TabsState, + ArgsTable, + ArgsTableProps, + ArgsTableError, + ArgTypes, + TabbedArgsTable, } from '@storybook/components'; +import { Args } from '@storybook/addons'; +import { StoryStore } from '@storybook/client-api'; +import Events from '@storybook/core-events'; + import { DocsContext, DocsContextProps } from './DocsContext'; -import { Component, PropsSlot, CURRENT_SELECTION } from './shared'; -import { getComponentName } from './utils'; +import { Component, CURRENT_SELECTION, PRIMARY_STORY } from './types'; +import { getComponentName, getDocsStories } from './utils'; +import { ArgTypesExtractor } from '../lib/docgen/types'; +import { lookupStoryId } from './Story'; -import { PropsExtractor } from '../lib/docgen/types'; -import { extractProps as reactExtractProps } from '../frameworks/react/extractProps'; -import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps'; +type PropDescriptor = string[] | RegExp; + +interface BaseProps { + include?: PropDescriptor; + exclude?: PropDescriptor; +} -interface PropsProps { - exclude?: string[]; - of?: '.' | Component; - components?: { +type OfProps = BaseProps & { + of: '.' | Component; +}; + +type ComponentsProps = BaseProps & { + components: { [label: string]: Component; }; - slot?: PropsSlot; -} +}; -// FIXME: remove in SB6.0 & require config -const inferPropsExtractor = (framework: string): PropsExtractor | null => { - switch (framework) { - case 'react': - return reactExtractProps; - case 'vue': - return vueExtractProps; - default: - return null; - } +type StoryProps = BaseProps & { + story: '.' | string; + showComponents?: boolean; }; -const filterRows = (rows: PropDef[], exclude: string[]) => - rows && rows.filter((row: PropDef) => !exclude.includes(row.name)); +type PropsProps = BaseProps | OfProps | ComponentsProps | StoryProps; -export const getComponentProps = ( - component: Component, - { exclude }: PropsProps, - { parameters }: DocsContextProps -): PropsTableProps => { - if (!component) { - return null; +const useArgs = (storyId: string, storyStore: StoryStore): [Args, (args: Args) => void] => { + const story = storyStore.fromId(storyId); + if (!story) { + throw new Error(`Unknown story: ${storyId}`); } - try { - const params = parameters || {}; - const { framework = null } = params; - const { extractProps = inferPropsExtractor(framework) }: { extractProps: PropsExtractor } = - params.docs || {}; - if (!extractProps) { - throw new Error(PropsTableError.PROPS_UNSUPPORTED); - } - let props = extractProps(component); - if (!isNil(exclude)) { - const { rows } = props as PropsTableRowsProps; - const { sections } = props as PropsTableSectionsProps; - if (rows) { - props = { rows: filterRows(rows, exclude) }; - } else if (sections) { - Object.keys(sections).forEach(section => { - sections[section] = filterRows(sections[section], exclude); - }); + const { args: initialArgs } = story; + const [args, setArgs] = useState(initialArgs); + useEffect(() => { + const cb = (changedId: string, newArgs: Args) => { + if (changedId === storyId) { + setArgs(newArgs); } - } + }; + storyStore._channel.on(Events.STORY_ARGS_UPDATED, cb); + return () => storyStore._channel.off(Events.STORY_ARGS_UPDATED, cb); + }, [storyId]); + const updateArgs = useCallback((newArgs) => storyStore.updateStoryArgs(storyId, newArgs), [ + storyId, + ]); + return [args, updateArgs]; +}; - return props; - } catch (err) { - return { error: err.message }; +const matches = (name: string, descriptor: PropDescriptor) => + Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor); + +const filterArgTypes = (argTypes: ArgTypes, include?: PropDescriptor, exclude?: PropDescriptor) => { + if (!include && !exclude) { + return argTypes; + } + return ( + argTypes && + pickBy(argTypes, (argType, key) => { + const name = argType.name || key; + return (!include || matches(name, include)) && (!exclude || !matches(name, exclude)); + }) + ); +}; + +export const extractComponentArgTypes = ( + component: Component, + { parameters }: DocsContextProps, + include?: PropDescriptor, + exclude?: PropDescriptor +): ArgTypes => { + const params = parameters || {}; + const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = params.docs || {}; + if (!extractArgTypes) { + throw new Error(ArgsTableError.ARGS_UNSUPPORTED); } + let argTypes = extractArgTypes(component); + argTypes = filterArgTypes(argTypes, include, exclude); + + return argTypes; }; export const getComponent = (props: PropsProps = {}, context: DocsContextProps): Component => { - const { of } = props; + const { of } = props as OfProps; const { parameters = {} } = context; const { component } = parameters; @@ -88,60 +109,129 @@ export const getComponent = (props: PropsProps = {}, context: DocsContextProps): if (of === CURRENT_SELECTION) { return null; } - throw new Error(PropsTableError.NO_COMPONENT); + throw new Error(ArgsTableError.NO_COMPONENT); } return target; }; -const PropsContainer: FunctionComponent<PropsProps> = props => { +const addComponentTabs = ( + tabs: Record<string, ArgsTableProps>, + components: Record<string, Component>, + context: DocsContextProps, + include?: PropDescriptor, + exclude?: PropDescriptor +) => ({ + ...tabs, + ...mapValues(components, (comp) => ({ + rows: extractComponentArgTypes(comp, context, include, exclude), + })), +}); + +export const StoryTable: FC<StoryProps & { components: Record<string, Component> }> = (props) => { + const context = useContext(DocsContext); + const { + id: currentId, + parameters: { argTypes }, + storyStore, + } = context; + const { story, showComponents, components, include, exclude } = props; + let storyArgTypes; + try { + let storyId; + switch (story) { + case CURRENT_SELECTION: { + storyId = currentId; + storyArgTypes = argTypes; + break; + } + case PRIMARY_STORY: { + const primaryStory = getDocsStories(context)[0]; + storyId = primaryStory.id; + storyArgTypes = primaryStory.parameters.argTypes; + break; + } + default: { + storyId = lookupStoryId(story, context); + const data = storyStore.fromId(storyId); + storyArgTypes = data.parameters.argTypes; + } + } + storyArgTypes = filterArgTypes(storyArgTypes, include, exclude); + + // This code handles three cases: + // 1. the story has args, in which case we want to show controls for the story + // 2. the story has args, and the user specifies showComponents, in which case + // we want to show controls for the primary component AND show props for each component + // 3. the story has NO args, in which case we want to show props for each component + + // eslint-disable-next-line prefer-const + let [args, updateArgs] = useArgs(storyId, storyStore); + let tabs = { Story: { rows: storyArgTypes, args, updateArgs } } as Record< + string, + ArgsTableProps + >; + + // Use the dynamically generated component tabs if there are no controls + const storyHasArgsWithControls = + storyArgTypes && Object.values(storyArgTypes).find((v) => !!v?.control); + + if (!storyHasArgsWithControls) { + updateArgs = null; + tabs = {}; + } + + if (showComponents || !storyHasArgsWithControls) { + tabs = addComponentTabs(tabs, components, context, include, exclude); + } + + return <TabbedArgsTable tabs={tabs} />; + } catch (err) { + return <ArgsTable error={err.message} />; + } +}; + +export const ComponentsTable: FC<ComponentsProps> = (props) => { + const context = useContext(DocsContext); + const { components, include, exclude } = props; + + const tabs = addComponentTabs({}, components, context, include, exclude); + return <TabbedArgsTable tabs={tabs} />; +}; + +export const Props: FC<PropsProps> = (props) => { const context = useContext(DocsContext); - const { slot, components } = props; const { parameters: { subcomponents }, } = context; - let allComponents = components; - if (!allComponents) { - const main = getComponent(props, context); - const mainLabel = getComponentName(main); - const mainProps = slot ? slot(context, main) : getComponentProps(main, props, context); + const { include, exclude, components } = props as ComponentsProps; + const { story } = props as StoryProps; - if (!subcomponents || typeof subcomponents !== 'object') { - return mainProps && <PropsTable {...mainProps} />; - } + let allComponents = components; + const main = getComponent(props, context); + if (!allComponents && main) { + const mainLabel = getComponentName(main); allComponents = { [mainLabel]: main, ...subcomponents }; } - const tabs: { label: string; table: PropsTableProps }[] = []; - Object.entries(allComponents).forEach(([label, component]) => { - tabs.push({ - label, - table: slot ? slot(context, component) : getComponentProps(component, props, context), - }); - }); + if (story) { + return <StoryTable {...(props as StoryProps)} components={allComponents} />; + } - return ( - <TabsState> - {tabs.map(({ label, table }) => { - if (!table) { - return null; - } - const id = `prop_table_div_${label}`; - return ( - <div key={id} id={id} title={label}> - {({ active }: { active: boolean }) => - active ? <PropsTable key={`prop_table_${label}`} {...table} /> : null - } - </div> - ); - })} - </TabsState> - ); -}; + if (!components && !subcomponents) { + let mainProps; + try { + mainProps = { rows: extractComponentArgTypes(main, context, include, exclude) }; + } catch (err) { + mainProps = { error: err.message }; + } + return <ArgsTable {...mainProps} />; + } -PropsContainer.defaultProps = { - of: '.', + return <ComponentsTable {...(props as ComponentsProps)} components={allComponents} />; }; -export { PropsContainer as Props }; +Props.defaultProps = { + of: CURRENT_SELECTION, +}; diff --git a/addons/docs/src/blocks/Source.tsx b/addons/docs/src/blocks/Source.tsx index 4464c02a6293..fcf7b0998fac 100644 --- a/addons/docs/src/blocks/Source.tsx +++ b/addons/docs/src/blocks/Source.tsx @@ -1,7 +1,8 @@ import React, { FunctionComponent } from 'react'; import { Source, SourceProps as PureSourceProps, SourceError } from '@storybook/components'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { CURRENT_SELECTION } from './shared'; +import { CURRENT_SELECTION } from './types'; +import { enhanceSource } from './enhanceSource'; interface CommonProps { language?: string; @@ -24,35 +25,6 @@ type NoneProps = CommonProps; type SourceProps = SingleSourceProps | MultiSourceProps | CodeProps | NoneProps; -interface Location { - line: number; - col: number; -} - -interface StorySource { - source: string; - locationsMap: { [id: string]: { startBody: Location; endBody: Location } }; -} - -const extract = (targetId: string, { source, locationsMap }: StorySource) => { - const location = locationsMap[targetId]; - // FIXME: bad locationsMap generated for module export functions whose titles are overridden - if (!location) return null; - const { startBody: start, endBody: end } = location; - const lines = source.split('\n'); - if (start.line === end.line) { - return lines[start.line - 1].substring(start.col, end.col); - } - // NOTE: storysource locations are 1-based not 0-based! - const startLine = lines[start.line - 1]; - const endLine = lines[end.line - 1]; - return [ - startLine.substring(start.col), - ...lines.slice(start.line, end.line - 1), - endLine.substring(0, end.col), - ].join('\n'); -}; - export const getSourceProps = ( props: SourceProps, { id: currentId, storyStore }: DocsContextProps @@ -63,16 +35,14 @@ export const getSourceProps = ( let source = codeProps.code; // prefer user-specified code if (!source) { - const targetId = singleProps.id === CURRENT_SELECTION ? currentId : singleProps.id; + const targetId = + singleProps.id === CURRENT_SELECTION || !singleProps.id ? currentId : singleProps.id; const targetIds = multiProps.ids || [targetId]; source = targetIds - .map(sourceId => { + .map((sourceId) => { const data = storyStore.fromId(sourceId); - if (data && data.parameters) { - const { mdxSource, storySource } = data.parameters; - return mdxSource || (storySource && extract(sourceId, storySource)); - } - return ''; + const enhanced = data && (enhanceSource(data) || data.parameters); + return enhanced?.docs?.source?.code || ''; }) .join('\n\n'); } @@ -86,9 +56,9 @@ export const getSourceProps = ( * or the source for a story if `storyId` is provided, or * the source for the current story if nothing is provided. */ -const SourceContainer: FunctionComponent<SourceProps> = props => ( +const SourceContainer: FunctionComponent<SourceProps> = (props) => ( <DocsContext.Consumer> - {context => { + {(context) => { const sourceProps = getSourceProps(props, context); return <Source {...sourceProps} />; }} diff --git a/addons/docs/src/blocks/Stories.tsx b/addons/docs/src/blocks/Stories.tsx index c1b585e82db7..afb7de3e8ad8 100644 --- a/addons/docs/src/blocks/Stories.tsx +++ b/addons/docs/src/blocks/Stories.tsx @@ -3,27 +3,27 @@ import { DocsContext } from './DocsContext'; import { DocsStory } from './DocsStory'; import { Heading } from './Heading'; import { getDocsStories } from './utils'; -import { StoriesSlot, DocsStoryProps } from './shared'; +import { DocsStoryProps } from './types'; interface StoriesProps { - slot?: StoriesSlot; title?: JSX.Element | string; + includePrimary?: boolean; } -export const Stories: FunctionComponent<StoriesProps> = ({ slot, title }) => { +export const Stories: FunctionComponent<StoriesProps> = ({ title, includePrimary = false }) => { const context = useContext(DocsContext); const componentStories = getDocsStories(context); - const stories: DocsStoryProps[] = slot - ? slot(componentStories, context) - : componentStories && componentStories.slice(1); - if (!stories) { + let stories: DocsStoryProps[] = componentStories; + if (!includePrimary) stories = stories.slice(1); + + if (!stories || stories.length === 0) { return null; } return ( <> <Heading>{title}</Heading> - {stories.map(story => story && <DocsStory key={story.id} {...story} expanded />)} + {stories.map((story) => story && <DocsStory key={story.id} {...story} expanded />)} </> ); }; diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index f9a4902b08f4..589962677415 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -1,17 +1,15 @@ -import React, { createElement, ElementType, FunctionComponent, ReactNode } from 'react'; +import React, { FunctionComponent, ReactNode, ComponentProps } from 'react'; import { MDXProvider } from '@mdx-js/react'; -import { components as docsComponents } from '@storybook/components/html'; -import { Story, StoryProps as PureStoryProps } from '@storybook/components'; -import { CURRENT_SELECTION } from './shared'; +import { resetComponents } from '@storybook/components/html'; +import { Story as PureStory } from '@storybook/components'; +import { toId, storyNameFromExport } from '@storybook/csf'; +import { CURRENT_SELECTION } from './types'; import { DocsContext, DocsContextProps } from './DocsContext'; export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`; -const resetComponents: Record<string, ElementType> = {}; -Object.keys(docsComponents).forEach(key => { - resetComponents[key] = (props: any) => createElement(key, props); -}); +type PureStoryProps = ComponentProps<typeof PureStory>; interface CommonProps { height?: string; @@ -29,50 +27,41 @@ type StoryRefProps = { export type StoryProps = StoryDefProps | StoryRefProps; -const inferInlineStories = (framework: string): boolean => { - switch (framework) { - case 'react': - return true; - default: - return false; - } -}; +export const lookupStoryId = ( + storyName: string, + { mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps +) => + toId( + mdxComponentMeta.id || mdxComponentMeta.title, + storyNameFromExport(mdxStoryNameToKey[storyName]) + ); -export const getStoryProps = ( - props: StoryProps, - { id: currentId, storyStore, parameters, mdxStoryNameToId }: DocsContextProps | null -): PureStoryProps => { +export const getStoryProps = (props: StoryProps, context: DocsContextProps): PureStoryProps => { const { id } = props as StoryRefProps; const { name } = props as StoryDefProps; - const inputId = id === CURRENT_SELECTION ? currentId : id; - const previewId = inputId || mdxStoryNameToId[name]; + const inputId = id === CURRENT_SELECTION ? context.id : id; + const previewId = inputId || lookupStoryId(name, context); + const data = context.storyStore.fromId(previewId) || {}; const { height, inline } = props; - const data = storyStore.fromId(previewId); - const { framework = null } = (data && data.parameters) || {}; + const { storyFn = undefined, name: storyName = undefined, parameters = {} } = data; + const { docs = {} } = parameters; - const docsParam = (data && data.parameters && data.parameters.docs) || {}; - - if (docsParam.disable) { + if (docs.disable) { return null; } - // prefer props, then global options, then framework-inferred values - const { - inlineStories = inferInlineStories(framework), - iframeHeight = undefined, - prepareForInline = undefined, - } = docsParam; - const { storyFn = undefined, name: storyName = undefined } = data || {}; - + // prefer block props, then story parameters defined by the framework-specific settings and optionally overriden by users + const { inlineStories = false, iframeHeight = 100, prepareForInline } = docs; const storyIsInline = typeof inline === 'boolean' ? inline : inlineStories; - if (storyIsInline && !prepareForInline && framework !== 'react') { + if (storyIsInline && !prepareForInline) { throw new Error( `Story '${storyName}' is set to render inline, but no 'prepareForInline' function is implemented in your docs configuration!` ); } return { + parameters, inline: storyIsInline, id: previewId, storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn) : storyFn, @@ -81,9 +70,9 @@ export const getStoryProps = ( }; }; -const StoryContainer: FunctionComponent<StoryProps> = props => ( +const Story: FunctionComponent<StoryProps> = (props) => ( <DocsContext.Consumer> - {context => { + {(context) => { const storyProps = getStoryProps(props, context); if (!storyProps) { return null; @@ -91,7 +80,7 @@ const StoryContainer: FunctionComponent<StoryProps> = props => ( return ( <div id={storyBlockIdFromId(storyProps.id)}> <MDXProvider components={resetComponents}> - <Story {...storyProps} /> + <PureStory {...storyProps} /> </MDXProvider> </div> ); @@ -99,9 +88,9 @@ const StoryContainer: FunctionComponent<StoryProps> = props => ( </DocsContext.Consumer> ); -StoryContainer.defaultProps = { +Story.defaultProps = { children: null, name: null, }; -export { StoryContainer as Story }; +export { Story }; diff --git a/addons/docs/src/blocks/Subtitle.tsx b/addons/docs/src/blocks/Subtitle.tsx index 595bac6452f2..3fb44a8af5a6 100644 --- a/addons/docs/src/blocks/Subtitle.tsx +++ b/addons/docs/src/blocks/Subtitle.tsx @@ -1,19 +1,17 @@ import React, { useContext, FunctionComponent } from 'react'; import { Subtitle as PureSubtitle } from '@storybook/components'; import { DocsContext } from './DocsContext'; -import { StringSlot } from './shared'; interface SubtitleProps { - slot?: StringSlot; children?: JSX.Element | string; } -export const Subtitle: FunctionComponent<SubtitleProps> = ({ slot, children }) => { +export const Subtitle: FunctionComponent<SubtitleProps> = ({ children }) => { const context = useContext(DocsContext); const { parameters } = context; let text: JSX.Element | string = children; if (!text) { - text = slot ? slot(context) : parameters && parameters.componentSubtitle; + text = parameters?.componentSubtitle; } return text ? <PureSubtitle className="sbdocs-subtitle">{text}</PureSubtitle> : null; }; diff --git a/addons/docs/src/blocks/Title.tsx b/addons/docs/src/blocks/Title.tsx index bbc4533bb61e..fd9bf9acbc22 100644 --- a/addons/docs/src/blocks/Title.tsx +++ b/addons/docs/src/blocks/Title.tsx @@ -1,14 +1,12 @@ import React, { useContext, FunctionComponent } from 'react'; import { parseKind } from '@storybook/csf'; import { Title as PureTitle } from '@storybook/components'; -import { DocsContext } from './DocsContext'; -import { StringSlot } from './shared'; +import { DocsContext, DocsContextProps } from './DocsContext'; interface TitleProps { - slot?: StringSlot; children?: JSX.Element | string; } -export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => { +export const extractTitle = ({ kind, parameters }: DocsContextProps) => { const { showRoots, hierarchyRootSeparator: rootSeparator = '|', @@ -17,28 +15,23 @@ export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => { let groups; if (typeof showRoots !== 'undefined') { - groups = selectedKind.split('/'); + groups = kind.split('/'); } else { // This covers off all the remaining cases: // - If the separators were set above, we should use them // - If they weren't set, we should only should use the old defaults if the kind contains '.' or '|', // which for this particular splitting is the only case in which it actually matters. - ({ groups } = parseKind(selectedKind, { rootSeparator, groupSeparator })); + ({ groups } = parseKind(kind, { rootSeparator, groupSeparator })); } - return (groups && groups[groups.length - 1]) || selectedKind; + return (groups && groups[groups.length - 1]) || kind; }; -export const Title: FunctionComponent<TitleProps> = ({ slot, children }) => { +export const Title: FunctionComponent<TitleProps> = ({ children }) => { const context = useContext(DocsContext); - const { selectedKind, parameters } = context; let text: JSX.Element | string = children; if (!text) { - if (slot) { - text = slot(context); - } else { - text = defaultTitleSlot({ selectedKind, parameters }); - } + text = extractTitle(context); } return text ? <PureTitle className="sbdocs-title">{text}</PureTitle> : null; }; diff --git a/addons/docs/src/blocks/enhanceSource.test.ts b/addons/docs/src/blocks/enhanceSource.test.ts new file mode 100644 index 000000000000..393cbd9c2b9c --- /dev/null +++ b/addons/docs/src/blocks/enhanceSource.test.ts @@ -0,0 +1,82 @@ +import { StoryContext } from '@storybook/addons'; +import { enhanceSource } from './enhanceSource'; + +const emptyContext: StoryContext = { + id: 'foo--bar', + kind: 'foo', + name: 'bar', + args: {}, + globalArgs: {}, + parameters: {}, +}; + +const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); + +describe('addon-docs enhanceSource', () => { + describe('no source loaded', () => { + const baseContext = emptyContext; + it('no transformSource', () => { + expect(enhanceSource(baseContext)).toBeNull(); + }); + it('transformSource', () => { + const parameters = { ...baseContext.parameters, docs: { transformSource } }; + expect(enhanceSource({ ...baseContext, parameters })).toBeNull(); + }); + }); + describe('custom/mdx source loaded', () => { + const baseContext = { + ...emptyContext, + parameters: { storySource: { source: 'storySource.source' } }, + }; + it('no transformSource', () => { + expect(enhanceSource(baseContext)).toEqual({ + docs: { source: { code: 'storySource.source' } }, + }); + }); + it('transformSource', () => { + const parameters = { ...baseContext.parameters, docs: { transformSource } }; + expect(enhanceSource({ ...baseContext, parameters }).docs.source).toEqual({ + code: 'formatted: storySource.source', + }); + }); + }); + describe('storysource source loaded w/ locationsMap', () => { + const baseContext = { + ...emptyContext, + parameters: { + storySource: { + source: 'storySource.source', + locationsMap: { + 'foo--bar': { startBody: { line: 1, col: 5 }, endBody: { line: 1, col: 11 } }, + }, + }, + }, + }; + it('no transformSource', () => { + expect(enhanceSource(baseContext)).toEqual({ docs: { source: { code: 'Source' } } }); + }); + it('transformSource', () => { + const parameters = { ...baseContext.parameters, docs: { transformSource } }; + expect(enhanceSource({ ...baseContext, parameters }).docs.source).toEqual({ + code: 'formatted: Source', + }); + }); + }); + describe('custom docs.source provided', () => { + const baseContext = { + ...emptyContext, + parameters: { + storySource: { source: 'storySource.source' }, + docs: { source: { code: 'docs.source.code' } }, + }, + }; + it('no transformSource', () => { + expect(enhanceSource(baseContext)).toBeNull(); + }); + it('transformSource', () => { + const { source } = baseContext.parameters.docs; + const parameters = { ...baseContext.parameters, docs: { source, transformSource } }; + expect(enhanceSource({ ...baseContext, parameters })).toBeNull(); + }); + }); +}); diff --git a/addons/docs/src/blocks/enhanceSource.ts b/addons/docs/src/blocks/enhanceSource.ts new file mode 100644 index 000000000000..77afcbfa707c --- /dev/null +++ b/addons/docs/src/blocks/enhanceSource.ts @@ -0,0 +1,54 @@ +import { combineParameters } from '@storybook/client-api'; +import { StoryContext, Parameters } from '@storybook/addons'; + +interface Location { + line: number; + col: number; +} + +interface StorySource { + source: string; + locationsMap: { [id: string]: { startBody: Location; endBody: Location } }; +} + +const extract = (targetId: string, { source, locationsMap }: StorySource) => { + if (!locationsMap) { + return source; + } + const location = locationsMap[targetId]; + + // FIXME: bad locationsMap generated for module export functions whose titles are overridden + if (!location) return null; + const { startBody: start, endBody: end } = location; + const lines = source.split('\n'); + if (start.line === end.line && lines[start.line - 1] !== undefined) { + return lines[start.line - 1].substring(start.col, end.col); + } + // NOTE: storysource locations are 1-based not 0-based! + const startLine = lines[start.line - 1]; + const endLine = lines[end.line - 1]; + if (startLine === undefined || endLine === undefined) { + return source; + } + return [ + startLine.substring(start.col), + ...lines.slice(start.line, end.line - 1), + endLine.substring(0, end.col), + ].join('\n'); +}; + +export const enhanceSource = (context: StoryContext): Parameters => { + const { id, parameters } = context; + const { storySource, docs = {} } = parameters; + const { transformSource } = docs; + + // no input or user has manually overridden the output + if (!storySource?.source || docs.source?.code) { + return null; + } + + const input = extract(id, storySource); + const code = transformSource ? transformSource(input, id) : input; + + return { docs: combineParameters(docs, { source: { code } }) }; +}; diff --git a/addons/docs/src/blocks/index.ts b/addons/docs/src/blocks/index.ts index f4f5def5d237..00569b5874f7 100644 --- a/addons/docs/src/blocks/index.ts +++ b/addons/docs/src/blocks/index.ts @@ -19,5 +19,5 @@ export * from './Subtitle'; export * from './Title'; export * from './Wrapper'; -export * from './shared'; +export * from './types'; export * from './mdx'; diff --git a/addons/docs/src/blocks/mdx.tsx b/addons/docs/src/blocks/mdx.tsx index ca5225602199..44bed3c9de52 100644 --- a/addons/docs/src/blocks/mdx.tsx +++ b/addons/docs/src/blocks/mdx.tsx @@ -1,11 +1,11 @@ import React, { FC, SyntheticEvent } from 'react'; +import addons from '@storybook/addons'; import { Source } from '@storybook/components'; +import { NAVIGATE_URL } from '@storybook/core-events'; import { Code, components } from '@storybook/components/html'; -import { document, window } from 'global'; -import { isNil } from 'lodash'; +import { document } from 'global'; import { styled } from '@storybook/theming'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { scrollToElement } from './utils'; // Hacky utility for asserting identifiers in MDX Story elements export const assertIsFn = (val: any) => { @@ -16,7 +16,7 @@ export const assertIsFn = (val: any) => { }; // Hacky utilty for adding mdxStoryToId to the default context -export const AddContext: FC<DocsContextProps> = props => { +export const AddContext: FC<DocsContextProps> = (props) => { const { children, ...rest } = props; const parentContext = React.useContext(DocsContext); return ( @@ -48,11 +48,8 @@ export const CodeOrSourceMdx: FC<CodeOrSourceMdxProps> = ({ className, children, ); }; -function generateHrefWithHash(hash: string): string { - const url = new URL(window.parent.location); - const href = `${url.origin}/${url.search}#${hash}`; - - return href; +function navigate(url: string) { + addons.getChannel().emit(NAVIGATE_URL, url); } // @ts-ignore @@ -65,14 +62,12 @@ interface AnchorInPageProps { const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => ( <A href={hash} + target="_self" onClick={(event: SyntheticEvent) => { - event.preventDefault(); - - const hashValue = hash.substring(1); - const element = document.getElementById(hashValue); - if (!isNil(element)) { - window.parent.history.replaceState(null, '', generateHrefWithHash(hashValue)); - scrollToElement(element); + const id = hash.substring(1); + const element = document.getElementById(id); + if (element) { + navigate(hash); } }} > @@ -85,10 +80,10 @@ interface AnchorMdxProps { target: string; } -export const AnchorMdx: FC<AnchorMdxProps> = props => { +export const AnchorMdx: FC<AnchorMdxProps> = (props) => { const { href, target, children, ...rest } = props; - if (!isNil(href)) { + if (href) { // Enable scrolling for in-page anchors. if (href.startsWith('#')) { return <AnchorInPage hash={href}>{children}</AnchorInPage>; @@ -96,11 +91,16 @@ export const AnchorMdx: FC<AnchorMdxProps> = props => { // Links to other pages of SB should use the base URL of the top level iframe instead of the base URL of the preview iframe. if (target !== '_blank') { - const parentUrl = new URL(window.parent.location.href); - const newHref = `${parentUrl.origin}${href}`; - return ( - <A href={newHref} target={target} {...rest}> + <A + href={href} + onClick={(event: SyntheticEvent) => { + event.preventDefault(); + navigate(href); + }} + target={target} + {...rest} + > {children} </A> ); @@ -149,17 +149,19 @@ const HeaderWithOcticonAnchor: FC<HeaderWithOcticonAnchorProps> = ({ }) => { // @ts-ignore const OcticonHeader = OcticonHeaders[as]; + const hash = `#${id}`; return ( <OcticonHeader id={id} {...rest}> <OcticonAnchor aria-hidden="true" - href={generateHrefWithHash(id)} + href={hash} tabIndex={-1} - onClick={() => { + target="_self" + onClick={(event: SyntheticEvent) => { const element = document.getElementById(id); - if (!isNil(element)) { - scrollToElement(element); + if (element) { + navigate(hash); } }} > @@ -180,11 +182,11 @@ interface HeaderMdxProps { id: string; } -export const HeaderMdx: FC<HeaderMdxProps> = props => { +export const HeaderMdx: FC<HeaderMdxProps> = (props) => { const { as, id, children, ...rest } = props; // An id should have been added on every header by the "remark-slug" plugin. - if (!isNil(id)) { + if (id) { return ( <HeaderWithOcticonAnchor as={as} id={id} {...rest}> {children} diff --git a/addons/docs/src/blocks/shared.ts b/addons/docs/src/blocks/shared.ts deleted file mode 100644 index baab2861c309..000000000000 --- a/addons/docs/src/blocks/shared.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PropsTableProps } from '@storybook/components'; - -export const CURRENT_SELECTION = '.'; - -export type Component = any; - -export interface StoryData { - id?: string; - kind?: string; - name?: string; - parameters?: any; -} - -export type DocsStoryProps = StoryData & { - expanded?: boolean; - withToolbar?: boolean; -}; - -export interface SlotContext { - id?: string; - selectedKind?: string; - selectedStory?: string; - parameters?: any; - storyStore?: any; -} - -export type StringSlot = (context: SlotContext) => string; -export type DescriptionSlot = (description: string, context: SlotContext) => string; -export type PropsSlot = (context: SlotContext, component: Component) => PropsTableProps; -export type StorySlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps; - -export type StoriesSlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps[]; - -export interface DocsPageProps { - titleSlot?: StringSlot; - subtitleSlot?: StringSlot; - descriptionSlot?: DescriptionSlot; - primarySlot?: StorySlot; - propsSlot?: PropsSlot; - storiesSlot?: StoriesSlot; -} diff --git a/addons/docs/src/blocks/types.ts b/addons/docs/src/blocks/types.ts new file mode 100644 index 000000000000..93eabc67b867 --- /dev/null +++ b/addons/docs/src/blocks/types.ts @@ -0,0 +1,16 @@ +export const CURRENT_SELECTION = '.'; +export const PRIMARY_STORY = '^'; + +export type Component = any; + +export interface StoryData { + id?: string; + kind?: string; + name?: string; + parameters?: any; +} + +export type DocsStoryProps = StoryData & { + expanded?: boolean; + withToolbar?: boolean; +}; diff --git a/addons/docs/src/blocks/utils.ts b/addons/docs/src/blocks/utils.ts index 92129cca1801..036925af27e5 100644 --- a/addons/docs/src/blocks/utils.ts +++ b/addons/docs/src/blocks/utils.ts @@ -1,18 +1,23 @@ /* eslint-disable no-underscore-dangle */ import { DocsContextProps } from './DocsContext'; -import { StoryData, Component } from './shared'; +import { StoryData, Component } from './types'; export const getDocsStories = (context: DocsContextProps): StoryData[] => { - const { storyStore, selectedKind } = context; + const { storyStore, kind } = context; + + if (!storyStore) { + return []; + } + return storyStore - .getStoriesForKind(selectedKind) + .getStoriesForKind(kind) .filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable)); }; const titleCase = (str: string): string => str .split('-') - .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(''); export const getComponentName = (component: Component): string => { diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/argtypes.snapshot b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/argtypes.snapshot new file mode 100644 index 000000000000..2eb0d33f0cba --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/argtypes.snapshot @@ -0,0 +1,294 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`angular component properties doc-button 1`] = ` +Object { + "_inputValue": Object { + "defaultValue": "some value", + "description": "", + "name": "_inputValue", + "table": Object { + "category": "properties", + "type": Object { + "required": true, + "summary": "string", + }, + }, + "type": Object { + "name": "void", + }, + }, + "_value": Object { + "defaultValue": "Private hello", + "description": "<p>Private value. </p> +", + "name": "_value", + "table": Object { + "category": "properties", + "type": Object { + "required": true, + "summary": "string", + }, + }, + "type": Object { + "name": "void", + }, + }, + "appearance": Object { + "defaultValue": "secondary", + "description": "<p>Appearance style of the button. </p> +", + "name": "appearance", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "\\"primary\\" | \\"secondary\\"", + }, + }, + "type": Object { + "name": "enum", + "value": Array [ + "primary", + "secondary", + ], + }, + }, + "buttonRef": Object { + "defaultValue": undefined, + "description": "", + "name": "buttonRef", + "table": Object { + "category": "view child", + "type": Object { + "required": true, + "summary": "ElementRef", + }, + }, + "type": Object { + "name": "void", + }, + }, + "calc": Object { + "defaultValue": undefined, + "description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p> +", + "name": "calc", + "table": Object { + "category": "methods", + "type": Object { + "required": false, + "summary": "(x: number, y: string | number) => number", + }, + }, + "type": Object { + "name": "void", + }, + }, + "inputValue": Object { + "defaultValue": undefined, + "description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p> +", + "name": "inputValue", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "string", + }, + }, + "type": Object { + "name": "string", + }, + }, + "internalProperty": Object { + "defaultValue": "Public hello", + "description": "<p>Public value. </p> +", + "name": "internalProperty", + "table": Object { + "category": "properties", + "type": Object { + "required": true, + "summary": "string", + }, + }, + "type": Object { + "name": "void", + }, + }, + "isDisabled": Object { + "defaultValue": false, + "description": "<p>Sets the button to a disabled state. </p> +", + "name": "isDisabled", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": undefined, + }, + }, + "type": Object { + "name": "boolean", + }, + }, + "item": Object { + "defaultValue": undefined, + "description": undefined, + "name": "item", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "[]", + }, + }, + "type": Object { + "name": "object", + }, + }, + "label": Object { + "defaultValue": undefined, + "description": "<p>The inner text of the button.</p> +", + "name": "label", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "string", + }, + }, + "type": Object { + "name": "string", + }, + }, + "onClick": Object { + "defaultValue": undefined, + "description": "<p>Handler to be called when the button is clicked by a user.</p> +<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p> +", + "name": "onClick", + "table": Object { + "category": "outputs", + "type": Object { + "required": true, + "summary": "EventEmitter", + }, + }, + "type": Object { + "name": "void", + }, + }, + "privateMethod": Object { + "defaultValue": undefined, + "description": "<p>A private method.</p> +", + "name": "privateMethod", + "table": Object { + "category": "methods", + "type": Object { + "required": false, + "summary": "(password: string) => void", + }, + }, + "type": Object { + "name": "void", + }, + }, + "processedItem": Object { + "defaultValue": undefined, + "description": "", + "name": "processedItem", + "table": Object { + "category": "properties", + "type": Object { + "required": true, + "summary": "T[]", + }, + }, + "type": Object { + "name": "void", + }, + }, + "protectedMethod": Object { + "defaultValue": undefined, + "description": "<p>A protected method.</p> +", + "name": "protectedMethod", + "table": Object { + "category": "methods", + "type": Object { + "required": false, + "summary": "(id?: number) => void", + }, + }, + "type": Object { + "name": "void", + }, + }, + "publicMethod": Object { + "defaultValue": undefined, + "description": "<p>A public method using an interface. </p> +", + "name": "publicMethod", + "table": Object { + "category": "methods", + "type": Object { + "required": false, + "summary": "(things: ISomeInterface) => void", + }, + }, + "type": Object { + "name": "void", + }, + }, + "showKeyAlias": Object { + "defaultValue": undefined, + "description": undefined, + "name": "showKeyAlias", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "", + }, + }, + "type": Object { + "name": "void", + }, + }, + "size": Object { + "defaultValue": "medium", + "description": "<p>Size of the button. </p> +", + "name": "size", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": "ButtonSize", + }, + }, + "type": Object { + "name": "object", + }, + }, + "somethingYouShouldNotUse": Object { + "defaultValue": false, + "description": "<p>Some input you shouldn't use.</p> +", + "name": "somethingYouShouldNotUse", + "table": Object { + "category": "inputs", + "type": Object { + "required": true, + "summary": undefined, + }, + }, + "type": Object { + "name": "boolean", + }, + }, +} +`; diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc.snapshot b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc.snapshot new file mode 100644 index 000000000000..658e8698ef3e --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc.snapshot @@ -0,0 +1,1020 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`angular component properties doc-button 1`] = ` +Object { + "classes": Array [], + "components": Array [ + Object { + "accessors": Object { + "inputValue": Object { + "getSignature": Object { + "description": "<p>Getter for <code>inputValue</code>. </p> +", + "line": 102, + "name": "inputValue", + "returnType": "", + "type": "", + }, + "name": "inputValue", + "setSignature": Object { + "args": Array [ + Object { + "name": "value", + "type": "string", + }, + ], + "description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p> +", + "jsdoctags": Array [ + Object { + "name": "value", + "tagName": Object { + "text": "param", + }, + "type": "string", + }, + ], + "line": 97, + "name": "inputValue", + "returnType": "void", + "type": "void", + }, + }, + "item": Object { + "name": "item", + "setSignature": Object { + "args": Array [ + Object { + "name": "item", + "type": "[]", + }, + ], + "jsdoctags": Array [ + Object { + "name": "item", + "tagName": Object { + "text": "param", + }, + "type": "[]", + }, + ], + "line": 182, + "name": "item", + "returnType": "void", + "type": "void", + }, + }, + "value": Object { + "getSignature": Object { + "description": "<p>Get the private value. </p> +", + "line": 141, + "name": "value", + "returnType": "string | number", + "type": "", + }, + "name": "value", + "setSignature": Object { + "args": Array [ + Object { + "name": "value", + "type": "", + }, + ], + "description": "<p>Set the private value. </p> +", + "jsdoctags": Array [ + Object { + "name": "value", + "tagName": Object { + "text": "param", + }, + "type": "", + }, + ], + "line": 136, + "name": "value", + "returnType": "void", + "type": "void", + }, + }, + }, + "assetsDirs": Array [], + "description": "<p>This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular.</p> +<p>It supports <a href=\\"https://en.wikipedia.org/wiki/Markdown\\">markdown</a>, so you can embed formatted text, +like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p> +<blockquote> +<p>How you like dem apples?! It's never been easier to document all your components.</p> +</blockquote> +", + "encapsulation": Array [], + "entryComponents": Array [], + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "hostBindings": Array [ + Object { + "defaultValue": "false", + "line": 111, + "name": "class.focused", + }, + ], + "hostListeners": Array [ + Object { + "args": Array [ + Object { + "name": "btn", + "type": "", + }, + ], + "argsDecorator": Array [ + "$event.target", + ], + "line": 107, + "name": "click", + }, + ], + "id": "component-InputComponent-8088cb533b6defa9d43d10841b59ce18", + "inputs": Array [], + "inputsClass": Array [ + Object { + "defaultValue": "'secondary'", + "description": "<p>Appearance style of the button. </p> +", + "line": 46, + "name": "appearance", + "type": "\\"primary\\" | \\"secondary\\"", + }, + Object { + "description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p> +", + "line": 97, + "name": "inputValue", + "type": "string", + }, + Object { + "defaultValue": "false", + "description": "<p>Sets the button to a disabled state. </p> +", + "line": 50, + "name": "isDisabled", + }, + Object { + "line": 182, + "name": "item", + "type": "[]", + }, + Object { + "description": "<p>The inner text of the button.</p> +", + "line": 58, + "name": "label", + "type": "string", + }, + Object { + "line": 179, + "name": "showKeyAlias", + "type": "", + }, + Object { + "defaultValue": "'medium'", + "description": "<p>Size of the button. </p> +", + "line": 62, + "name": "size", + "type": "ButtonSize", + }, + Object { + "defaultValue": "false", + "description": "<p>Some input you shouldn't use.</p> +", + "line": 70, + "name": "somethingYouShouldNotUse", + }, + ], + "jsdoctags": Array [ + Object { + "atToken": Object { + "end": 787, + "flags": 0, + "kind": 57, + "pos": 786, + }, + "comment": "Hello world", + "end": 794, + "flags": 0, + "kind": 288, + "pos": 786, + "tagName": Object { + "end": 793, + "escapedText": "string", + "flags": 0, + "pos": 787, + }, + }, + Object { + "atToken": Object { + "end": 810, + "flags": 0, + "kind": 57, + "pos": 809, + }, + "comment": "[Example](http://example.com)", + "end": 815, + "flags": 0, + "kind": 288, + "pos": 809, + "tagName": Object { + "end": 814, + "escapedText": "link", + "flags": 0, + "pos": 810, + }, + }, + Object { + "atToken": Object { + "end": 849, + "flags": 0, + "kind": 57, + "pos": 848, + }, + "comment": "\`ThingThing\`", + "end": 854, + "flags": 0, + "kind": 288, + "pos": 848, + "tagName": Object { + "end": 853, + "escapedText": "code", + "flags": 0, + "pos": 849, + }, + }, + Object { + "atToken": Object { + "end": 871, + "flags": 0, + "kind": 57, + "pos": 870, + }, + "comment": "<span class=\\"badge\\">aaa</span>", + "end": 876, + "flags": 0, + "kind": 288, + "pos": 870, + "tagName": Object { + "end": 875, + "escapedText": "html", + "flags": 0, + "pos": 871, + }, + }, + ], + "methodsClass": Array [ + Object { + "args": Array [ + Object { + "name": "x", + "type": "number", + }, + Object { + "name": "y", + "type": "string | number", + }, + ], + "description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p> +", + "jsdoctags": Array [ + Object { + "comment": "<p>Some number you'd like to use.</p> +", + "name": Object { + "end": 3219, + "escapedText": "x", + "flags": 0, + "pos": 3218, + }, + "tagName": Object { + "end": 3217, + "escapedText": "param", + "flags": 0, + "pos": 3212, + }, + "type": "number", + }, + Object { + "comment": "<p>Some other number or string you'd like to use, will have <code>parseInt()</code> applied before calculation.</p> +", + "name": Object { + "end": 3264, + "escapedText": "y", + "flags": 0, + "pos": 3263, + }, + "tagName": Object { + "end": 3262, + "escapedText": "param", + "flags": 0, + "pos": 3257, + }, + "type": "string | number", + }, + ], + "line": 151, + "modifierKind": Array [ + 114, + ], + "name": "calc", + "optional": false, + "returnType": "number", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "name": "password", + "type": "string", + }, + ], + "description": "<p>A private method.</p> +", + "jsdoctags": Array [ + Object { + "comment": "<p>Some <code>password</code>.</p> +", + "name": Object { + "end": 3780, + "escapedText": "password", + "flags": 0, + "pos": 3772, + }, + "tagName": Object { + "end": 3771, + "escapedText": "param", + "flags": 0, + "pos": 3766, + }, + "type": "string", + }, + ], + "line": 174, + "modifierKind": Array [ + 112, + ], + "name": "privateMethod", + "optional": false, + "returnType": "void", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "name": "id", + "optional": true, + "type": "number", + }, + ], + "description": "<p>A protected method.</p> +", + "jsdoctags": Array [ + Object { + "comment": "<p>Some <code>id</code>.</p> +", + "name": Object { + "end": 3639, + "escapedText": "id", + "flags": 0, + "pos": 3637, + }, + "optional": true, + "tagName": Object { + "end": 3636, + "escapedText": "param", + "flags": 0, + "pos": 3631, + }, + "type": "number", + }, + ], + "line": 165, + "modifierKind": Array [ + 113, + ], + "name": "protectedMethod", + "optional": false, + "returnType": "void", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "name": "things", + "type": "ISomeInterface", + }, + ], + "description": "<p>A public method using an interface. </p> +", + "jsdoctags": Array [ + Object { + "name": "things", + "tagName": Object { + "text": "param", + }, + "type": "ISomeInterface", + }, + ], + "line": 156, + "modifierKind": Array [ + 114, + ], + "name": "publicMethod", + "optional": false, + "returnType": "void", + "typeParameters": Array [], + }, + ], + "name": "InputComponent", + "outputs": Array [], + "outputsClass": Array [ + Object { + "defaultValue": "new EventEmitter<Event>()", + "description": "<p>Handler to be called when the button is clicked by a user.</p> +<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p> +", + "line": 78, + "name": "onClick", + "type": "EventEmitter", + }, + ], + "propertiesClass": Array [ + Object { + "defaultValue": "'some value'", + "description": "", + "line": 93, + "modifierKind": Array [ + 112, + ], + "name": "_inputValue", + "optional": false, + "type": "string", + }, + Object { + "defaultValue": "'Private hello'", + "description": "<p>Private value. </p> +", + "line": 133, + "modifierKind": Array [ + 112, + ], + "name": "_value", + "optional": false, + "type": "string", + }, + Object { + "decorators": Array [ + Object { + "name": "ViewChild", + "stringifiedArguments": "'buttonRef', {static: false}", + }, + ], + "description": "", + "line": 42, + "name": "buttonRef", + "optional": false, + "type": "ElementRef", + }, + Object { + "defaultValue": "'Public hello'", + "description": "<p>Public value. </p> +", + "line": 130, + "modifierKind": Array [ + 114, + ], + "name": "internalProperty", + "optional": false, + "type": "string", + }, + Object { + "description": "", + "line": 186, + "modifierKind": Array [ + 114, + ], + "name": "processedItem", + "optional": false, + "type": "T[]", + }, + ], + "providers": Array [], + "rawdescription": "This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + +It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, +like **bold**, _italic_, and \`inline code\`. + +> How you like dem apples?! It's never been easier to document all your components.", + "selector": "doc-button", + "sourceCode": "import { + Component, + EventEmitter, + Input, + Output, + ViewChild, + HostListener, + HostBinding, + ElementRef, +} from '@angular/core'; + +export const exportedConstant = 'An exported constant'; + +export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge'; + +export interface ISomeInterface { + one: string; + two: boolean; + three: any[]; +} + +/** + * This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + * + * It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, + * like **bold**, _italic_, and \`inline code\`. + * + * > How you like dem apples?! It's never been easier to document all your components. + * + * @string Hello world + * @link [Example](http://example.com) + * @code \`ThingThing\` + * @html <span class=\\"badge\\">aaa</span> + */ +@Component({ + selector: 'doc-button', +}) +export class InputComponent<T> { + @ViewChild('buttonRef', { static: false }) buttonRef: ElementRef; + + /** Appearance style of the button. */ + @Input() + public appearance: 'primary' | 'secondary' = 'secondary'; + + /** Sets the button to a disabled state. */ + @Input() + public isDisabled = false; + + /** + * The inner text of the button. + * + * @required + */ + @Input() + public label: string; + + /** Size of the button. */ + @Input() + public size?: ButtonSize = 'medium'; + + /** + * Some input you shouldn't use. + * + * @deprecated + */ + @Input() + public somethingYouShouldNotUse = false; + + /** + * Handler to be called when the button is clicked by a user. + * + * Will also block the emission of the event if \`isDisabled\` is true. + */ + @Output() + public onClick = new EventEmitter<Event>(); + + /** + * This is an internal method that we don't want to document and have added the \`ignore\` annoation to. + * + * @ignore + */ + public handleClick(event: Event) { + event.stopPropagation(); + + if (!this.isDisabled) { + this.onClick.emit(event); + } + } + + private _inputValue = 'some value'; + + /** Setter for \`inputValue\` that is also an \`@Input\`. */ + @Input() + public set inputValue(value: string) { + this._inputValue = value; + } + + /** Getter for \`inputValue\`. */ + public get inputValue() { + return this._inputValue; + } + + @HostListener('click', ['$event.target']) + onClickListener(btn) { + console.log('button', btn); + } + + @HostBinding('class.focused') focus = false; + + /** + * Returns all the CSS classes for the button. + * + * @ignore + */ + public get classes(): string[] { + return [this.appearance, this.size] + .filter((_class) => !!_class) + .map((_class) => \`btn-\${_class}\`); + } + + /** + * @ignore + */ + public ignoredProperty = 'Ignore me'; + + /** Public value. */ + public internalProperty = 'Public hello'; + + /** Private value. */ + private _value = 'Private hello'; + + /** Set the private value. */ + public set value(value: string | number) { + this._value = \`\${value}\`; + } + + /** Get the private value. */ + public get value(): string | number { + return this._value; + } + + /** + * An internal calculation method which adds \`x\` and \`y\` together. + * + * @param x Some number you'd like to use. + * @param y Some other number or string you'd like to use, will have \`parseInt()\` applied before calculation. + */ + public calc(x: number, y: string | number): number { + return x + parseInt(\`\${y}\`, 10); + } + + /** A public method using an interface. */ + public publicMethod(things: ISomeInterface) { + console.log(things); + } + + /** + * A protected method. + * + * @param id Some \`id\`. + */ + protected protectedMethod(id?: number) { + console.log(id); + } + + /** + * A private method. + * + * @param password Some \`password\`. + */ + private privateMethod(password: string) { + console.log(password); + } + + @Input('showKeyAlias') + public showKey: keyof T; + + @Input() + public set item(item: T[]) { + this.processedItem = item; + } + + public processedItem: T[]; +} +", + "styleUrls": Array [], + "styleUrlsData": "", + "styles": Array [], + "stylesData": "", + "templateUrl": Array [], + "type": "component", + "viewProviders": Array [], + }, + ], + "coverage": Object { + "count": 22, + "files": Array [ + Object { + "coverageCount": "14/21", + "coveragePercent": 66, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linktype": "component", + "name": "InputComponent", + "status": "good", + "type": "component", + }, + Object { + "coverageCount": "0/4", + "coveragePercent": 0, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linktype": "interface", + "name": "ISomeInterface", + "status": "low", + "type": "interface", + }, + Object { + "coverageCount": "0/1", + "coveragePercent": 0, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linksubtype": "variable", + "linktype": "miscellaneous", + "name": "exportedConstant", + "status": "low", + "type": "variable", + }, + ], + "status": "low", + }, + "directives": Array [], + "injectables": Array [], + "interfaces": Array [ + Object { + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "id": "interface-ISomeInterface-8088cb533b6defa9d43d10841b59ce18", + "indexSignatures": Array [], + "kind": 150, + "methods": Array [], + "name": "ISomeInterface", + "properties": Array [ + Object { + "description": "", + "line": 20, + "name": "one", + "optional": false, + "type": "string", + }, + Object { + "description": "", + "line": 22, + "name": "three", + "optional": false, + "type": "any[]", + }, + Object { + "description": "", + "line": 21, + "name": "two", + "optional": false, + "type": "boolean", + }, + ], + "sourceCode": "import { + Component, + EventEmitter, + Input, + Output, + ViewChild, + HostListener, + HostBinding, + ElementRef, +} from '@angular/core'; + +export const exportedConstant = 'An exported constant'; + +export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge'; + +export interface ISomeInterface { + one: string; + two: boolean; + three: any[]; +} + +/** + * This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + * + * It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, + * like **bold**, _italic_, and \`inline code\`. + * + * > How you like dem apples?! It's never been easier to document all your components. + * + * @string Hello world + * @link [Example](http://example.com) + * @code \`ThingThing\` + * @html <span class=\\"badge\\">aaa</span> + */ +@Component({ + selector: 'doc-button', +}) +export class InputComponent<T> { + @ViewChild('buttonRef', { static: false }) buttonRef: ElementRef; + + /** Appearance style of the button. */ + @Input() + public appearance: 'primary' | 'secondary' = 'secondary'; + + /** Sets the button to a disabled state. */ + @Input() + public isDisabled = false; + + /** + * The inner text of the button. + * + * @required + */ + @Input() + public label: string; + + /** Size of the button. */ + @Input() + public size?: ButtonSize = 'medium'; + + /** + * Some input you shouldn't use. + * + * @deprecated + */ + @Input() + public somethingYouShouldNotUse = false; + + /** + * Handler to be called when the button is clicked by a user. + * + * Will also block the emission of the event if \`isDisabled\` is true. + */ + @Output() + public onClick = new EventEmitter<Event>(); + + /** + * This is an internal method that we don't want to document and have added the \`ignore\` annoation to. + * + * @ignore + */ + public handleClick(event: Event) { + event.stopPropagation(); + + if (!this.isDisabled) { + this.onClick.emit(event); + } + } + + private _inputValue = 'some value'; + + /** Setter for \`inputValue\` that is also an \`@Input\`. */ + @Input() + public set inputValue(value: string) { + this._inputValue = value; + } + + /** Getter for \`inputValue\`. */ + public get inputValue() { + return this._inputValue; + } + + @HostListener('click', ['$event.target']) + onClickListener(btn) { + console.log('button', btn); + } + + @HostBinding('class.focused') focus = false; + + /** + * Returns all the CSS classes for the button. + * + * @ignore + */ + public get classes(): string[] { + return [this.appearance, this.size] + .filter((_class) => !!_class) + .map((_class) => \`btn-\${_class}\`); + } + + /** + * @ignore + */ + public ignoredProperty = 'Ignore me'; + + /** Public value. */ + public internalProperty = 'Public hello'; + + /** Private value. */ + private _value = 'Private hello'; + + /** Set the private value. */ + public set value(value: string | number) { + this._value = \`\${value}\`; + } + + /** Get the private value. */ + public get value(): string | number { + return this._value; + } + + /** + * An internal calculation method which adds \`x\` and \`y\` together. + * + * @param x Some number you'd like to use. + * @param y Some other number or string you'd like to use, will have \`parseInt()\` applied before calculation. + */ + public calc(x: number, y: string | number): number { + return x + parseInt(\`\${y}\`, 10); + } + + /** A public method using an interface. */ + public publicMethod(things: ISomeInterface) { + console.log(things); + } + + /** + * A protected method. + * + * @param id Some \`id\`. + */ + protected protectedMethod(id?: number) { + console.log(id); + } + + /** + * A private method. + * + * @param password Some \`password\`. + */ + private privateMethod(password: string) { + console.log(password); + } + + @Input('showKeyAlias') + public showKey: keyof T; + + @Input() + public set item(item: T[]) { + this.processedItem = item; + } + + public processedItem: T[]; +} +", + "type": "interface", + }, + ], + "miscellaneous": Object { + "enumerations": Array [], + "functions": Array [], + "groupedEnumerations": Object {}, + "groupedFunctions": Object {}, + "groupedTypeAliases": Object { + "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [ + Object { + "ctype": "miscellaneous", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "kind": 168, + "name": "ButtonSize", + "rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"", + "subtype": "typealias", + }, + ], + }, + "groupedVariables": Object { + "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [ + Object { + "ctype": "miscellaneous", + "defaultValue": "'An exported constant'", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "exportedConstant", + "subtype": "variable", + "type": "string", + }, + ], + }, + "typealiases": Array [ + Object { + "ctype": "miscellaneous", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "kind": 168, + "name": "ButtonSize", + "rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"", + "subtype": "typealias", + }, + ], + "variables": Array [ + Object { + "ctype": "miscellaneous", + "defaultValue": "'An exported constant'", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "exportedConstant", + "subtype": "variable", + "type": "string", + }, + ], + }, + "modules": Array [], + "pipes": Array [], + "routes": Array [], +} +`; diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts new file mode 100644 index 000000000000..8f705e01e255 --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts @@ -0,0 +1,187 @@ +// @ts-nocheck +/* eslint-disable no-console */ +/* eslint-disable no-underscore-dangle */ +import { + Component, + EventEmitter, + Input, + Output, + ViewChild, + HostListener, + HostBinding, + ElementRef, +} from '@angular/core'; + +export const exportedConstant = 'An exported constant'; + +export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge'; + +export interface ISomeInterface { + one: string; + two: boolean; + three: any[]; +} + +/** + * This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + * + * It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, + * like **bold**, _italic_, and `inline code`. + * + * > How you like dem apples?! It's never been easier to document all your components. + * + * @string Hello world + * @link [Example](http://example.com) + * @code `ThingThing` + * @html <span class="badge">aaa</span> + */ +@Component({ + selector: 'doc-button', +}) +export class InputComponent<T> { + @ViewChild('buttonRef', { static: false }) buttonRef: ElementRef; + + /** Appearance style of the button. */ + @Input() + public appearance: 'primary' | 'secondary' = 'secondary'; + + /** Sets the button to a disabled state. */ + @Input() + public isDisabled = false; + + /** + * The inner text of the button. + * + * @required + */ + @Input() + public label: string; + + /** Size of the button. */ + @Input() + public size?: ButtonSize = 'medium'; + + /** + * Some input you shouldn't use. + * + * @deprecated + */ + @Input() + public somethingYouShouldNotUse = false; + + /** + * Handler to be called when the button is clicked by a user. + * + * Will also block the emission of the event if `isDisabled` is true. + */ + @Output() + public onClick = new EventEmitter<Event>(); + + /** + * This is an internal method that we don't want to document and have added the `ignore` annoation to. + * + * @ignore + */ + public handleClick(event: Event) { + event.stopPropagation(); + + if (!this.isDisabled) { + this.onClick.emit(event); + } + } + + private _inputValue = 'some value'; + + /** Setter for `inputValue` that is also an `@Input`. */ + @Input() + public set inputValue(value: string) { + this._inputValue = value; + } + + /** Getter for `inputValue`. */ + public get inputValue() { + return this._inputValue; + } + + @HostListener('click', ['$event.target']) + onClickListener(btn) { + console.log('button', btn); + } + + @HostBinding('class.focused') focus = false; + + /** + * Returns all the CSS classes for the button. + * + * @ignore + */ + public get classes(): string[] { + return [this.appearance, this.size] + .filter((_class) => !!_class) + .map((_class) => `btn-${_class}`); + } + + /** + * @ignore + */ + public ignoredProperty = 'Ignore me'; + + /** Public value. */ + public internalProperty = 'Public hello'; + + /** Private value. */ + private _value = 'Private hello'; + + /** Set the private value. */ + public set value(value: string | number) { + this._value = `${value}`; + } + + /** Get the private value. */ + public get value(): string | number { + return this._value; + } + + /** + * An internal calculation method which adds `x` and `y` together. + * + * @param x Some number you'd like to use. + * @param y Some other number or string you'd like to use, will have `parseInt()` applied before calculation. + */ + public calc(x: number, y: string | number): number { + return x + parseInt(`${y}`, 10); + } + + /** A public method using an interface. */ + public publicMethod(things: ISomeInterface) { + console.log(things); + } + + /** + * A protected method. + * + * @param id Some `id`. + */ + protected protectedMethod(id?: number) { + console.log(id); + } + + /** + * A private method. + * + * @param password Some `password`. + */ + private privateMethod(password: string) { + console.log(password); + } + + @Input('showKeyAlias') + public showKey: keyof T; + + @Input() + public set item(item: T[]) { + this.processedItem = item; + } + + public processedItem: T[]; +} diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/properties.snapshot b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/properties.snapshot new file mode 100644 index 000000000000..efd774f746b2 --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/properties.snapshot @@ -0,0 +1,230 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`angular component properties doc-button 1`] = ` +Object { + "sections": Object { + "inputs": Array [ + Object { + "defaultValue": Object { + "summary": "'secondary'", + }, + "description": "<p>Appearance style of the button. </p> +", + "name": "appearance", + "required": true, + "type": Object { + "summary": "\\"primary\\" | \\"secondary\\"", + }, + }, + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p> +", + "name": "inputValue", + "required": true, + "type": Object { + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "summary": "false", + }, + "description": "<p>Sets the button to a disabled state. </p> +", + "name": "isDisabled", + "required": true, + "type": Object { + "summary": undefined, + }, + }, + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": undefined, + "name": "item", + "required": true, + "type": Object { + "summary": "[]", + }, + }, + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": "<p>The inner text of the button.</p> +", + "name": "label", + "required": true, + "type": Object { + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": undefined, + "name": "showKeyAlias", + "required": true, + "type": Object { + "summary": "", + }, + }, + Object { + "defaultValue": Object { + "summary": "'medium'", + }, + "description": "<p>Size of the button. </p> +", + "name": "size", + "required": true, + "type": Object { + "summary": "ButtonSize", + }, + }, + Object { + "defaultValue": Object { + "summary": "false", + }, + "description": "<p>Some input you shouldn't use.</p> +", + "name": "somethingYouShouldNotUse", + "required": true, + "type": Object { + "summary": undefined, + }, + }, + ], + "methods": Array [ + Object { + "defaultValue": Object { + "summary": "", + }, + "description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p> +", + "name": "calc", + "required": false, + "type": Object { + "summary": "(x: number, y: string | number) => number", + }, + }, + Object { + "defaultValue": Object { + "summary": "", + }, + "description": "<p>A private method.</p> +", + "name": "privateMethod", + "required": false, + "type": Object { + "summary": "(password: string) => void", + }, + }, + Object { + "defaultValue": Object { + "summary": "", + }, + "description": "<p>A protected method.</p> +", + "name": "protectedMethod", + "required": false, + "type": Object { + "summary": "(id?: number) => void", + }, + }, + Object { + "defaultValue": Object { + "summary": "", + }, + "description": "<p>A public method using an interface. </p> +", + "name": "publicMethod", + "required": false, + "type": Object { + "summary": "(things: ISomeInterface) => void", + }, + }, + ], + "outputs": Array [ + Object { + "defaultValue": Object { + "summary": "new EventEmitter<Event>()", + }, + "description": "<p>Handler to be called when the button is clicked by a user.</p> +<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p> +", + "name": "onClick", + "required": true, + "type": Object { + "summary": "EventEmitter", + }, + }, + ], + "properties": Array [ + Object { + "defaultValue": Object { + "summary": "'some value'", + }, + "description": "", + "name": "_inputValue", + "required": true, + "type": Object { + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "summary": "'Private hello'", + }, + "description": "<p>Private value. </p> +", + "name": "_value", + "required": true, + "type": Object { + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "summary": "'Public hello'", + }, + "description": "<p>Public value. </p> +", + "name": "internalProperty", + "required": true, + "type": Object { + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": "", + "name": "processedItem", + "required": true, + "type": Object { + "summary": "T[]", + }, + }, + ], + "view child": Array [ + Object { + "defaultValue": Object { + "summary": undefined, + }, + "description": "", + "name": "buttonRef", + "required": true, + "type": Object { + "summary": "ElementRef", + }, + }, + ], + }, +} +`; diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/tsconfig.json b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/tsconfig.json new file mode 100644 index 000000000000..ced6b7ae2f7c --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["./*.ts"] +} diff --git a/addons/docs/src/frameworks/angular/angular-properties.test.ts b/addons/docs/src/frameworks/angular/angular-properties.test.ts new file mode 100644 index 000000000000..d914edb10152 --- /dev/null +++ b/addons/docs/src/frameworks/angular/angular-properties.test.ts @@ -0,0 +1,54 @@ +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs'; +import tmp from 'tmp'; +import { sync as spawnSync } from 'cross-spawn'; + +import { findComponentByName, extractArgTypesFromData } from './compodoc'; + +// File hierarchy: __testfixtures__ / some-test-case / input.* +const inputRegExp = /^input\..*$/; + +const runCompodoc = (inputPath: string) => { + const testDir = path.dirname(inputPath); + const { name: tmpDir, removeCallback } = tmp.dirSync(); + + // FIXME: for now, this requires a tsconfig.json for each test case. Tried generating + // one dynamically in tmpDir, but compodoc doesn't handle absolute paths properly + // (and screwed around with relative paths as well, but couldn't get it working) + spawnSync('compodoc', ['-p', `${testDir}/tsconfig.json`, '-e', 'json', '-d', tmpDir], { + stdio: 'inherit', + }); + const output = fs.readFileSync(`${tmpDir}/documentation.json`, 'utf8'); + try { + removeCallback(); + } catch (e) { + // + } + return output; +}; + +describe('angular component properties', () => { + const fixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(fixturesDir, { withFileTypes: true }).forEach((testEntry) => { + if (testEntry.isDirectory()) { + const testDir = path.join(fixturesDir, testEntry.name); + const testFile = fs.readdirSync(testDir).find((fileName) => inputRegExp.test(fileName)); + if (testFile) { + it(testEntry.name, () => { + const inputPath = path.join(testDir, testFile); + + // snapshot the output of compodoc + const compodocOutput = runCompodoc(inputPath); + const compodocJson = JSON.parse(compodocOutput); + expect(compodocJson).toMatchSpecificSnapshot(path.join(testDir, 'compodoc.snapshot')); + + // snapshot the output of addon-docs angular-properties + const componentData = findComponentByName('InputComponent', compodocJson); + const argTypes = extractArgTypesFromData(componentData); + expect(argTypes).toMatchSpecificSnapshot(path.join(testDir, 'argtypes.snapshot')); + }); + } + } + }); +}); diff --git a/addons/docs/src/frameworks/angular/compodoc.test.ts b/addons/docs/src/frameworks/angular/compodoc.test.ts new file mode 100644 index 000000000000..6e1590c4bc74 --- /dev/null +++ b/addons/docs/src/frameworks/angular/compodoc.test.ts @@ -0,0 +1,41 @@ +import { extractType } from './compodoc'; +import { Decorator } from './types'; + +const makeProperty = (compodocType?: string) => ({ + type: compodocType, + name: 'dummy', + decorators: [] as Decorator[], + optional: true, +}); + +describe('extractType', () => { + describe('with compodoc type', () => { + it.each([ + ['string', { name: 'string' }], + ['boolean', { name: 'boolean' }], + ['number', { name: 'number' }], + ['object', { name: 'object' }], + ['foo', { name: 'object' }], + [null, { name: 'void' }], + [undefined, { name: 'void' }], + ['T[]', { name: 'object' }], + ['[]', { name: 'object' }], + ['"primary" | "secondary"', { name: 'enum', value: ['primary', 'secondary'] }], + ])('%s', (compodocType, expected) => { + expect(extractType(makeProperty(compodocType), null)).toEqual(expected); + }); + }); + + describe('without compodoc type', () => { + it.each([ + ['string', { name: 'string' }], + [false, { name: 'boolean' }], + [10, { name: 'number' }], + [['abc'], { name: 'object' }], + [{ foo: 1 }, { name: 'object' }], + [undefined, { name: 'void' }], + ])('%s', (defaultValue, expected) => { + expect(extractType(makeProperty(null), defaultValue)).toEqual(expected); + }); + }); +}); diff --git a/addons/docs/src/frameworks/angular/compodoc.ts b/addons/docs/src/frameworks/angular/compodoc.ts index 8362919f1755..8810a8a799f2 100644 --- a/addons/docs/src/frameworks/angular/compodoc.ts +++ b/addons/docs/src/frameworks/angular/compodoc.ts @@ -2,7 +2,19 @@ /* global window */ import { PropDef } from '@storybook/components'; -import { Argument, CompodocJson, Component, Method, Property, Directive } from './types'; +import { ArgType, ArgTypes } from '@storybook/api'; +import { logger } from '@storybook/client-logger'; +import { + Argument, + Class, + CompodocJson, + Component, + Injectable, + Method, + Pipe, + Property, + Directive, +} from './types'; type Sections = Record<string, PropDef[]>; @@ -30,10 +42,6 @@ export const checkValidCompodocJson = (compodocJson: CompodocJson) => { } }; -function isEmpty(obj: any) { - return Object.entries(obj).length === 0 && obj.constructor === Object; -} - const hasDecorator = (item: Property, decoratorName: string) => item.decorators && item.decorators.find((x: any) => x.name === decoratorName); @@ -55,12 +63,14 @@ const mapPropertyToSection = (key: string, item: Property) => { const mapItemToSection = (key: string, item: Method | Property): string => { switch (key) { + case 'methods': case 'methodsClass': return 'methods'; case 'inputsClass': return 'inputs'; case 'outputsClass': return 'outputs'; + case 'properties': case 'propertiesClass': if (isMethod(item)) { throw new Error("Cannot be of type Method if key === 'propertiesClass'"); @@ -71,6 +81,13 @@ const mapItemToSection = (key: string, item: Method | Property): string => { } }; +export const findComponentByName = (name: string, compodocJson: CompodocJson) => + compodocJson.components.find((c: Component) => c.name === name) || + compodocJson.directives.find((c: Directive) => c.name === name) || + compodocJson.pipes.find((c: Pipe) => c.name === name) || + compodocJson.injectables.find((c: Injectable) => c.name === name) || + compodocJson.classes.find((c: Class) => c.name === name); + const getComponentData = (component: Component | Directive) => { if (!component) { return null; @@ -79,10 +96,7 @@ const getComponentData = (component: Component | Directive) => { const compodocJson = getCompdocJson(); checkValidCompodocJson(compodocJson); const { name } = component; - return ( - compodocJson.components.find((c: Component) => c.name === name) || - compodocJson.directives.find((c: Directive) => c.name === name) - ); + return findComponentByName(name, compodocJson); }; const displaySignature = (item: Method): string => { @@ -92,36 +106,94 @@ const displaySignature = (item: Method): string => { return `(${args.join(', ')}) => ${item.returnType}`; }; -export const extractProps = (component: Component | Directive) => { - const componentData = getComponentData(component); - if (!componentData) { +const extractTypeFromValue = (defaultValue: any) => { + const valueType = typeof defaultValue; + return defaultValue || valueType === 'boolean' ? valueType : null; +}; + +const extractEnumValues = (compodocType: any) => { + if (typeof compodocType !== 'string' || compodocType.indexOf('|') === -1) { return null; } - const sectionToItems: Sections = {}; - const compodocClasses = ['propertiesClass', 'methodsClass', 'inputsClass', 'outputsClass']; - type COMPODOC_CLASS = 'propertiesClass' | 'methodsClass' | 'inputsClass' | 'outputsClass'; + try { + return compodocType.split('|').map((value) => JSON.parse(value)); + } catch (e) { + return null; + } +}; + +export const extractType = (property: Property, defaultValue: any) => { + const compodocType = property.type || extractTypeFromValue(defaultValue); + switch (compodocType) { + case 'string': + case 'boolean': + case 'number': + return { name: compodocType }; + case undefined: + case null: + return { name: 'void' }; + default: { + const enumValues = extractEnumValues(compodocType); + return enumValues ? { name: 'enum', value: enumValues } : { name: 'object' }; + } + } +}; + +const extractDefaultValue = (property: Property) => { + try { + // eslint-disable-next-line no-eval + const value = eval(property.defaultValue); + return value; + } catch (err) { + logger.debug(`Error extracting ${property.name}: ${property.defaultValue}`); + return undefined; + } +}; + +export const extractArgTypesFromData = (componentData: Class | Directive | Injectable | Pipe) => { + const sectionToItems: Record<string, ArgType[]> = {}; + const compodocClasses = ['component', 'directive'].includes(componentData.type) + ? ['propertiesClass', 'methodsClass', 'inputsClass', 'outputsClass'] + : ['properties', 'methods']; + type COMPODOC_CLASS = + | 'properties' + | 'methods' + | 'propertiesClass' + | 'methodsClass' + | 'inputsClass' + | 'outputsClass'; compodocClasses.forEach((key: COMPODOC_CLASS) => { - const data = componentData[key] || []; + const data = (componentData as any)[key] || []; data.forEach((item: Method | Property) => { - const sectionItem: PropDef = { + const section = mapItemToSection(key, item); + const defaultValue = isMethod(item) ? undefined : extractDefaultValue(item as Property); + const type = + isMethod(item) || section !== 'inputs' + ? { name: 'void' } + : extractType(item as Property, defaultValue); + const argType = { name: item.name, - type: { summary: isMethod(item) ? displaySignature(item) : item.type }, - required: isMethod(item) ? false : !item.optional, description: item.description, - defaultValue: { summary: isMethod(item) ? '' : item.defaultValue }, + defaultValue, + type, + table: { + category: section, + type: { + summary: isMethod(item) ? displaySignature(item) : item.type, + required: isMethod(item) ? false : !item.optional, + }, + }, }; - const section = mapItemToSection(key, item); if (!sectionToItems[section]) { sectionToItems[section] = []; } - sectionToItems[section].push(sectionItem); + sectionToItems[section].push(argType); }); }); - // sort the sections const SECTIONS = [ 'inputs', 'outputs', @@ -132,15 +204,22 @@ export const extractProps = (component: Component | Directive) => { 'content child', 'content children', ]; - const sections: Sections = {}; - SECTIONS.forEach(section => { + const argTypes: ArgTypes = {}; + SECTIONS.forEach((section) => { const items = sectionToItems[section]; if (items) { - sections[section] = items; + items.forEach((argType) => { + argTypes[argType.name] = argType; + }); } }); - return isEmpty(sections) ? null : { sections }; + return argTypes; +}; + +export const extractArgTypes = (component: Component | Directive) => { + const componentData = getComponentData(component); + return componentData && extractArgTypesFromData(componentData); }; export const extractComponentDescription = (component: Component | Directive) => { @@ -148,5 +227,5 @@ export const extractComponentDescription = (component: Component | Directive) => if (!componentData) { return null; } - return componentData.rawdescription; + return componentData.rawdescription || componentData.description; }; diff --git a/addons/docs/src/frameworks/angular/config.ts b/addons/docs/src/frameworks/angular/config.ts index c8513d0445b3..dc06b77c8d37 100644 --- a/addons/docs/src/frameworks/angular/config.ts +++ b/addons/docs/src/frameworks/angular/config.ts @@ -1,10 +1,9 @@ -/* eslint-disable import/no-extraneous-dependencies */ import { addParameters } from '@storybook/client-api'; -import { extractProps, extractComponentDescription } from './compodoc'; +import { extractArgTypes, extractComponentDescription } from './compodoc'; addParameters({ docs: { - extractProps, + extractArgTypes, extractComponentDescription, }, }); diff --git a/addons/docs/src/frameworks/angular/types.ts b/addons/docs/src/frameworks/angular/types.ts index 9bc7f760c852..9f7e64afbabf 100644 --- a/addons/docs/src/frameworks/angular/types.ts +++ b/addons/docs/src/frameworks/angular/types.ts @@ -2,26 +2,56 @@ export interface Method { name: string; args: Argument[]; returnType: string; - decorators: Decorator[]; - description: string; + decorators?: Decorator[]; + description?: string; } export interface Property { name: string; - decorators: Decorator[]; + decorators?: Decorator[]; type: string; optional: boolean; defaultValue?: string; description?: string; } +export interface Class { + name: string; + ngname: string; + type: 'pipe'; + properties: Property[]; + methods: Method[]; + description?: string; + rawdescription?: string; +} + +export interface Injectable { + name: string; + type: 'injectable'; + properties: Property[]; + methods: Method[]; + description?: string; + rawdescription?: string; +} + +export interface Pipe { + name: string; + type: 'class'; + properties: Property[]; + methods: Method[]; + description?: string; + rawdescription?: string; +} + export interface Directive { name: string; + type: 'directive' | 'component'; propertiesClass: Property[]; inputsClass: Property[]; outputsClass: Property[]; methodsClass: Method[]; - rawdescription: string; + description?: string; + rawdescription?: string; } export type Component = Directive; @@ -39,4 +69,7 @@ export interface Decorator { export interface CompodocJson { directives: Directive[]; components: Component[]; + pipes: Pipe[]; + injectables: Injectable[]; + classes: Class[]; } diff --git a/addons/docs/src/frameworks/common/config.ts b/addons/docs/src/frameworks/common/config.ts index 1d34169c1e74..26b8364f920d 100644 --- a/addons/docs/src/frameworks/common/config.ts +++ b/addons/docs/src/frameworks/common/config.ts @@ -1,10 +1,13 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/client-api'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; +import { DocsContainer, DocsPage } from '../../blocks'; +import { enhanceArgTypes } from './enhanceArgTypes'; -addParameters({ +export const parameters = { docs: { + inlineStories: false, container: DocsContainer, page: DocsPage, + iframeHeight: 100, }, -}); +}; + +export const argTypesEnhancers = [enhanceArgTypes]; diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts new file mode 100644 index 000000000000..10ba2a3fc24d --- /dev/null +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts @@ -0,0 +1,317 @@ +import { ArgType, ArgTypes, Args } from '@storybook/api'; +import { enhanceArgTypes } from './enhanceArgTypes'; + +expect.addSnapshotSerializer({ + print: (val: any) => JSON.stringify(val, null, 2), + test: (val) => typeof val !== 'string', +}); + +const enhance = ({ + argType, + arg, + extractedArgTypes, + storyFn = (args: Args) => 0, +}: { + argType?: ArgType; + arg?: any; + extractedArgTypes?: ArgTypes; + storyFn?: any; +}) => { + const context = { + id: 'foo--bar', + kind: 'foo', + name: 'bar', + storyFn, + parameters: { + component: 'dummy', + docs: { + extractArgTypes: extractedArgTypes && (() => extractedArgTypes), + }, + argTypes: argType && { + input: argType, + }, + args: { + input: arg, + }, + }, + args: {}, + globalArgs: {}, + }; + return enhanceArgTypes(context); +}; + +describe('enhanceArgTypes', () => { + describe('no-args story function', () => { + it('should no-op', () => { + expect( + enhance({ + argType: { foo: 'unmodified', type: { name: 'number' } }, + storyFn: () => 0, + }).input + ).toMatchInlineSnapshot(` + { + "name": "input", + "foo": "unmodified", + "type": { + "name": "number" + } + } + `); + }); + }); + describe('args story function', () => { + describe('single-source input', () => { + describe('argTypes input', () => { + it('number', () => { + expect( + enhance({ + argType: { type: { name: 'number' } }, + }).input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + } + `); + }); + }); + + describe('args input', () => { + it('number', () => { + expect(enhance({ arg: 5 }).input).toMatchInlineSnapshot(` + { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + } + `); + }); + }); + + describe('extraction from component', () => { + it('number', () => { + expect( + enhance({ extractedArgTypes: { input: { name: 'input', type: { name: 'number' } } } }) + .input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + } + `); + }); + }); + + describe('controls input', () => { + it('range', () => { + expect( + enhance({ + argType: { control: { type: 'range', min: 0, max: 100 } }, + }).input + ).toMatchInlineSnapshot(` + { + "name": "input", + "control": { + "type": "range", + "min": 0, + "max": 100 + } + } + `); + }); + it('options', () => { + expect( + enhance({ + argType: { control: { type: 'radio', options: [1, 2] } }, + }).input + ).toMatchInlineSnapshot(` + { + "name": "input", + "control": { + "type": "radio", + "options": [ + 1, + 2 + ] + } + } + `); + }); + }); + }); + + describe('mixed-source input', () => { + it('user-specified argTypes take precedence over extracted argTypes', () => { + expect( + enhance({ + argType: { type: { name: 'number' } }, + extractedArgTypes: { input: { type: { name: 'string' } } }, + }).input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "number" + }, + "type": { + "name": "number" + }, + "name": "input" + } + `); + }); + + it('user-specified argTypes take precedence over inferred argTypes', () => { + expect( + enhance({ + argType: { type: { name: 'number' } }, + arg: 'hello', + }).input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + } + `); + }); + + it('extracted argTypes take precedence over inferred argTypes', () => { + expect( + enhance({ + extractedArgTypes: { input: { type: { name: 'string' } } }, + arg: 6, + }).input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "text" + }, + "name": "input", + "type": { + "name": "string" + } + } + `); + }); + + it('user-specified controls take precedence over inferred controls', () => { + expect( + enhance({ + argType: { defaultValue: 5, control: { type: 'range', step: 50 } }, + arg: 3, + extractedArgTypes: { input: { name: 'input' } }, + }).input + ).toMatchInlineSnapshot(` + { + "control": { + "type": "range", + "step": 50 + }, + "name": "input", + "type": { + "name": "number" + }, + "defaultValue": 5 + } + `); + }); + + it('includes extracted argTypes when there are no user-specified argTypes', () => { + expect( + enhance({ + arg: 3, + extractedArgTypes: { input: { name: 'input' }, foo: { type: { name: 'number' } } }, + }) + ).toMatchInlineSnapshot(` + { + "input": { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + }, + "foo": { + "control": { + "type": "number" + }, + "type": { + "name": "number" + } + } + } + `); + }); + + it('includes extracted argTypes when user-specified argTypes match', () => { + expect( + enhance({ + argType: { type: { name: 'number' } }, + extractedArgTypes: { input: { name: 'input' }, foo: { type: { name: 'number' } } }, + }) + ).toMatchInlineSnapshot(` + { + "input": { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + }, + "foo": { + "control": { + "type": "number" + }, + "type": { + "name": "number" + } + } + } + `); + }); + + it('excludes extracted argTypes when user-specified argTypes do not match', () => { + expect( + enhance({ + argType: { type: { name: 'number' } }, + extractedArgTypes: { foo: { type: { name: 'number' } } }, + }) + ).toMatchInlineSnapshot(` + { + "input": { + "control": { + "type": "number" + }, + "name": "input", + "type": { + "name": "number" + } + } + } + `); + }); + }); + }); +}); diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.ts new file mode 100644 index 000000000000..10d705ebe0c5 --- /dev/null +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.ts @@ -0,0 +1,40 @@ +import mapValues from 'lodash/mapValues'; +import { ArgTypesEnhancer, combineParameters } from '@storybook/client-api'; +import { ArgTypes } from '@storybook/api'; +import { inferArgTypes } from './inferArgTypes'; +import { inferControls } from './inferControls'; + +const isSubset = (kind: string, subset: object, superset: object) => { + const keys = Object.keys(subset); + // eslint-disable-next-line no-prototype-builtins + const overlap = keys.filter((key) => superset.hasOwnProperty(key)); + return overlap.length === keys.length; +}; + +export const enhanceArgTypes: ArgTypesEnhancer = (context) => { + const { component, argTypes: userArgTypes = {}, docs = {}, args = {} } = context.parameters; + const { extractArgTypes } = docs; + + const namedArgTypes = mapValues(userArgTypes, (val, key) => ({ name: key, ...val })); + const inferredArgTypes = inferArgTypes(args); + let extractedArgTypes: ArgTypes = extractArgTypes && component ? extractArgTypes(component) : {}; + + if ( + (Object.keys(userArgTypes).length > 0 && + !isSubset(context.kind, userArgTypes, extractedArgTypes)) || + (Object.keys(inferredArgTypes).length > 0 && + !isSubset(context.kind, inferredArgTypes, extractedArgTypes)) + ) { + extractedArgTypes = {}; + } + + const withArgTypes = combineParameters(inferredArgTypes, extractedArgTypes, namedArgTypes); + + if (context.storyFn.length === 0) { + return withArgTypes; + } + + const withControls = inferControls(withArgTypes); + const result = combineParameters(withControls, withArgTypes); + return result; +}; diff --git a/addons/docs/src/frameworks/common/inferArgTypes.ts b/addons/docs/src/frameworks/common/inferArgTypes.ts new file mode 100644 index 000000000000..41fdaab110af --- /dev/null +++ b/addons/docs/src/frameworks/common/inferArgTypes.ts @@ -0,0 +1,36 @@ +import mapValues from 'lodash/mapValues'; +import { Args, ArgTypes } from '@storybook/addons'; +import { SBType } from '../../lib/sbtypes'; + +const inferType = (value?: any): SBType => { + const type = typeof value; + switch (type) { + case 'boolean': + case 'string': + case 'number': + case 'function': + return { name: type }; + default: + break; + } + if (Array.isArray(value)) { + const childType: SBType = + value.length > 0 ? inferType(value[0]) : { name: 'other', value: 'unknown' }; + return { name: 'array', value: childType }; + } + if (value) { + const fieldTypes = mapValues(value, (field) => inferType(field)); + return { name: 'object', value: fieldTypes }; + } + return { name: 'other', value: 'unknown' }; +}; + +export const inferArgTypes = (args: Args): ArgTypes => { + if (!args) return {}; + return mapValues(args, (arg, name) => { + if (arg !== null && typeof arg !== 'undefined') { + return { name, type: inferType(arg) }; + } + return undefined; + }); +}; diff --git a/addons/docs/src/frameworks/common/inferControls.ts b/addons/docs/src/frameworks/common/inferControls.ts new file mode 100644 index 000000000000..c49a7da84e6a --- /dev/null +++ b/addons/docs/src/frameworks/common/inferControls.ts @@ -0,0 +1,47 @@ +import mapValues from 'lodash/mapValues'; +import { ArgTypes, ArgType } from '@storybook/addons'; +import { Control } from '@storybook/components'; +import { SBEnumType } from '../../lib/sbtypes'; + +const inferControl = (argType: ArgType): Control => { + const { type } = argType; + if (!type) { + // console.log('no sbtype', { argType }); + return null; + } + switch (type.name) { + case 'array': { + const { value } = type; + if (value?.name && ['object', 'other'].includes(value.name)) { + return { + type: 'object', + validator: (obj: any) => Array.isArray(obj), + }; + } + return { type: 'array' }; + } + case 'boolean': + return { type: 'boolean' }; + case 'string': + return { type: 'text' }; + case 'number': + return { type: 'number' }; + case 'enum': { + const { value } = type as SBEnumType; + return { type: 'select', options: value }; + } + case 'function': + case 'symbol': + case 'void': + return null; + default: + return { type: 'object' }; + } +}; + +export const inferControls = (argTypes: ArgTypes): ArgTypes => { + return mapValues(argTypes, (argType) => { + const control = argType && argType.type && inferControl(argType); + return control ? { control } : undefined; + }); +}; diff --git a/addons/docs/src/frameworks/common/makePreset.ts b/addons/docs/src/frameworks/common/makePreset.ts deleted file mode 100644 index 08654c15063b..000000000000 --- a/addons/docs/src/frameworks/common/makePreset.ts +++ /dev/null @@ -1,17 +0,0 @@ -import deprecate from 'util-deprecate'; -import dedent from 'ts-dedent'; -import * as common from '../../preset'; - -const makePreset = (framework: string) => { - deprecate( - () => {}, - dedent` - Framework-specific presets are no longer-needed as of Storybook 5.3 and will be removed in 6.0. - - Please use '@storybook/addon-docs/preset' instead of '@storybook/addon-docs/${framework}/preset'. - ` - )(); - return common; -}; - -export default makePreset; diff --git a/addons/docs/src/frameworks/common/preset.ts b/addons/docs/src/frameworks/common/preset.ts index ce48d65c65f5..6d650289cec6 100644 --- a/addons/docs/src/frameworks/common/preset.ts +++ b/addons/docs/src/frameworks/common/preset.ts @@ -1,8 +1,18 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import createCompiler from '@storybook/addon-docs/mdx-compiler-plugin'; +import path from 'path'; import remarkSlug from 'remark-slug'; import remarkExternalLinks from 'remark-external-links'; +import { DllReferencePlugin } from 'webpack'; + +// @ts-ignore +import createCompiler from '../../mdx/mdx-compiler-plugin'; + +const coreDirName = path.dirname(require.resolve('@storybook/core/package.json')); +// TODO: improve node_modules detection +const context = coreDirName.includes('node_modules') + ? path.join(coreDirName, '../../') // Real life case, already in node_modules + : path.join(coreDirName, '../../node_modules'); // SB Monorepo + function createBabelOptions(babelOptions?: any, configureJSX?: boolean) { if (!configureJSX) { return babelOptions; @@ -18,6 +28,10 @@ function createBabelOptions(babelOptions?: any, configureJSX?: boolean) { }; } +export const webpackDlls = (dlls: string[], options: any) => { + return options.dll ? [...dlls, './sb_dll/storybook_docs_dll.js'] : []; +}; + export function webpack(webpackConfig: any = {}, options: any = {}) { const { module = {} } = webpackConfig; // it will reuse babel options that are already in use in storybook @@ -52,10 +66,10 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ...(module.rules || []), { test: /\.js$/, - include: /node_modules\/acorn-jsx/, + include: new RegExp(`node_modules\\${path.sep}acorn-jsx`), use: [ { - loader: 'babel-loader', + loader: require.resolve('babel-loader'), options: { presets: [[require.resolve('@babel/preset-env'), { modules: 'commonjs' }]], }, @@ -66,11 +80,11 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { test: /\.(stories|story).mdx$/, use: [ { - loader: 'babel-loader', + loader: require.resolve('babel-loader'), options: createBabelOptions(babelOptions, configureJSX), }, { - loader: '@mdx-js/loader', + loader: require.resolve('@mdx-js/loader'), options: { compilers: [createCompiler(options)], ...mdxLoaderOptions, @@ -83,11 +97,11 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { exclude: /\.(stories|story).mdx$/, use: [ { - loader: 'babel-loader', + loader: require.resolve('babel-loader'), options: createBabelOptions(babelOptions, configureJSX), }, { - loader: '@mdx-js/loader', + loader: require.resolve('@mdx-js/loader'), options: mdxLoaderOptions, }, ], @@ -96,10 +110,20 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ], }, }; + + if (options.dll) { + result.plugins.push( + new DllReferencePlugin({ + context, + manifest: require.resolve('@storybook/core/dll/storybook_docs-manifest.json'), + }) + ); + } + return result; } -export function addons(entry: any[] = [], options: any) { +export function managerEntries(entry: any[] = [], options: any) { return [...entry, require.resolve('../../register')]; } diff --git a/addons/docs/src/frameworks/ember/config.js b/addons/docs/src/frameworks/ember/config.js index 2ba143d9b433..9745c9eca4c9 100644 --- a/addons/docs/src/frameworks/ember/config.js +++ b/addons/docs/src/frameworks/ember/config.js @@ -1,11 +1,10 @@ -/* eslint-disable import/no-extraneous-dependencies */ import { addParameters } from '@storybook/client-api'; -import { extractProps, extractComponentDescription } from './jsondoc'; +import { extractArgTypes, extractComponentDescription } from './jsondoc'; addParameters({ docs: { iframeHeight: 80, - extractProps, + extractArgTypes, extractComponentDescription, }, }); diff --git a/addons/docs/src/frameworks/ember/jsondoc.js b/addons/docs/src/frameworks/ember/jsondoc.js index a3a996d548c6..c9d95fedac60 100644 --- a/addons/docs/src/frameworks/ember/jsondoc.js +++ b/addons/docs/src/frameworks/ember/jsondoc.js @@ -1,30 +1,49 @@ /* eslint-disable no-underscore-dangle */ /* global window */ -export const setJSONDoc = jsondoc => { +export const setJSONDoc = (jsondoc) => { window.__EMBER_GENERATED_DOC_JSON__ = jsondoc; }; export const getJSONDoc = () => { return window.__EMBER_GENERATED_DOC_JSON__; }; -export const extractProps = componentName => { +export const extractArgTypes = (componentName) => { const json = getJSONDoc(); - const componentDoc = json.included.find(doc => doc.attributes.name === componentName); - const rows = componentDoc.attributes.arguments.map(prop => { + if (!(json && json.included)) { + return null; + } + const componentDoc = json.included.find((doc) => doc.attributes.name === componentName); + + if (!componentDoc) { + return null; + } + const rows = componentDoc.attributes.arguments.map((prop) => { return { name: prop.name, - type: prop.type, - required: prop.tags.length ? prop.tags.some(tag => tag.name === 'required') : false, defaultValue: prop.defaultValue, description: prop.description, + table: { + type: { + summary: prop.type, + required: prop.tags.length ? prop.tags.some((tag) => tag.name === 'required') : false, + }, + }, }; }); return { rows }; }; -export const extractComponentDescription = componentName => { +export const extractComponentDescription = (componentName) => { const json = getJSONDoc(); - const componentDoc = json.included.find(doc => doc.attributes.name === componentName); + if (!(json && json.included)) { + return null; + } + const componentDoc = json.included.find((doc) => doc.attributes.name === componentName); + + if (!componentDoc) { + return null; + } + return componentDoc.attributes.description; }; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/docgen.snapshot new file mode 100644 index 000000000000..e702b18e4220 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/docgen.snapshot @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 10017-ts-union 1`] = ` +"import React from 'react'; + +const Avatar = ({ + icon +}) => { + return /*#__PURE__*/React.createElement(\\"div\\", { + className: \\"hello\\" + }, \\"Hello Component \\", icon); +}; + +export const component = Avatar; +Avatar.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Avatar\\", + \\"props\\": { + \\"icon\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"union\\", + \\"raw\\": \\"React.ReactNode | string\\", + \\"elements\\": [{ + \\"name\\": \\"ReactReactNode\\", + \\"raw\\": \\"React.ReactNode\\" + }, { + \\"name\\": \\"string\\" + }] + }, + \\"description\\": \\"specify icon=\\\\\\"search\\\\\\" or icon={IconComponent}\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/input.tsx new file mode 100644 index 000000000000..399f8b31a632 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/input.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +interface AvatarProps { + /** specify icon="search" or icon={IconComponent} */ + icon: React.ReactNode | string; +} + +const Avatar = ({ icon }: AvatarProps) => { + return <div className="hello">Hello Component {icon}</div>; +}; + +export const component = Avatar; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/properties.snapshot new file mode 100644 index 000000000000..430e86422090 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10017-ts-union/properties.snapshot @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 10017-ts-union 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "specify icon=\\"search\\" or icon={IconComponent}", + "name": "icon", + "required": true, + "sbType": Object { + "name": "union", + "raw": "React.ReactNode | string", + "value": Array [ + Object { + "name": "other", + "raw": "React.ReactNode", + "value": "ReactReactNode", + }, + Object { + "name": "string", + }, + ], + }, + "type": Object { + "detail": undefined, + "summary": "union", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/docgen.snapshot new file mode 100644 index 000000000000..3947ed35573d --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/docgen.snapshot @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 10278-ts-multiple-components 1`] = ` +"/* eslint-disable react/destructuring-assignment */ +import React from 'react'; + +/** + * A component + */ +const A = props => { + return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Hi \\", props.aProperty); +}; +/** + * B component + */ + + +const B = props => { + return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Hi \\", props.bProperty); +}; + +A.__docgenInfo = { + \\"description\\": \\"A component\\", + \\"methods\\": [], + \\"displayName\\": \\"A\\", + \\"props\\": { + \\"aProperty\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"any\\" + }, + \\"description\\": \\"\\" + } + } +}; +B.__docgenInfo = { + \\"description\\": \\"B component\\", + \\"methods\\": [], + \\"displayName\\": \\"B\\", + \\"props\\": { + \\"bProperty\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"any\\" + }, + \\"description\\": \\"\\" + } + } +}; +export { A, B }; +export const component = A;" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/input.tsx new file mode 100644 index 000000000000..fea6eaea664a --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/input.tsx @@ -0,0 +1,27 @@ +/* eslint-disable react/destructuring-assignment */ +import React from 'react'; + +interface IAProps { + aProperty: any; +} + +interface IBProps { + bProperty: any; +} + +/** + * A component + */ +const A = (props: IAProps): JSX.Element => { + return <>Hi {props.aProperty}</>; +}; + +/** + * B component + */ +const B = (props: IBProps): JSX.Element => { + return <>Hi {props.bProperty}</>; +}; + +export { A, B }; +export const component = A; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/properties.snapshot new file mode 100644 index 000000000000..f9f94045bb53 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/10278-ts-multiple-components/properties.snapshot @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 10278-ts-multiple-components 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "aProperty", + "required": true, + "sbType": Object { + "name": "other", + "value": "any", + }, + "type": Object { + "detail": undefined, + "summary": "any", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/docgen.snapshot new file mode 100644 index 000000000000..0f5ed90d714b --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/docgen.snapshot @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8140-js-prop-types-oneof 1`] = ` +"import React from 'react'; +import PropTypes from 'prop-types'; + +const Alert = props => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(props)); + +Alert.defaultProps = { + mode: 'static', + type: 'warning' +}; +Alert.propTypes = { + mode: PropTypes.oneOf(['static', 'timed']), + type: PropTypes.oneOf(['success', 'warning', 'error', 'primary']), + message: PropTypes.string.isRequired, + + /** + * No background or border if static alert + */ + blank: PropTypes.bool, + + /** + * Allows icon override, accepts material icon name + */ + icon: PropTypes.string +}; +export const component = Alert; +Alert.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Alert\\", + \\"props\\": { + \\"mode\\": { + \\"defaultValue\\": { + \\"value\\": \\"'static'\\", + \\"computed\\": false + }, + \\"type\\": { + \\"name\\": \\"enum\\", + \\"value\\": [{ + \\"value\\": \\"'static'\\", + \\"computed\\": false + }, { + \\"value\\": \\"'timed'\\", + \\"computed\\": false + }] + }, + \\"required\\": false, + \\"description\\": \\"\\" + }, + \\"type\\": { + \\"defaultValue\\": { + \\"value\\": \\"'warning'\\", + \\"computed\\": false + }, + \\"type\\": { + \\"name\\": \\"enum\\", + \\"value\\": [{ + \\"value\\": \\"'success'\\", + \\"computed\\": false + }, { + \\"value\\": \\"'warning'\\", + \\"computed\\": false + }, { + \\"value\\": \\"'error'\\", + \\"computed\\": false + }, { + \\"value\\": \\"'primary'\\", + \\"computed\\": false + }] + }, + \\"required\\": false, + \\"description\\": \\"\\" + }, + \\"message\\": { + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": true, + \\"description\\": \\"\\" + }, + \\"blank\\": { + \\"type\\": { + \\"name\\": \\"bool\\" + }, + \\"required\\": false, + \\"description\\": \\"No background or border if static alert\\" + }, + \\"icon\\": { + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": false, + \\"description\\": \\"Allows icon override, accepts material icon name\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/input.js b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/input.js new file mode 100644 index 000000000000..1eb6b30cf299 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/input.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Alert = (props) => <>{JSON.stringify(props)}</>; + +Alert.defaultProps = { + mode: 'static', + type: 'warning', +}; + +Alert.propTypes = { + mode: PropTypes.oneOf(['static', 'timed']), + type: PropTypes.oneOf(['success', 'warning', 'error', 'primary']), + message: PropTypes.string.isRequired, + /** + * No background or border if static alert + */ + blank: PropTypes.bool, + /** + * Allows icon override, accepts material icon name + */ + icon: PropTypes.string, +}; + +export const component = Alert; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/properties.snapshot new file mode 100644 index 000000000000..ff183b161512 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8140-js-prop-types-oneof/properties.snapshot @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8140-js-prop-types-oneof 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'static'", + }, + "description": "", + "name": "mode", + "required": false, + "sbType": Object { + "name": "enum", + "value": Array [ + "static", + "timed", + ], + }, + "type": Object { + "detail": undefined, + "summary": "'static' | 'timed'", + }, + }, + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'warning'", + }, + "description": "", + "name": "type", + "required": false, + "sbType": Object { + "name": "enum", + "value": Array [ + "success", + "warning", + "error", + "primary", + ], + }, + "type": Object { + "detail": undefined, + "summary": "'success' | 'warning' | 'error' | 'primary'", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "message", + "required": true, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + Object { + "defaultValue": null, + "description": "No background or border if static alert", + "name": "blank", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "bool", + }, + }, + Object { + "defaultValue": null, + "description": "Allows icon override, accepts material icon name", + "name": "icon", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/docgen.snapshot new file mode 100644 index 000000000000..99f9c02801e3 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/docgen.snapshot @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8143-ts-imported-types 1`] = ` +"import React from 'react'; +export const FooComponent = foo => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(foo)); +export const component = FooComponent; +FooComponent.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"FooComponent\\", + \\"props\\": { + \\"bar\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"Foo['bar']\\", + \\"raw\\": \\"Foo['bar']\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/input.tsx new file mode 100644 index 000000000000..7e30066389a2 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/input.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Foo } from './types'; + +interface FooProps { + bar: Foo['bar']; +} + +export const FooComponent = (foo: FooProps) => <>{JSON.stringify(foo)}</>; + +export const component = FooComponent; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/properties.snapshot new file mode 100644 index 000000000000..e999d6a79f28 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/properties.snapshot @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8143-ts-imported-types 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "bar", + "required": true, + "sbType": Object { + "name": "other", + "raw": "Foo['bar']", + "value": "Foo['bar']", + }, + "type": Object { + "detail": undefined, + "summary": "Foo['bar']", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/types.ts b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/types.ts new file mode 100644 index 000000000000..156212cf95ce --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-imported-types/types.ts @@ -0,0 +1,3 @@ +export interface Foo { + bar: number; +} diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/docgen.snapshot new file mode 100644 index 000000000000..cd06005f3581 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/docgen.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8143-ts-react-fc-generics 1`] = ` +"import React from 'react'; +export const Text = ({ + padding = '0', + margin +}) => /*#__PURE__*/React.createElement(React.Fragment, null, \\"Text\\"); +export const component = Text; +Text.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Text\\", + \\"props\\": { + \\"padding\\": { + \\"defaultValue\\": { + \\"value\\": \\"'0'\\", + \\"computed\\": false + }, + \\"required\\": false + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/input.tsx new file mode 100644 index 000000000000..9116328f1937 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/input.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +interface Props { + padding: string; + margin: number; +} + +export const Text: React.FC<Props> = ({ padding = '0', margin }) => <>Text</>; + +export const component = Text; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/properties.snapshot new file mode 100644 index 000000000000..7e70b8104795 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8143-ts-react-fc-generics/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8143-ts-react-fc-generics 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'0'", + }, + "description": undefined, + "name": "padding", + "required": false, + "type": Object { + "detail": undefined, + "summary": "unknown", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/docgen.snapshot new file mode 100644 index 000000000000..670ab38ca25b --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/docgen.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8279-js-styled-docgen 1`] = ` +"import styled from 'styled-components'; +import PropTypes from 'prop-types'; +/** + * Use \`A\` to provide a regular link + */ + +const A = styled('a')({ + margin: '8px 0', + outline: 'none' +}); +A.displayName = 'Link'; +A.defaultProps = { + children: 'This is a link' +}; +A.propTypes = { + /** That should be the clickable element */ + children: PropTypes.node.isRequired +}; +export default A; +export const component = A;" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/input.js b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/input.js new file mode 100644 index 000000000000..95cf14f635c8 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/input.js @@ -0,0 +1,23 @@ +import styled from 'styled-components'; +import PropTypes from 'prop-types'; + +/** + * Use `A` to provide a regular link + */ +const A = styled('a')({ + margin: '8px 0', + outline: 'none', +}); + +A.displayName = 'Link'; +A.defaultProps = { + children: 'This is a link', +}; + +A.propTypes = { + /** That should be the clickable element */ + children: PropTypes.node.isRequired, +}; +export default A; + +export const component = A; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/properties.snapshot new file mode 100644 index 000000000000..b429f083fcc0 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8279-js-styled-docgen/properties.snapshot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8279-js-styled-docgen 1`] = ` +Object { + "rows": Array [], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/docgen.snapshot new file mode 100644 index 000000000000..10a2a46836d4 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/docgen.snapshot @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8428-js-static-prop-types 1`] = ` +"function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import React from 'react'; +import PropTypes from 'prop-types'; // eslint-disable-next-line react/prefer-stateless-function + +export default class Test extends React.Component { + render() { + return /*#__PURE__*/React.createElement(\\"div\\", null, \\"test\\"); + } + +} + +_defineProperty(Test, \\"propTypes\\", { + /** + * Please work... + */ + test: PropTypes.string +}); + +export const component = Test; +Test.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Test\\", + \\"props\\": { + \\"test\\": { + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": false, + \\"description\\": \\"Please work...\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/input.js b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/input.js new file mode 100644 index 000000000000..20094fa268dd --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/input.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Test extends React.Component { + static propTypes = { + /** + * Please work... + */ + test: PropTypes.string, + }; + + render() { + return <div>test</div>; + } +} + +export const component = Test; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/properties.snapshot new file mode 100644 index 000000000000..343ea8913700 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8428-js-static-prop-types/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8428-js-static-prop-types 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "Please work...", + "name": "test", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/docgen.snapshot new file mode 100644 index 000000000000..bf03a19e7769 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/docgen.snapshot @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8663-js-styled-components 1`] = ` +"import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +const Box = styled.div\` + background-color: \${props => props.bg}; +\`; +Box.propTypes = { + bg: PropTypes.string +}; +export const MyBox = props => /*#__PURE__*/React.createElement(Box, props); +MyBox.propTypes = { + bg: PropTypes.string +}; +export const component = MyBox; +MyBox.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"MyBox\\", + \\"props\\": { + \\"bg\\": { + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/input.js b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/input.js new file mode 100644 index 000000000000..7eb40f01b946 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/input.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + +const Box = styled.div` + background-color: ${(props) => props.bg}; +`; + +Box.propTypes = { + bg: PropTypes.string, +}; + +export const MyBox = (props) => <Box {...props} />; + +MyBox.propTypes = { + bg: PropTypes.string, +}; + +export const component = MyBox; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/properties.snapshot new file mode 100644 index 000000000000..e6923faff496 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8663-js-styled-components/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8663-js-styled-components 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "bg", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/docgen.snapshot new file mode 100644 index 000000000000..8c8de9f93085 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/docgen.snapshot @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8740-ts-multi-props 1`] = ` +"import React from 'react'; +export const Header = ({ + size = 'a', + children +}) => /*#__PURE__*/React.createElement(\\"div\\", { + className: size +}, children); +export const Paragraph = ({ + size, + children +}) => /*#__PURE__*/React.createElement(\\"div\\", { + className: size +}, children); +Paragraph.defaultProps = { + size: 'md' +}; +export const component = Header; +Header.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Header\\", + \\"props\\": { + \\"size\\": { + \\"defaultValue\\": { + \\"value\\": \\"'a'\\", + \\"computed\\": false + }, + \\"required\\": false + } + } +}; +Paragraph.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Paragraph\\", + \\"props\\": { + \\"size\\": { + \\"defaultValue\\": { + \\"value\\": \\"'md'\\", + \\"computed\\": false + }, + \\"required\\": false + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/input.tsx new file mode 100644 index 000000000000..ee19cf1a8e94 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/input.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export interface ElemAProps { + size?: 'a' | 'b' | 'c' | 'd'; +} + +export const Header: React.SFC<ElemAProps> = ({ size = 'a', children }) => ( + <div className={size}>{children}</div> +); + +export interface ElemBProps { + size?: 'sm' | 'md' | 'lg'; +} + +export const Paragraph: React.SFC<ElemBProps> = ({ size, children }) => ( + <div className={size}>{children}</div> +); + +Paragraph.defaultProps = { size: 'md' }; + +export const component = Header; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/properties.snapshot new file mode 100644 index 000000000000..9877ca576ceb --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8740-ts-multi-props/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8740-ts-multi-props 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'a'", + }, + "description": undefined, + "name": "size", + "required": false, + "type": Object { + "detail": undefined, + "summary": "unknown", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot new file mode 100644 index 000000000000..36a0021a6218 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8894-9511-ts-forward-ref 1`] = ` +"import React, { forwardRef } from 'react'; +const Button = forwardRef(({ + disabled = false, + variant = 'small', + children +}, ref) => +/*#__PURE__*/ +// eslint-disable-next-line react/button-has-type +React.createElement(\\"button\\", { + disabled: disabled, + ref: ref +}, children, \\" \\", variant)); +Button.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Button\\", + \\"props\\": { + \\"disabled\\": { + \\"defaultValue\\": { + \\"value\\": \\"false\\", + \\"computed\\": false + }, + \\"required\\": false + }, + \\"variant\\": { + \\"defaultValue\\": { + \\"value\\": \\"'small'\\", + \\"computed\\": false + }, + \\"required\\": false + } + } +}; +export default Button; +export const component = Button;" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/input.tsx new file mode 100644 index 000000000000..0878c8cd8ca3 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/input.tsx @@ -0,0 +1,25 @@ +import React, { forwardRef } from 'react'; + +interface ButtonProps { + /** + * Sets the button size. + */ + variant?: 'small' | 'large'; + /** + * Disables the button. + */ + disabled?: boolean; +} + +const Button = forwardRef<HTMLButtonElement, ButtonProps>( + ({ disabled = false, variant = 'small', children }, ref) => ( + // eslint-disable-next-line react/button-has-type + <button disabled={disabled} ref={ref}> + {children} {variant} + </button> + ) +); + +export default Button; + +export const component = Button; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/properties.snapshot new file mode 100644 index 000000000000..63a04364ae3a --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/properties.snapshot @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 8894-9511-ts-forward-ref 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "false", + }, + "description": undefined, + "name": "disabled", + "required": false, + "type": Object { + "detail": undefined, + "summary": "unknown", + }, + }, + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'small'", + }, + "description": undefined, + "name": "variant", + "required": false, + "type": Object { + "detail": undefined, + "summary": "unknown", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/docgen.snapshot new file mode 100644 index 000000000000..6755a05a5cfc --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/docgen.snapshot @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9023-js-hoc 1`] = ` +"/* eslint-disable react/prefer-stateless-function */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const withStyles = themeFn => Comp => Comp; + +class Alert extends React.Component { + render() { + return /*#__PURE__*/React.createElement(React.Fragment, null, \\"Alert\\"); + } + +} + +Alert.propTypes = { + variant: PropTypes.string, + dismissible: PropTypes.bool, + icon: PropTypes.elementType, + classes: PropTypes.object.isRequired +}; +Alert.defaultProps = { + variant: 'primary', + dismissible: false +}; +const StyledAlert = withStyles(theme => ({ + alert: props => ({ + backgroundColor: theme.palette[props.variant].main + }), + message: { + display: 'flex', + alignItems: 'center' + }, + icon: { + marginRight: theme.spacing(2) + } +}))(Alert); +export const component = StyledAlert; +Alert.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Alert\\", + \\"props\\": { + \\"variant\\": { + \\"defaultValue\\": { + \\"value\\": \\"'primary'\\", + \\"computed\\": false + }, + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + }, + \\"dismissible\\": { + \\"defaultValue\\": { + \\"value\\": \\"false\\", + \\"computed\\": false + }, + \\"type\\": { + \\"name\\": \\"bool\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + }, + \\"icon\\": { + \\"type\\": { + \\"name\\": \\"elementType\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + }, + \\"classes\\": { + \\"type\\": { + \\"name\\": \\"object\\" + }, + \\"required\\": true, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/input.js b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/input.js new file mode 100644 index 000000000000..3c40e3472a01 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/input.js @@ -0,0 +1,36 @@ +/* eslint-disable react/prefer-stateless-function */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const withStyles = (themeFn) => (Comp) => Comp; + +class Alert extends React.Component { + render() { + return <>Alert</>; + } +} +Alert.propTypes = { + variant: PropTypes.string, + dismissible: PropTypes.bool, + icon: PropTypes.elementType, + classes: PropTypes.object.isRequired, +}; +Alert.defaultProps = { + variant: 'primary', + dismissible: false, +}; + +const StyledAlert = withStyles((theme) => ({ + alert: (props) => ({ + backgroundColor: theme.palette[props.variant].main, + }), + message: { + display: 'flex', + alignItems: 'center', + }, + icon: { + marginRight: theme.spacing(2), + }, +}))(Alert); + +export const component = StyledAlert; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/properties.snapshot new file mode 100644 index 000000000000..7d6c8e0a2749 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9023-js-hoc/properties.snapshot @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9023-js-hoc 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'primary'", + }, + "description": "", + "name": "variant", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "false", + }, + "description": "", + "name": "dismissible", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "bool", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "icon", + "required": false, + "sbType": Object { + "name": "other", + "value": "elementType", + }, + "type": Object { + "detail": undefined, + "summary": "elementType", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "classes", + "required": true, + "sbType": Object { + "name": "object", + }, + "type": Object { + "detail": undefined, + "summary": "object", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/docgen.snapshot new file mode 100644 index 000000000000..97f537acf986 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/docgen.snapshot @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9399-js-proptypes-shape 1`] = ` +"import React from 'react'; +import PropTypes from 'prop-types'; +export const Credits = ({ + areas +}) => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(areas)); // https://github.com/storybookjs/storybook/issues/9399 + +Credits.propTypes = { + areas: PropTypes.arrayOf(PropTypes.shape({ + position: PropTypes.string.isRequired, + names: PropTypes.arrayOf(PropTypes.string).isRequired + })).isRequired +}; +export const component = Credits; +Credits.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Credits\\", + \\"props\\": { + \\"areas\\": { + \\"type\\": { + \\"name\\": \\"arrayOf\\", + \\"value\\": { + \\"name\\": \\"shape\\", + \\"value\\": { + \\"position\\": { + \\"name\\": \\"string\\", + \\"required\\": true + }, + \\"names\\": { + \\"name\\": \\"arrayOf\\", + \\"value\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": true + } + } + } + }, + \\"required\\": true, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/input.js b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/input.js new file mode 100644 index 000000000000..af120524fa8f --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/input.js @@ -0,0 +1,16 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Credits = ({ areas }) => <>{JSON.stringify(areas)}</>; + +// https://github.com/storybookjs/storybook/issues/9399 +Credits.propTypes = { + areas: PropTypes.arrayOf( + PropTypes.shape({ + position: PropTypes.string.isRequired, + names: PropTypes.arrayOf(PropTypes.string).isRequired, + }) + ).isRequired, +}; + +export const component = Credits; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/properties.snapshot new file mode 100644 index 000000000000..e831f52c1ab1 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9399-js-proptypes-shape/properties.snapshot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9399-js-proptypes-shape 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "areas", + "required": true, + "sbType": Object { + "name": "array", + "value": Object { + "name": "object", + "value": Object { + "names": Object { + "name": "array", + "value": Object { + "name": "string", + }, + }, + "position": Object { + "name": "string", + }, + }, + }, + }, + "type": Object { + "detail": "[object]", + "summary": "object[]", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/docgen.snapshot new file mode 100644 index 000000000000..eed6ac7c10e2 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/docgen.snapshot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9465-ts-type-props 1`] = ` +"import React from 'react'; + +const Component = ({ + disabled = false, + children +}) => +/*#__PURE__*/ +// eslint-disable-next-line react/button-has-type +React.createElement(\\"button\\", { + disabled: disabled +}, children); + +export const component = Component; +Component.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Component\\", + \\"props\\": { + \\"disabled\\": { + \\"defaultValue\\": { + \\"value\\": \\"false\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/input.tsx new file mode 100644 index 000000000000..e5627c8058fa --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/input.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & { + disabled?: boolean; +}; + +const Component = ({ disabled = false, children }: Props) => ( + // eslint-disable-next-line react/button-has-type + <button disabled={disabled}>{children}</button> +); + +export const component = Component; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/properties.snapshot new file mode 100644 index 000000000000..e2d4df1dcc0e --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9465-ts-type-props/properties.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9465-ts-type-props 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "false", + }, + "description": "", + "name": "disabled", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/docgen.snapshot new file mode 100644 index 000000000000..c70717fe8958 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/docgen.snapshot @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9493-ts-display-name 1`] = ` +"import styled from '@emotion/styled'; +import React from 'react'; +const Wrapper = styled('div')(({ + theme +}) => ({ + backgroundColor: 'tomato', + color: 'white', + padding: 10 +})); + +/** + * This message should show up in the Docs panel if everything works fine. + */ +export const EmpireAlert = ({ + title = 'Code Yellow', + message +}) => /*#__PURE__*/React.createElement(Wrapper, null, /*#__PURE__*/React.createElement(\\"h1\\", null, title), /*#__PURE__*/React.createElement(\\"p\\", null, message)); +EmpireAlert.displayName = 'SomeOtherDisplayName'; +export const component = EmpireAlert; +EmpireAlert.__docgenInfo = { + \\"description\\": \\"This message should show up in the Docs panel if everything works fine.\\", + \\"methods\\": [], + \\"displayName\\": \\"SomeOtherDisplayName\\", + \\"props\\": { + \\"title\\": { + \\"defaultValue\\": { + \\"value\\": \\"'Code Yellow'\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"union\\", + \\"raw\\": \\"'Code Red' | 'Code Yellow' | 'Code Green'\\", + \\"elements\\": [{ + \\"name\\": \\"literal\\", + \\"value\\": \\"'Code Red'\\" + }, { + \\"name\\": \\"literal\\", + \\"value\\": \\"'Code Yellow'\\" + }, { + \\"name\\": \\"literal\\", + \\"value\\": \\"'Code Green'\\" + }] + }, + \\"description\\": \\"A title that brings attention to the alert.\\" + }, + \\"message\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"string\\" + }, + \\"description\\": \\"A message alerting about Empire activities.\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/input.tsx new file mode 100644 index 000000000000..57155c790165 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/input.tsx @@ -0,0 +1,37 @@ +import styled from '@emotion/styled'; +import React from 'react'; + +const Wrapper = styled('div')<{}>(({ theme }) => ({ + backgroundColor: 'tomato', + color: 'white', + padding: 10, +})); + +type AlertCode = 'Code Red' | 'Code Yellow' | 'Code Green'; + +export interface EmpireAlertProps { + /** + * A title that brings attention to the alert. + */ + title: AlertCode; + /** + * A message alerting about Empire activities. + */ + message: string; +} + +/** + * This message should show up in the Docs panel if everything works fine. + */ +export const EmpireAlert: React.FC<EmpireAlertProps> = ({ + title = 'Code Yellow', + message, +}: EmpireAlertProps) => ( + <Wrapper> + <h1>{title}</h1> + <p>{message}</p> + </Wrapper> +); +EmpireAlert.displayName = 'SomeOtherDisplayName'; + +export const component = EmpireAlert; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/properties.snapshot new file mode 100644 index 000000000000..f6be2f921ecf --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9493-ts-display-name/properties.snapshot @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9493-ts-display-name 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'Code Yellow'", + }, + "description": "A title that brings attention to the alert.", + "name": "title", + "required": false, + "sbType": Object { + "name": "union", + "raw": "'Code Red' | 'Code Yellow' | 'Code Green'", + "value": Array [ + Object { + "name": "other", + "value": "literal", + }, + Object { + "name": "other", + "value": "literal", + }, + Object { + "name": "other", + "value": "literal", + }, + ], + }, + "type": Object { + "detail": undefined, + "summary": "union", + }, + }, + Object { + "defaultValue": null, + "description": "A message alerting about Empire activities.", + "name": "message", + "required": true, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/docgen.snapshot new file mode 100644 index 000000000000..3885b65757ac --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/docgen.snapshot @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9556-ts-react-default-exports 1`] = ` +"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +/* eslint-disable react/button-has-type */ +import React from 'react'; +export const Button = ({ + isDisabled = false, + ...props +}) => /*#__PURE__*/React.createElement(\\"button\\", _extends({ + disabled: isDisabled +}, props)); +export const component = Button; +Button.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Button\\", + \\"props\\": { + \\"isDisabled\\": { + \\"defaultValue\\": { + \\"value\\": \\"false\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/input.tsx new file mode 100644 index 000000000000..3517870221b2 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/input.tsx @@ -0,0 +1,12 @@ +/* eslint-disable react/button-has-type */ +import React from 'react'; + +export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> { + isDisabled?: boolean; +} + +export const Button: React.FC<Props> = ({ isDisabled = false, ...props }: Props) => ( + <button disabled={isDisabled} {...props} /> +); + +export const component = Button; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/properties.snapshot new file mode 100644 index 000000000000..daa979f95c81 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9556-ts-react-default-exports/properties.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9556-ts-react-default-exports 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "false", + }, + "description": "", + "name": "isDisabled", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/docgen.snapshot new file mode 100644 index 000000000000..9494b9c88cd9 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/docgen.snapshot @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9575-ts-camel-case 1`] = ` +"import PropTypes from 'prop-types'; +import React from 'react'; + +const iconButton = function IconButton(props) { + return /*#__PURE__*/React.createElement(\\"div\\", { + className: \\"icon-button\\" + }, \\"icon-button\\"); +}; + +iconButton.propTypes = { + color: PropTypes.string +}; +iconButton.defaultProps = { + color: 'primary' +}; +iconButton.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"iconButton\\", + \\"props\\": { + \\"color\\": { + \\"defaultValue\\": { + \\"value\\": \\"'primary'\\", + \\"computed\\": false + }, + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + } + } +}; +export default iconButton; +export const component = iconButton;" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/input.tsx new file mode 100644 index 000000000000..20ddf073f781 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/input.tsx @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types'; +import React, { FC } from 'react'; + +export interface IProps { + /** + * button color + */ + color?: string; +} + +const iconButton: FC<IProps> = function IconButton(props) { + return <div className="icon-button">icon-button</div>; +}; + +iconButton.propTypes = { + color: PropTypes.string, +}; + +iconButton.defaultProps = { + color: 'primary', +}; + +export default iconButton; +export const component = iconButton; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/properties.snapshot new file mode 100644 index 000000000000..4ca7be31120d --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9575-ts-camel-case/properties.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9575-ts-camel-case 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'primary'", + }, + "description": "", + "name": "color", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot new file mode 100644 index 000000000000..67c39e903008 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9586-js-react-memo 1`] = ` +"import React from 'react'; +import PropTypes from 'prop-types'; + +function Button({ + label, + onClick +}) { + // eslint-disable-next-line react/button-has-type + return /*#__PURE__*/React.createElement(\\"button\\", { + onClick: onClick + }, label); +} + +Button.propTypes = { + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired +}; +const MemoButton = React.memo(Button); +export const component = MemoButton; +Button.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Button\\", + \\"props\\": { + \\"label\\": { + \\"type\\": { + \\"name\\": \\"string\\" + }, + \\"required\\": true, + \\"description\\": \\"\\" + }, + \\"onClick\\": { + \\"type\\": { + \\"name\\": \\"func\\" + }, + \\"required\\": true, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/input.js b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/input.js new file mode 100644 index 000000000000..19f7951a337a --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/input.js @@ -0,0 +1,15 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function Button({ label, onClick }) { + // eslint-disable-next-line react/button-has-type + return <button onClick={onClick}>{label}</button>; +} + +Button.propTypes = { + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +}; + +const MemoButton = React.memo(Button); +export const component = MemoButton; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/properties.snapshot new file mode 100644 index 000000000000..125e28045362 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/properties.snapshot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9586-js-react-memo 1`] = ` +Object { + "rows": Array [], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/Bar.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/Bar.tsx new file mode 100644 index 000000000000..7dee0fa7f6ec --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/Bar.tsx @@ -0,0 +1,3 @@ +export interface BarProps { + foo?: string; +} diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/docgen.snapshot new file mode 100644 index 000000000000..a059bf1880b0 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/docgen.snapshot @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9591-ts-import-types 1`] = ` +"import React from 'react'; + +const Other = props => /*#__PURE__*/React.createElement(\\"span\\", props, \\"Other\\"); + +export const component = Other; +Other.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Other\\", + \\"props\\": { + \\"other\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"number\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/input.tsx new file mode 100644 index 000000000000..f1049b4e1acb --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/input.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { BarProps } from './Bar'; + +type OtherProps = BarProps & { + other?: number; +}; + +const Other = (props: OtherProps) => <span {...props}>Other</span>; + +export const component = Other; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/properties.snapshot new file mode 100644 index 000000000000..cc8d448239d6 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9591-ts-import-types/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9591-ts-import-types 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "other", + "required": false, + "sbType": Object { + "name": "number", + }, + "type": Object { + "detail": undefined, + "summary": "number", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/docgen.snapshot new file mode 100644 index 000000000000..867262a0c978 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/docgen.snapshot @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9592-ts-styled-props 1`] = ` +"import React from 'react'; +import styled from 'styled-components'; +const StyledHello = styled.div\` + color: red; +\`; + +const Hello = ({ + title +}) => { + return /*#__PURE__*/React.createElement(StyledHello, { + className: \\"hello\\" + }, \\"Hello Component \\", title); +}; + +export const component = Hello; +Hello.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Hello\\", + \\"props\\": { + \\"title\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"string\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/input.tsx new file mode 100644 index 000000000000..485bad8f10b7 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/input.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import styled from 'styled-components'; + +interface HelloProps { + title: string; +} + +const StyledHello = styled.div` + color: red; +`; + +const Hello = ({ title }: HelloProps) => { + return <StyledHello className="hello">Hello Component {title}</StyledHello>; +}; + +export const component = Hello; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/properties.snapshot new file mode 100644 index 000000000000..3298e7c6d942 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9592-ts-styled-props/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9592-ts-styled-props 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "title", + "required": true, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/docgen.snapshot new file mode 100644 index 000000000000..7b5b63c1440b --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/docgen.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9626-js-default-values 1`] = ` +"import React from 'react'; // eslint-disable-next-line react/prop-types + +export const Tag = ({ + title = 'Beta' +}) => /*#__PURE__*/React.createElement(\\"div\\", null, title); +export const component = Tag; +Tag.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Tag\\", + \\"props\\": { + \\"title\\": { + \\"defaultValue\\": { + \\"value\\": \\"'Beta'\\", + \\"computed\\": false + }, + \\"required\\": false + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/input.js b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/input.js new file mode 100644 index 000000000000..451411c1c1e4 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/input.js @@ -0,0 +1,5 @@ +import React from 'react'; + +// eslint-disable-next-line react/prop-types +export const Tag = ({ title = 'Beta' }) => <div>{title}</div>; +export const component = Tag; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/properties.snapshot new file mode 100644 index 000000000000..b1043c87f419 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9626-js-default-values/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9626-js-default-values 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'Beta'", + }, + "description": undefined, + "name": "title", + "required": false, + "type": Object { + "detail": undefined, + "summary": "unknown", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/docgen.snapshot new file mode 100644 index 000000000000..1d189717438e --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/docgen.snapshot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9668-js-proptypes-no-jsdoc 1`] = ` +"import React from 'react'; +import PropTypes from 'prop-types'; + +const CCTable = props => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(props)); + +CCTable.propTypes = { + heads: PropTypes.array.isRequired, + onAddClick: PropTypes.func +}; +export const component = CCTable; +CCTable.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"CCTable\\", + \\"props\\": { + \\"heads\\": { + \\"type\\": { + \\"name\\": \\"array\\" + }, + \\"required\\": true, + \\"description\\": \\"\\" + }, + \\"onAddClick\\": { + \\"type\\": { + \\"name\\": \\"func\\" + }, + \\"required\\": false, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/input.js b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/input.js new file mode 100644 index 000000000000..2e66bdd696d2 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/input.js @@ -0,0 +1,10 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const CCTable = (props) => <>{JSON.stringify(props)}</>; +CCTable.propTypes = { + heads: PropTypes.array.isRequired, + onAddClick: PropTypes.func, +}; + +export const component = CCTable; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/properties.snapshot new file mode 100644 index 000000000000..3c8760699b96 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9668-js-proptypes-no-jsdoc/properties.snapshot @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9668-js-proptypes-no-jsdoc 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "heads", + "required": true, + "sbType": Object { + "name": "array", + "value": undefined, + }, + "type": Object { + "detail": undefined, + "summary": "array", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "onAddClick", + "required": false, + "sbType": Object { + "name": "function", + }, + "type": Object { + "detail": undefined, + "summary": "func", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/docgen.snapshot new file mode 100644 index 000000000000..69821e3c56b5 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/docgen.snapshot @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9721-ts-deprecated-jsdoc 1`] = ` +"import React from 'react'; + +const Foo = props => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(props)); + +export const component = Foo; +Foo.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Foo\\", + \\"props\\": { + \\"width\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"number\\" + }, + \\"description\\": \\"@deprecated Do not use! Use \`size\` instead!\\\\n\\\\nWidth of foo\\" + }, + \\"size\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"signature\\", + \\"type\\": \\"object\\", + \\"raw\\": \\"{ width: number; height: number }\\", + \\"signature\\": { + \\"properties\\": [{ + \\"key\\": \\"width\\", + \\"value\\": { + \\"name\\": \\"number\\", + \\"required\\": true + } + }, { + \\"key\\": \\"height\\", + \\"value\\": { + \\"name\\": \\"number\\", + \\"required\\": true + } + }] + } + }, + \\"description\\": \\"The size (replaces width)\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/input.tsx new file mode 100644 index 000000000000..1d9fa51da825 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/input.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +interface FooProps { + /** + * @deprecated Do not use! Use `size` instead! + * + * Width of foo + */ + width: number; + /** + * The size (replaces width) + */ + size: { width: number; height: number }; +} + +const Foo: React.FC<FooProps> = (props: FooProps) => <>{JSON.stringify(props)}</>; + +export const component = Foo; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/properties.snapshot new file mode 100644 index 000000000000..db5588270f7c --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9721-ts-deprecated-jsdoc/properties.snapshot @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9721-ts-deprecated-jsdoc 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "width", + "required": true, + "sbType": Object { + "name": "number", + }, + "type": Object { + "detail": undefined, + "summary": "number", + }, + }, + Object { + "defaultValue": null, + "description": "The size (replaces width)", + "name": "size", + "required": true, + "sbType": Object { + "name": "object", + "raw": "{ width: number; height: number }", + "value": Object { + "height": Object { + "name": "number", + }, + "width": Object { + "name": "number", + }, + }, + }, + "type": Object { + "detail": undefined, + "summary": "signature", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/docgen.snapshot new file mode 100644 index 000000000000..af6e21ceac74 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/docgen.snapshot @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9764-ts-extend-props 1`] = ` +"import React from 'react'; + +const Radio = props => /*#__PURE__*/React.createElement(React.Fragment, null, JSON.stringify(props)); + +export const component = Radio; +Radio.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Radio\\", + \\"props\\": { + \\"value\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"union\\", + \\"raw\\": \\"string | number\\", + \\"elements\\": [{ + \\"name\\": \\"string\\" + }, { + \\"name\\": \\"number\\" + }] + }, + \\"description\\": \\"The input content value\\" + }, + \\"defaultChecked\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"\\" + }, + \\"checked\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/input.tsx new file mode 100644 index 000000000000..2abd61eed129 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/input.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +interface InputProps { + /** + * The input content value + */ + value?: string | number; +} + +interface RadioProps extends InputProps { + defaultChecked?: boolean; + checked?: boolean; +} + +const Radio: React.FC<RadioProps> = (props: RadioProps) => <>{JSON.stringify(props)}</>; + +export const component = Radio; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/properties.snapshot new file mode 100644 index 000000000000..3f3b110e6ddb --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9764-ts-extend-props/properties.snapshot @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9764-ts-extend-props 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "The input content value", + "name": "value", + "required": false, + "sbType": Object { + "name": "union", + "raw": "string | number", + "value": Array [ + Object { + "name": "string", + }, + Object { + "name": "number", + }, + ], + }, + "type": Object { + "detail": undefined, + "summary": "union", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "defaultChecked", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "checked", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/docgen.snapshot new file mode 100644 index 000000000000..e029b62e2a13 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/docgen.snapshot @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9827-ts-default-values 1`] = ` +"import React from 'react'; + +const Hello = ({ + title +}) => { + return /*#__PURE__*/React.createElement(\\"div\\", { + className: \\"hello\\" + }, \\"Hello Component \\", title); +}; + +Hello.defaultProps = { + title: 'this is the default :)' +}; +export const component = Hello; +Hello.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"Hello\\", + \\"props\\": { + \\"title\\": { + \\"defaultValue\\": { + \\"value\\": \\"'this is the default :)'\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"string\\" + }, + \\"description\\": \\"\\" + }, + \\"foo\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"\\" + }, + \\"bar\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"Array\\", + \\"elements\\": [{ + \\"name\\": \\"string\\" + }], + \\"raw\\": \\"string[]\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/input.tsx new file mode 100644 index 000000000000..373829aac3b3 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/input.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +export interface HelloProps { + title: string; + foo: boolean; + bar?: string[]; +} + +const Hello = ({ title }: HelloProps) => { + return <div className="hello">Hello Component {title}</div>; +}; + +Hello.defaultProps = { + title: 'this is the default :)', +}; + +export const component = Hello; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/properties.snapshot new file mode 100644 index 000000000000..5d944694e62b --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9827-ts-default-values/properties.snapshot @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9827-ts-default-values 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "'this is the default :)'", + }, + "description": "", + "name": "title", + "required": false, + "sbType": Object { + "name": "string", + }, + "type": Object { + "detail": undefined, + "summary": "string", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "foo", + "required": true, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + Object { + "defaultValue": null, + "description": "", + "name": "bar", + "required": false, + "sbType": Object { + "name": "array", + "raw": "string[]", + "value": Array [ + Object { + "name": "string", + }, + ], + }, + "type": Object { + "detail": undefined, + "summary": "Array", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/docgen.snapshot new file mode 100644 index 000000000000..2af0f01d2944 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/docgen.snapshot @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9832-ts-enum-export 1`] = ` +"import React from 'react'; +export let EnumWithExtraProps; + +(function (EnumWithExtraProps) { + EnumWithExtraProps[\\"key1\\"] = \\"key1\\"; + EnumWithExtraProps[\\"key2\\"] = \\"key2\\"; +})(EnumWithExtraProps || (EnumWithExtraProps = {})); + +export const component = () => /*#__PURE__*/React.createElement(\\"div\\", null, \\"hello\\"); +component.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"component\\" +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/input.tsx new file mode 100644 index 000000000000..3072adb5705e --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/input.tsx @@ -0,0 +1,8 @@ +import React from 'react'; + +export enum EnumWithExtraProps { + key1 = 'key1', + key2 = 'key2', +} + +export const component = () => <div>hello</div>; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/properties.snapshot new file mode 100644 index 000000000000..85dec505d2c5 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9832-ts-enum-export/properties.snapshot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9832-ts-enum-export 1`] = ` +Object { + "rows": Array [], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/docgen.snapshot new file mode 100644 index 000000000000..82936b526eef --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/docgen.snapshot @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9922-ts-component-props 1`] = ` +"import React from 'react'; + +const Button = ({ + children, + onClick +}) => /*#__PURE__*/React.createElement(\\"button\\", { + onClick: onClick, + type: \\"button\\" +}, children); + +const WrappedButton = ({ + spacing, + ...buttonProps +}) => /*#__PURE__*/React.createElement(\\"div\\", { + style: { + padding: spacing + } +}, /*#__PURE__*/React.createElement(Button, buttonProps)); + +export const component = WrappedButton; +WrappedButton.__docgenInfo = { + \\"description\\": \\"\\", + \\"methods\\": [], + \\"displayName\\": \\"WrappedButton\\", + \\"props\\": { + \\"spacing\\": { + \\"required\\": true, + \\"tsType\\": { + \\"name\\": \\"number\\" + }, + \\"description\\": \\"\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/input.tsx new file mode 100644 index 000000000000..797836d2c0d5 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/input.tsx @@ -0,0 +1,23 @@ +import React, { FunctionComponent, ComponentProps, HTMLAttributes } from 'react'; + +type Props = Pick<HTMLAttributes<HTMLButtonElement>, 'onClick'>; +const Button: FunctionComponent<Props> = ({ children, onClick }) => ( + <button onClick={onClick} type="button"> + {children} + </button> +); + +type WrappedProps = { + spacing: number; +} & ComponentProps<typeof Button>; + +const WrappedButton: FunctionComponent<WrappedProps> = ({ + spacing, + ...buttonProps +}: WrappedProps) => ( + <div style={{ padding: spacing }}> + <Button {...buttonProps} /> + </div> +); + +export const component = WrappedButton; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/properties.snapshot new file mode 100644 index 000000000000..d9342f6004e2 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/9922-ts-component-props/properties.snapshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties 9922-ts-component-props 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "", + "name": "spacing", + "required": true, + "sbType": Object { + "name": "number", + }, + "type": Object { + "detail": undefined, + "summary": "number", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/docgen.snapshot new file mode 100644 index 000000000000..aedd22813aab --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/docgen.snapshot @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties js-class-component 1`] = ` +"/* eslint-disable react/prefer-stateless-function */ +import React from 'react'; +import PropTypes from 'prop-types'; +/** + * Component description + */ + +class ErrorBox extends React.Component { + render() { + const { + children + } = this.props; + return /*#__PURE__*/React.createElement(\\"div\\", { + className: \\"error-box\\" + }, children); + } + +} + +ErrorBox.propTypes = { + /** + * PropTypes description + */ + children: PropTypes.node.isRequired +}; +ErrorBox.__docgenInfo = { + \\"description\\": \\"Component description\\", + \\"methods\\": [], + \\"displayName\\": \\"ErrorBox\\", + \\"props\\": { + \\"children\\": { + \\"type\\": { + \\"name\\": \\"node\\" + }, + \\"required\\": true, + \\"description\\": \\"PropTypes description\\" + } + } +}; +export default ErrorBox; +export const component = ErrorBox;" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/input.js b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/input.js new file mode 100644 index 000000000000..c94bb47ca22b --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/input.js @@ -0,0 +1,24 @@ +/* eslint-disable react/prefer-stateless-function */ +import React from 'react'; +import PropTypes from 'prop-types'; + +/** + * Component description + */ +class ErrorBox extends React.Component { + render() { + const { children } = this.props; + + return <div className="error-box">{children}</div>; + } +} + +ErrorBox.propTypes = { + /** + * PropTypes description + */ + children: PropTypes.node.isRequired, +}; + +export default ErrorBox; +export const component = ErrorBox; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/properties.snapshot new file mode 100644 index 000000000000..abceadfadeaf --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/js-class-component/properties.snapshot @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties js-class-component 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": null, + "description": "PropTypes description", + "name": "children", + "required": true, + "sbType": Object { + "name": "other", + "value": "node", + }, + "type": Object { + "detail": undefined, + "summary": "node", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/docgen.snapshot new file mode 100644 index 000000000000..5905ae39b421 --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/docgen.snapshot @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties ts-function-component 1`] = ` +"import React from 'react'; + +/** + * The world's most _basic_ button + */ +export const Button = ({ + onClick +}) => /*#__PURE__*/React.createElement(\\"button\\", { + onClick: onClick, + type: \\"button\\" +}, \\"hello\\"); +Button.defaultProps = { + primary: true, + secondary: false +}; +export const component = Button; +Button.__docgenInfo = { + \\"description\\": \\"The world's most _basic_ button\\", + \\"methods\\": [], + \\"displayName\\": \\"Button\\", + \\"props\\": { + \\"primary\\": { + \\"defaultValue\\": { + \\"value\\": \\"true\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"Is primary?\\" + }, + \\"secondary\\": { + \\"defaultValue\\": { + \\"value\\": \\"false\\", + \\"computed\\": false + }, + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"boolean\\" + }, + \\"description\\": \\"default is false\\" + }, + \\"onClick\\": { + \\"required\\": false, + \\"tsType\\": { + \\"name\\": \\"signature\\", + \\"type\\": \\"function\\", + \\"raw\\": \\"() => void\\", + \\"signature\\": { + \\"arguments\\": [], + \\"return\\": { + \\"name\\": \\"void\\" + } + } + }, + \\"description\\": \\"Simple click handler\\" + } + } +};" +`; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/input.tsx b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/input.tsx new file mode 100644 index 000000000000..fdeea8fa6c8a --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/input.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +interface ButtonProps { + /** + * Simple click handler + */ + onClick?: () => void; + + /** + * Is primary? + */ + primary?: boolean; + + /** + * default is false + */ + secondary?: boolean; +} + +/** + * The world's most _basic_ button + */ +export const Button: React.FC<ButtonProps> = ({ onClick }: ButtonProps) => ( + <button onClick={onClick} type="button"> + hello + </button> +); + +Button.defaultProps = { + primary: true, + secondary: false, +}; + +export const component = Button; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/properties.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/properties.snapshot new file mode 100644 index 000000000000..85ea3277565e --- /dev/null +++ b/addons/docs/src/frameworks/react/__testfixtures__/ts-function-component/properties.snapshot @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react component properties ts-function-component 1`] = ` +Object { + "rows": Array [ + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "true", + }, + "description": "Is primary?", + "name": "primary", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + Object { + "defaultValue": Object { + "detail": undefined, + "summary": "false", + }, + "description": "default is false", + "name": "secondary", + "required": false, + "sbType": Object { + "name": "boolean", + }, + "type": Object { + "detail": undefined, + "summary": "boolean", + }, + }, + Object { + "defaultValue": null, + "description": "Simple click handler", + "name": "onClick", + "required": false, + "sbType": Object { + "name": "function", + "raw": "() => void", + }, + "type": Object { + "detail": undefined, + "summary": "signature", + }, + }, + ], +} +`; diff --git a/addons/docs/src/frameworks/react/config.ts b/addons/docs/src/frameworks/react/config.ts index 11c34986a0a6..c84f20f7025f 100644 --- a/addons/docs/src/frameworks/react/config.ts +++ b/addons/docs/src/frameworks/react/config.ts @@ -1,15 +1,13 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/client-api'; import { StoryFn } from '@storybook/addons'; -import { extractProps } from './extractProps'; +import { extractArgTypes } from './extractArgTypes'; import { extractComponentDescription } from '../../lib/docgen'; -addParameters({ +export const parameters = { docs: { - // react is Storybook's "native" framework, so it's stories are inherently prepared to be rendered inline + inlineStories: true, // NOTE: that the result is a react element. Hooks support is provided by the outer code. prepareForInline: (storyFn: StoryFn) => storyFn(), - extractProps, + extractArgTypes, extractComponentDescription, }, -}); +}; diff --git a/addons/docs/src/frameworks/react/extractArgTypes.ts b/addons/docs/src/frameworks/react/extractArgTypes.ts new file mode 100644 index 000000000000..fa35147a79ad --- /dev/null +++ b/addons/docs/src/frameworks/react/extractArgTypes.ts @@ -0,0 +1,36 @@ +import { PropDef, PropsTableRowsProps } from '@storybook/components'; +import { ArgTypes } from '@storybook/api'; +import { ArgTypesExtractor } from '../../lib/docgen'; +import { extractProps } from './extractProps'; + +export const extractArgTypes: ArgTypesExtractor = (component) => { + if (component) { + const props = extractProps(component); + const { rows } = props as PropsTableRowsProps; + if (rows) { + return rows.reduce((acc: ArgTypes, row: PropDef) => { + const { type, sbType, defaultValue: defaultSummary, jsDocTags, required } = row; + let defaultValue = defaultSummary && (defaultSummary.detail || defaultSummary.summary); + try { + // eslint-disable-next-line no-eval + defaultValue = eval(defaultValue); + // eslint-disable-next-line no-empty + } catch {} + + acc[row.name] = { + ...row, + defaultValue, + type: { required, ...sbType }, + table: { + type, + jsDocTags, + defaultValue: defaultSummary, + }, + }; + return acc; + }, {}); + } + } + + return null; +}; diff --git a/addons/docs/src/frameworks/react/extractProps.ts b/addons/docs/src/frameworks/react/extractProps.ts index 20d3032054c6..e087abbfa380 100644 --- a/addons/docs/src/frameworks/react/extractProps.ts +++ b/addons/docs/src/frameworks/react/extractProps.ts @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import { isForwardRef, isMemo } from 'react-is'; import { PropDef } from '@storybook/components'; import { hasDocgen, extractComponentProps, PropsExtractor, TypeSystem } from '../../lib/docgen'; -import { Component } from '../../blocks/shared'; +import { Component } from '../../blocks/types'; import { enhancePropTypesProps } from './propTypes/handleProp'; import { enhanceTypeScriptProps } from './typeScript/handleProp'; @@ -12,7 +12,7 @@ export interface PropDefMap { const propTypesMap = new Map(); -Object.keys(PropTypes).forEach(typeName => { +Object.keys(PropTypes).forEach((typeName) => { // @ts-ignore const type = PropTypes[typeName]; @@ -26,7 +26,7 @@ function getPropDefs(component: Component, section: string): PropDef[] { // eslint-disable-next-line react/forbid-foreign-prop-types if (!hasDocgen(component) && !component.propTypes) { if (isForwardRef(component) || component.render) { - processedComponent = component.render().type; + processedComponent = component.render({}).type; } if (isMemo(component)) { processedComponent = component.type().type; @@ -44,10 +44,10 @@ function getPropDefs(component: Component, section: string): PropDef[] { case TypeSystem.TYPESCRIPT: return enhanceTypeScriptProps(extractedProps); default: - return extractedProps.map(x => x.propDef); + return extractedProps.map((x) => x.propDef); } } -export const extractProps: PropsExtractor = component => ({ +export const extractProps: PropsExtractor = (component) => ({ rows: getPropDefs(component, 'props'), }); diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts b/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts index 09ead809bc50..80012d0f0552 100644 --- a/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts +++ b/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash'; import { PropDefaultValue } from '@storybook/components'; import { FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions'; import { @@ -19,7 +18,7 @@ import { getPrettyIdentifier } from './prettyIdentifier'; function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue { const { identifier } = inferedType as InspectionFunction; - if (!isNil(identifier)) { + if (identifier != null) { return createSummaryValue( getPrettyIdentifier(inferedType as InspectionIdentifiableInferedType), generateCode(ast) @@ -42,7 +41,7 @@ function generateElement( const { inferedType } = inspectionResult; const { identifier } = inferedType as InspectionElement; - if (!isNil(identifier)) { + if (identifier != null) { if (!isHtmlTag(identifier)) { const prettyIdentifier = getPrettyIdentifier( inferedType as InspectionIdentifiableInferedType diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts index 9adcce0091f9..e9caa16b9fac 100644 --- a/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts +++ b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts @@ -1,5 +1,7 @@ import { PropDefaultValue, PropDef } from '@storybook/components'; -import { isNil, isPlainObject, isArray, isFunction, isString } from 'lodash'; +import isPlainObject from 'lodash/isPlainObject'; +import isFunction from 'lodash/isFunction'; +import isString from 'lodash/isString'; // @ts-ignore import reactElementToJSXString from 'react-element-to-jsx-string'; import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib'; @@ -20,7 +22,7 @@ export interface TypeResolvers { } function isReactElement(element: any): boolean { - return !isNil(element.$$typeof); + return element.$$typeof != null; } export function extractFunctionName(func: Function, propName: string): string { @@ -34,7 +36,7 @@ export function extractFunctionName(func: Function, propName: string): string { return null; } -const stringResolver: TypeResolver = rawDefaultProp => { +const stringResolver: TypeResolver = (rawDefaultProp) => { return createSummaryValue(rawDefaultProp); }; @@ -44,7 +46,7 @@ function generateReactObject(rawDefaultProp: any) { const jsx = reactElementToJSXString(rawDefaultProp); - if (!isNil(displayName)) { + if (displayName != null) { const prettyIdentifier = getPrettyElementIdentifier(displayName); return createSummaryValue(prettyIdentifier, prettyIdentifier !== jsx ? jsx : undefined); @@ -65,8 +67,8 @@ function generateReactObject(rawDefaultProp: any) { return createSummaryValue(ELEMENT_CAPTION, jsx); } -const objectResolver: TypeResolver = rawDefaultProp => { - if (isReactElement(rawDefaultProp) && !isNil(rawDefaultProp.type)) { +const objectResolver: TypeResolver = (rawDefaultProp) => { + if (isReactElement(rawDefaultProp) && rawDefaultProp.type != null) { return generateReactObject(rawDefaultProp); } @@ -76,7 +78,7 @@ const objectResolver: TypeResolver = rawDefaultProp => { return generateObject(inspectionResult); } - if (isArray(rawDefaultProp)) { + if (Array.isArray(rawDefaultProp)) { const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp)); return generateArray(inspectionResult); @@ -92,7 +94,7 @@ const functionResolver: TypeResolver = (rawDefaultProp, propDef) => { // Try to display the name of the component. The body of the component is ommited since the code has been transpiled. if (isFunction(rawDefaultProp.render)) { isElement = true; - } else if (!isNil(rawDefaultProp.prototype) && isFunction(rawDefaultProp.prototype.render)) { + } else if (rawDefaultProp.prototype != null && isFunction(rawDefaultProp.prototype.render)) { isElement = true; } else { let innerElement; @@ -110,7 +112,7 @@ const functionResolver: TypeResolver = (rawDefaultProp, propDef) => { innerElement = rawDefaultProp(); } - if (!isNil(innerElement)) { + if (innerElement != null) { if (isReactElement(innerElement)) { isElement = true; } @@ -121,12 +123,12 @@ const functionResolver: TypeResolver = (rawDefaultProp, propDef) => { } const funcName = extractFunctionName(rawDefaultProp, propDef.name); - if (!isNil(funcName)) { + if (funcName != null) { if (isElement) { return createSummaryValue(getPrettyElementIdentifier(funcName)); } - if (!isNil(inspectionResult)) { + if (inspectionResult != null) { inspectionResult = inspectValue(rawDefaultProp.toString()); } @@ -138,7 +140,7 @@ const functionResolver: TypeResolver = (rawDefaultProp, propDef) => { return createSummaryValue(isElement ? ELEMENT_CAPTION : FUNCTION_CAPTION); }; -const defaultResolver: TypeResolver = rawDefaultProp => { +const defaultResolver: TypeResolver = (rawDefaultProp) => { return createSummaryValue(rawDefaultProp.toString()); }; @@ -168,8 +170,7 @@ export function createDefaultValueFromRawDefaultProp( ): PropDefaultValue { try { // Keep the extra () otherwise it will fail for functions. - // eslint-disable-next-line prettier/prettier - switch (typeof (rawDefaultProp)) { + switch (typeof rawDefaultProp) { case 'string': return typeResolvers.string(rawDefaultProp, propDef); case 'object': diff --git a/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts index 8ffe6fad77a4..2685f05efaf3 100644 --- a/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts @@ -233,7 +233,7 @@ describe('parse', () => { { name: 'boolean (true)', value: 'true' }, { name: 'boolean (false)', value: 'false' }, { name: 'null', value: 'null' }, - ].forEach(x => { + ].forEach((x) => { it(`support ${x.name}`, () => { const result = parse(x.value); const inferedType = result.inferedType as InspectionLiteral; diff --git a/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts index 60b552abc2fc..26a085fcdb9b 100644 --- a/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts @@ -1,9 +1,7 @@ import { Parser } from 'acorn'; -// @ts-ignore import jsx from 'acorn-jsx'; -import { isNil } from 'lodash'; +// eslint-disable-next-line import/no-extraneous-dependencies import estree from 'estree'; -// @ts-ignore import * as acornWalk from 'acorn-walk'; import { InspectionType, @@ -24,6 +22,7 @@ interface ParsingResult<T> { } const ACORN_WALK_VISITORS = { + // @ts-ignore ...acornWalk.base, JSXElement: () => {}, }; @@ -32,17 +31,18 @@ const acornParser = Parser.extend(jsx()); // Cannot use "estree.Identifier" type because this function also support "JSXIdentifier". function extractIdentifierName(identifierNode: any) { - return !isNil(identifierNode) ? identifierNode.name : null; + return identifierNode != null ? identifierNode.name : null; } function filterAncestors(ancestors: estree.Node[]): estree.Node[] { - return ancestors.filter(x => x.type === 'ObjectExpression' || x.type === 'ArrayExpression'); + return ancestors.filter((x) => x.type === 'ObjectExpression' || x.type === 'ArrayExpression'); } function calculateNodeDepth(node: estree.Expression): number { const depths: number[] = []; acornWalk.ancestor( + // @ts-ignore node, { ObjectExpression(_: any, ancestors: estree.Node[]) { @@ -82,6 +82,7 @@ function parseFunction( // If there is at least a JSXElement in the body of the function, then it's a React component. acornWalk.simple( + // @ts-ignore funcNode.body, { JSXElement(node: any) { @@ -91,7 +92,7 @@ function parseFunction( ACORN_WALK_VISITORS ); - const isJsx = !isNil(innerJsxElementNode); + const isJsx = innerJsxElementNode != null; const inferedType: InspectionFunction | InspectionElement = { type: isJsx ? InspectionType.ELEMENT : InspectionType.FUNCTION, @@ -100,7 +101,7 @@ function parseFunction( }; const identifierName = extractIdentifierName((funcNode as estree.FunctionExpression).id); - if (!isNil(identifierName)) { + if (identifierName != null) { inferedType.identifier = identifierName; } @@ -117,6 +118,7 @@ function parseClass( // If there is at least a JSXElement in the body of the class, then it's a React component. acornWalk.simple( + // @ts-ignore classNode.body, { JSXElement(node: any) { @@ -127,7 +129,7 @@ function parseClass( ); const inferedType: any = { - type: !isNil(innerJsxElementNode) ? InspectionType.ELEMENT : InspectionType.CLASS, + type: innerJsxElementNode != null ? InspectionType.ELEMENT : InspectionType.CLASS, identifier: extractIdentifierName(classNode.id), }; @@ -143,7 +145,7 @@ function parseJsxElement(jsxElementNode: any): ParsingResult<InspectionElement> }; const identifierName = extractIdentifierName(jsxElementNode.openingElement.name); - if (!isNil(identifierName)) { + if (identifierName != null) { inferedType.identifier = identifierName; } @@ -212,13 +214,13 @@ export function parse(value: string): ParsingResult<InspectionInferedType> { ast, }; - if (!isNil(ast.body[0])) { + if (ast.body[0] != null) { const rootNode = ast.body[0]; switch (rootNode.type) { case 'ExpressionStatement': { const expressionResult = parseExpression(rootNode.expression); - if (!isNil(expressionResult)) { + if (expressionResult != null) { parsingResult = expressionResult as any; } break; diff --git a/addons/docs/src/frameworks/react/propTypes/createType.ts b/addons/docs/src/frameworks/react/propTypes/createType.ts index 84a9e55f5620..f75ea0edbe4f 100644 --- a/addons/docs/src/frameworks/react/propTypes/createType.ts +++ b/addons/docs/src/frameworks/react/propTypes/createType.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash'; import { PropType } from '@storybook/components'; import { createSummaryValue, isTooLongForTypeSummary } from '../../../lib'; import { ExtractedProp, DocgenPropType } from '../../../lib/docgen'; @@ -74,7 +73,7 @@ function createTypeDef({ name, short, compact, - full: !isNil(full) ? full : short, + full: full != null ? full : short, inferedType, }; } @@ -137,7 +136,7 @@ function generateTypeFromString(value: string, originalTypeName: string): TypeDe case InspectionType.ELEMENT: { const { identifier } = inferedType as InspectionElement; - short = !isNil(identifier) && !isHtmlTag(identifier) ? identifier : ELEMENT_CAPTION; + short = identifier != null && !isHtmlTag(identifier) ? identifier : ELEMENT_CAPTION; compact = splitIntoLines(value).length === 1 ? value : null; full = value; break; @@ -167,7 +166,7 @@ function generateTypeFromString(value: string, originalTypeName: string): TypeDe } function generateCustom({ raw }: DocgenPropType): TypeDef { - if (!isNil(raw)) { + if (raw != null) { return generateTypeFromString(raw, PropTypesType.CUSTOM); } @@ -181,8 +180,8 @@ function generateCustom({ raw }: DocgenPropType): TypeDef { function generateFunc(extractedProp: ExtractedProp): TypeDef { const { jsDocTags } = extractedProp; - if (!isNil(jsDocTags)) { - if (!isNil(jsDocTags.params) || !isNil(jsDocTags.returns)) { + if (jsDocTags != null) { + if (jsDocTags.params != null || jsDocTags.returns != null) { return createTypeDef({ name: PropTypesType.FUNC, short: generateShortFuncSignature(jsDocTags.params, jsDocTags.returns), @@ -210,8 +209,8 @@ function generateShape(type: DocgenPropType, extractedProp: ExtractedProp): Type return createTypeDef({ name: PropTypesType.SHAPE, short: OBJECT_CAPTION, - compact: depth === 1 ? prettyObject(ast, true) : null, - full: prettyObject(ast), + compact: depth === 1 && ast ? prettyObject(ast, true) : null, + full: ast ? prettyObject(ast) : null, }); } @@ -225,7 +224,7 @@ function generateObjectOf(type: DocgenPropType, extractedProp: ExtractedProp): T return createTypeDef({ name: PropTypesType.OBJECTOF, short: objectOf(short), - compact: !isNil(compact) ? objectOf(compact) : null, + compact: compact != null ? objectOf(compact) : null, full: objectOf(full), }); } @@ -248,7 +247,7 @@ function generateUnion(type: DocgenPropType, extractedProp: ExtractedProp): Type return createTypeDef({ name: PropTypesType.UNION, short: values.short.join(' | '), - compact: values.compact.every((x: string) => !isNil(x)) ? values.compact.join(' | ') : null, + compact: values.compact.every((x: string) => x != null) ? values.compact.join(' | ') : null, full: values.full.join(' | '), }); } @@ -280,7 +279,7 @@ function generateEnum(type: DocgenPropType): TypeDef { return createTypeDef({ name: PropTypesType.ENUM, short: values.short.join(' | '), - compact: values.compact.every((x: string) => !isNil(x)) ? values.compact.join(' | ') : null, + compact: values.compact.every((x: string) => x != null) ? values.compact.join(' | ') : null, full: values.full.join(' | '), }); } @@ -300,7 +299,7 @@ function createArrayOfObjectTypeDef(short: string, compact: string, full: string return createTypeDef({ name: PropTypesType.ARRAYOF, short: braceAfter(short), - compact: !isNil(compact) ? braceAround(compact) : null, + compact: compact != null ? braceAround(compact) : null, full: braceAround(full), }); } @@ -361,7 +360,7 @@ export function createType(extractedProp: ExtractedProp): PropType { const { type } = extractedProp.docgenInfo; // A type could be null if a defaultProp has been provided without a type definition. - if (isNil(type)) { + if (type == null) { return null; } @@ -376,7 +375,7 @@ export function createType(extractedProp: ExtractedProp): PropType { case PropTypesType.ARRAYOF: { const { short, compact, full } = generateType(type, extractedProp); - if (!isNil(compact)) { + if (compact != null) { if (!isTooLongForTypeSummary(compact)) { return createSummaryValue(compact); } diff --git a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts index 63624c418958..0d6b6c251d86 100644 --- a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts +++ b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts @@ -1,12 +1,11 @@ -import { isNil } from 'lodash'; import { ExtractedJsDocParam, ExtractedJsDocReturns } from '../../../lib/jsdocParser'; export function generateFuncSignature( params: ExtractedJsDocParam[], returns: ExtractedJsDocReturns ): string { - const hasParams = !isNil(params); - const hasReturns = !isNil(returns); + const hasParams = params != null; + const hasReturns = returns != null; if (!hasParams && !hasReturns) { return ''; @@ -19,7 +18,7 @@ export function generateFuncSignature( const prettyName = x.getPrettyName(); const typeName = x.getTypeName(); - if (!isNil(typeName)) { + if (typeName != null) { return `${prettyName}: ${typeName}`; } @@ -42,8 +41,8 @@ export function generateShortFuncSignature( params: ExtractedJsDocParam[], returns: ExtractedJsDocReturns ): string { - const hasParams = !isNil(params); - const hasReturns = !isNil(returns); + const hasParams = params != null; + const hasReturns = returns != null; if (!hasParams && !hasReturns) { return ''; diff --git a/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx b/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx index 71417d46effe..cd0f24000ac5 100644 --- a/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx +++ b/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx @@ -3,7 +3,7 @@ import { PropDef } from '@storybook/components'; import PropTypes from 'prop-types'; import React from 'react'; -import { Component } from '../../../blocks/shared'; +import { Component } from '../../../blocks/types'; import { extractComponentProps, DocgenInfo, DocgenPropDefaultValue } from '../../../lib/docgen'; import { enhancePropTypesProp, enhancePropTypesProps } from './handleProp'; @@ -251,7 +251,7 @@ describe('enhancePropTypesProp', () => { 'element', 'elementType', 'node', - ].forEach(x => { + ].forEach((x) => { it(`should return '${x}' when type is ${x}`, () => { const component = createTestComponent({ type: { @@ -1173,7 +1173,7 @@ describe('enhancePropTypesProp', () => { { type: 'number', defaultProp: 1 }, { type: 'boolean', defaultProp: true }, { type: 'symbol', defaultProp: Symbol('hey!') }, - ].forEach(x => { + ].forEach((x) => { it(`should support ${x.type}`, () => { const component = createTestComponent(null); @@ -1327,7 +1327,7 @@ describe('enhancePropTypesProp', () => { expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); }); - ['element', 'elementType'].forEach(x => { + ['element', 'elementType'].forEach((x) => { it(`should support inlined React class component for ${x}`, () => { const component = createTestComponent(null, x); diff --git a/addons/docs/src/frameworks/react/propTypes/handleProp.ts b/addons/docs/src/frameworks/react/propTypes/handleProp.ts index 819b223cbdbb..f85b1ccb5605 100644 --- a/addons/docs/src/frameworks/react/propTypes/handleProp.ts +++ b/addons/docs/src/frameworks/react/propTypes/handleProp.ts @@ -1,9 +1,8 @@ -import { isNil } from 'lodash'; import { PropDef } from '@storybook/components'; import { ExtractedProp } from '../../../lib/docgen'; import { createType } from './createType'; import { createDefaultValue, createDefaultValueFromRawDefaultProp } from '../lib/defaultValues'; -import { Component } from '../../../blocks/shared'; +import { Component } from '../../../blocks/types'; import { keepOriginalDefinitionOrder } from './sortProps'; import { rawDefaultPropTypeResolvers } from './rawDefaultPropResolvers'; @@ -11,25 +10,25 @@ export function enhancePropTypesProp(extractedProp: ExtractedProp, rawDefaultPro const { propDef } = extractedProp; const newtype = createType(extractedProp); - if (!isNil(newtype)) { + if (newtype != null) { propDef.type = newtype; } const { defaultValue } = extractedProp.docgenInfo; - if (!isNil(defaultValue) && !isNil(defaultValue.value)) { + if (defaultValue != null && defaultValue.value != null) { const newDefaultValue = createDefaultValue(defaultValue.value); - if (!isNil(newDefaultValue)) { + if (newDefaultValue != null) { propDef.defaultValue = newDefaultValue; } - } else if (!isNil(rawDefaultProp)) { + } else if (rawDefaultProp != null) { const newDefaultValue = createDefaultValueFromRawDefaultProp( rawDefaultProp, propDef, rawDefaultPropTypeResolvers ); - if (!isNil(newDefaultValue)) { + if (newDefaultValue != null) { propDef.defaultValue = newDefaultValue; } } @@ -41,8 +40,8 @@ export function enhancePropTypesProps( extractedProps: ExtractedProp[], component: Component ): PropDef[] { - const rawDefaultProps = !isNil(component.defaultProps) ? component.defaultProps : {}; - const enhancedProps = extractedProps.map(x => + const rawDefaultProps = component.defaultProps != null ? component.defaultProps : {}; + const enhancedProps = extractedProps.map((x) => enhancePropTypesProp(x, rawDefaultProps[x.propDef.name]) ); diff --git a/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts b/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts index 452396222a93..923067c65c67 100644 --- a/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts +++ b/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash'; import { TypeResolver, extractFunctionName, createTypeResolvers } from '../lib/defaultValues'; import { createSummaryValue } from '../../../lib'; import { FUNCTION_CAPTION, ELEMENT_CAPTION } from '../lib'; @@ -12,7 +11,7 @@ const funcResolver: TypeResolver = (rawDefaultProp, { name, type }) => { const isElement = type.summary === 'element' || type.summary === 'elementType'; const funcName = extractFunctionName(rawDefaultProp, name); - if (!isNil(funcName)) { + if (funcName != null) { // Try to display the name of the component. The body of the component is ommited since the code has been transpiled. if (isElement) { return createSummaryValue(getPrettyElementIdentifier(funcName)); diff --git a/addons/docs/src/frameworks/react/propTypes/sortProps.ts b/addons/docs/src/frameworks/react/propTypes/sortProps.ts index 9061ee0ded52..b676481edd18 100644 --- a/addons/docs/src/frameworks/react/propTypes/sortProps.ts +++ b/addons/docs/src/frameworks/react/propTypes/sortProps.ts @@ -1,6 +1,5 @@ import { PropDef } from '@storybook/components'; -import { isNil } from 'lodash'; -import { Component } from '../../../blocks/shared'; +import { Component } from '../../../blocks/types'; // react-docgen doesn't returned the props in the order they were defined in the "propTypes" object of the component. // This function re-order them by their original definition order. @@ -11,10 +10,10 @@ export function keepOriginalDefinitionOrder( // eslint-disable-next-line react/forbid-foreign-prop-types const { propTypes } = component; - if (!isNil(propTypes)) { + if (propTypes != null) { return Object.keys(propTypes) - .map(x => extractedProps.find(y => y.name === x)) - .filter(x => x); + .map((x) => extractedProps.find((y) => y.name === x)) + .filter((x) => x); } return extractedProps; diff --git a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx new file mode 100644 index 000000000000..cb1d3d6a10fa --- /dev/null +++ b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx @@ -0,0 +1,106 @@ +import React, { useState } from 'react'; +import mapValues from 'lodash/mapValues'; +import { storiesOf } from '@storybook/react'; +import { ArgsTable } from '@storybook/components'; +import { Args } from '@storybook/api'; +import { combineParameters } from '@storybook/client-api'; + +import { extractArgTypes } from './extractArgTypes'; +import { inferControls } from '../common/inferControls'; +import { Component } from '../../blocks'; + +const argsTableProps = (component: Component) => { + const argTypes = extractArgTypes(component); + const controls = inferControls(argTypes); + const rows = combineParameters(argTypes, controls); + return { rows }; +}; + +const ArgsStory = ({ component }: any) => { + const { rows } = argsTableProps(component); + const initialArgs = mapValues(rows, (argType) => argType.defaultValue) as Args; + + const [args, setArgs] = useState(initialArgs); + return ( + <> + <table> + <tr> + <th>key</th> + <th>val</th> + </tr> + {Object.entries(args).map(([key, val]) => ( + <tr key={key}> + <td>{key}</td> + <td>{JSON.stringify(val, null, 2)}</td> + </tr> + ))} + </table> + <ArgsTable rows={rows} args={args} updateArgs={(val) => setArgs({ ...args, ...val })} /> + </> + ); +}; + +const typescriptFixtures = [ + 'aliases', + 'arrays', + 'enums', + 'functions', + 'interfaces', + 'intersections', + 'records', + 'scalars', + 'tuples', + 'unions', +]; + +const typescriptStories = storiesOf('ArgTypes/TypeScript', module); +typescriptFixtures.forEach((fixture) => { + // eslint-disable-next-line import/no-dynamic-require, global-require, no-shadow + const { Component } = require(`../../lib/sbtypes/__testfixtures__/typescript/${fixture}`); + typescriptStories.add(fixture, () => <ArgsStory component={Component} />); +}); + +const proptypesFixtures = ['arrays', 'enums', 'misc', 'objects', 'react', 'scalars']; + +const proptypesStories = storiesOf('ArgTypes/PropTypes', module); +proptypesFixtures.forEach((fixture) => { + // eslint-disable-next-line import/no-dynamic-require, global-require, no-shadow + const { Component } = require(`../../lib/sbtypes/__testfixtures__/proptypes/${fixture}`); + proptypesStories.add(fixture, () => <ArgsStory component={Component} />); +}); + +const issuesFixtures = [ + 'js-class-component', + 'ts-function-component', + '9399-js-proptypes-shape', + '8663-js-styled-components', + '9626-js-default-values', + '9668-js-proptypes-no-jsdoc', + '8143-ts-react-fc-generics', + '8143-ts-imported-types', + '8279-js-styled-docgen', + '8140-js-prop-types-oneof', + '9023-js-hoc', + '8740-ts-multi-props', + '9556-ts-react-default-exports', + '9592-ts-styled-props', + '9591-ts-import-types', + '9721-ts-deprecated-jsdoc', + '9827-ts-default-values', + '9586-js-react-memo', + '9575-ts-camel-case', + '9493-ts-display-name', + '8894-9511-ts-forward-ref', + '9465-ts-type-props', + '8428-js-static-prop-types', + '9764-ts-extend-props', + '9922-ts-component-props', +]; + +const issuesStories = storiesOf('ArgTypes/Issues', module); +issuesFixtures.forEach((fixture) => { + // eslint-disable-next-line import/no-dynamic-require, global-require + const { component } = require(`./__testfixtures__/${fixture}/input`); + + issuesStories.add(fixture, () => <ArgsStory component={component} />); +}); diff --git a/addons/docs/src/frameworks/react/react-properties.test.ts b/addons/docs/src/frameworks/react/react-properties.test.ts new file mode 100644 index 000000000000..3be8aff69215 --- /dev/null +++ b/addons/docs/src/frameworks/react/react-properties.test.ts @@ -0,0 +1,67 @@ +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs'; + +import { transformFileSync, transformSync } from '@babel/core'; +import requireFromString from 'require-from-string'; + +import { extractProps } from './extractProps'; +import { normalizeNewlines } from '../../lib/utils'; + +// File hierarchy: +// __testfixtures__ / some-test-case / input.* +const inputRegExp = /^input\..*$/; + +const transformToModule = (inputCode: string) => { + const options = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + esmodules: true, + }, + }, + ], + ], + }; + const { code } = transformSync(inputCode, options); + return normalizeNewlines(code); +}; + +const annotateWithDocgen = (inputPath: string) => { + const options = { + presets: ['@babel/typescript', '@babel/react'], + plugins: ['babel-plugin-react-docgen', '@babel/plugin-proposal-class-properties'], + babelrc: false, + }; + const { code } = transformFileSync(inputPath, options); + return normalizeNewlines(code); +}; + +describe('react component properties', () => { + const fixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(fixturesDir, { withFileTypes: true }).forEach((testEntry) => { + if (testEntry.isDirectory()) { + const testDir = path.join(fixturesDir, testEntry.name); + const testFile = fs.readdirSync(testDir).find((fileName) => inputRegExp.test(fileName)); + if (testFile) { + it(testEntry.name, () => { + const inputPath = path.join(testDir, testFile); + + // snapshot the output of babel-plugin-react-docgen + const docgenPretty = annotateWithDocgen(inputPath); + expect(docgenPretty).toMatchSpecificSnapshot(path.join(testDir, 'docgen.snapshot')); + + // transform into an uglier format that's works with require-from-string + const docgenModule = transformToModule(docgenPretty); + + // snapshot the output of component-properties/react + const { component } = requireFromString(docgenModule); + const properties = extractProps(component); + expect(properties).toMatchSpecificSnapshot(path.join(testDir, 'properties.snapshot')); + }); + } + } + }); +}); diff --git a/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx b/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx index a0cd380901bb..4cbfe60f5345 100644 --- a/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx +++ b/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx @@ -2,7 +2,7 @@ import { PropDef } from '@storybook/components'; import React from 'react'; -import { Component } from '../../../blocks/shared'; +import { Component } from '../../../blocks/types'; import { extractComponentProps, DocgenInfo, DocgenPropDefaultValue } from '../../../lib/docgen'; import { enhanceTypeScriptProp } from './handleProp'; @@ -278,7 +278,7 @@ describe('enhanceTypeScriptProp', () => { { type: 'number', defaultProp: 1 }, { type: 'boolean', defaultProp: true }, { type: 'symbol', defaultProp: Symbol('hey!') }, - ].forEach(x => { + ].forEach((x) => { it(`should support ${x.type}`, () => { const component = createTestComponent(null); @@ -432,7 +432,7 @@ describe('enhanceTypeScriptProp', () => { expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); }); - ['element', 'elementType'].forEach(x => { + ['element', 'elementType'].forEach((x) => { it(`should support inlined React class component for ${x}`, () => { const component = createTestComponent(null, x); diff --git a/addons/docs/src/frameworks/react/typeScript/handleProp.ts b/addons/docs/src/frameworks/react/typeScript/handleProp.ts index 9f840806de52..a63a4f2e3724 100644 --- a/addons/docs/src/frameworks/react/typeScript/handleProp.ts +++ b/addons/docs/src/frameworks/react/typeScript/handleProp.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash'; import { PropDef } from '@storybook/components'; import { ExtractedProp } from '../../../lib/docgen'; import { createDefaultValue, createDefaultValueFromRawDefaultProp } from '../lib/defaultValues'; @@ -7,15 +6,15 @@ export function enhanceTypeScriptProp(extractedProp: ExtractedProp, rawDefaultPr const { propDef } = extractedProp; const { defaultValue } = extractedProp.docgenInfo; - if (!isNil(defaultValue) && !isNil(defaultValue.value)) { + if (defaultValue != null && defaultValue.value != null) { const newDefaultValue = createDefaultValue(defaultValue.value); - if (!isNil(newDefaultValue)) { + if (newDefaultValue != null) { propDef.defaultValue = newDefaultValue; } - } else if (!isNil(rawDefaultProp)) { + } else if (rawDefaultProp != null) { const newDefaultValue = createDefaultValueFromRawDefaultProp(rawDefaultProp, propDef); - if (!isNil(newDefaultValue)) { + if (newDefaultValue != null) { propDef.defaultValue = newDefaultValue; } } @@ -24,5 +23,5 @@ export function enhanceTypeScriptProp(extractedProp: ExtractedProp, rawDefaultPr } export function enhanceTypeScriptProps(extractedProps: ExtractedProp[]): PropDef[] { - return extractedProps.map(enhanceTypeScriptProp); + return extractedProps.map((prop) => enhanceTypeScriptProp(prop)); } diff --git a/addons/docs/src/frameworks/vue/config.tsx b/addons/docs/src/frameworks/vue/config.tsx index 680e46165797..f796827b56ba 100644 --- a/addons/docs/src/frameworks/vue/config.tsx +++ b/addons/docs/src/frameworks/vue/config.tsx @@ -1,18 +1,18 @@ -/* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; import toReact from '@egoist/vue-to-react'; import { StoryFn } from '@storybook/addons'; import { addParameters } from '@storybook/client-api'; -import { extractProps } from './extractProps'; +import { extractArgTypes } from './extractArgTypes'; import { extractComponentDescription } from '../../lib/docgen'; addParameters({ docs: { + inlineStories: true, prepareForInline: (storyFn: StoryFn) => { const Story = toReact(storyFn()); return <Story />; }, - extractProps, + extractArgTypes, extractComponentDescription, }, }); diff --git a/addons/docs/src/frameworks/vue/extractArgTypes.ts b/addons/docs/src/frameworks/vue/extractArgTypes.ts new file mode 100644 index 000000000000..8ee12409c1d2 --- /dev/null +++ b/addons/docs/src/frameworks/vue/extractArgTypes.ts @@ -0,0 +1,35 @@ +import { ArgTypes } from '@storybook/api'; +import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; +import { convert } from '../../lib/sbtypes'; +import { trimQuotes } from '../../lib/sbtypes/utils'; + +const SECTIONS = ['props', 'events', 'slots']; + +const trim = (val: any) => (val && typeof val === 'string' ? trimQuotes(val) : val); + +export const extractArgTypes: ArgTypesExtractor = (component) => { + if (!hasDocgen(component)) { + return null; + } + const results: ArgTypes = {}; + SECTIONS.forEach((section) => { + const props = extractComponentProps(component, section); + props.forEach(({ propDef, docgenInfo, jsDocTags }) => { + const { name, type, description, defaultValue, required } = propDef; + const sbType = section === 'props' ? convert(docgenInfo) : { name: 'void' }; + results[name] = { + name, + description, + type: { required, ...sbType }, + defaultValue: defaultValue && trim(defaultValue.detail || defaultValue.summary), + table: { + type, + jsDocTags, + defaultValue, + category: section, + }, + }; + }); + }); + return results; +}; diff --git a/addons/docs/src/frameworks/vue/extractProps.ts b/addons/docs/src/frameworks/vue/extractProps.ts deleted file mode 100644 index f4a26acc76cf..000000000000 --- a/addons/docs/src/frameworks/vue/extractProps.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PropDef } from '@storybook/components'; -import { PropsExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; - -const SECTIONS = ['props', 'events', 'slots']; - -export const extractProps: PropsExtractor = component => { - if (!hasDocgen(component)) { - return null; - } - const sections: Record<string, PropDef[]> = {}; - SECTIONS.forEach(section => { - sections[section] = extractComponentProps(component, section).map(x => x.propDef); - }); - return { sections }; -}; diff --git a/addons/docs/src/frameworks/vue/preset.ts b/addons/docs/src/frameworks/vue/preset.ts index 527ce55570c9..f74526599697 100644 --- a/addons/docs/src/frameworks/vue/preset.ts +++ b/addons/docs/src/frameworks/vue/preset.ts @@ -1,8 +1,14 @@ -export function webpack(webpackConfig: any = {}, options: any = {}) { +export function webpackFinal(webpackConfig: any = {}, options: any = {}) { webpackConfig.module.rules.push({ test: /\.vue$/, loader: 'vue-docgen-loader', enforce: 'post', + options: { + docgenOptions: { + alias: webpackConfig.resolve.alias, + ...options.vueDocgenOptions, + }, + }, }); return webpackConfig; } diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/custom-elements.snapshot b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/custom-elements.snapshot new file mode 100644 index 000000000000..162254124055 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/custom-elements.snapshot @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web-components component properties lit-element-demo-card 1`] = ` +Object { + "tags": Array [ + Object { + "attributes": Array [ + Object { + "default": "false", + "description": "Indicates that the back of the card is shown", + "name": "back-side", + "type": "boolean", + }, + Object { + "default": "\\"Your Message\\"", + "description": "Header message", + "name": "header", + "type": "string", + }, + Object { + "default": "[]", + "description": "Data rows", + "name": "rows", + "type": "object", + }, + ], + "cssProperties": Array [ + Object { + "description": "Header font size", + "name": "--demo-wc-card-header-font-size", + }, + Object { + "description": "Font color for front", + "name": "--demo-wc-card-front-color", + }, + Object { + "description": "Font color for back", + "name": "--demo-wc-card-back-color", + }, + ], + "description": "This is a container looking like a card with a back and front side you can switch", + "events": Array [ + Object { + "description": "Fires whenever it switches between front/back", + "name": "side-changed", + }, + ], + "name": "input", + "path": "dummy-path-to-component", + "properties": Array [ + Object { + "attribute": "back-side", + "default": "false", + "description": "Indicates that the back of the card is shown", + "name": "backSide", + "type": "boolean", + }, + Object { + "attribute": "header", + "default": "\\"Your Message\\"", + "description": "Header message", + "name": "header", + "type": "string", + }, + Object { + "attribute": "rows", + "default": "[]", + "description": "Data rows", + "name": "rows", + "type": "object", + }, + ], + "slots": Array [ + Object { + "description": "This is an unnamed slot (the default slot)", + "name": "", + }, + ], + }, + ], + "version": "experimental", +} +`; diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/input.js b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/input.js new file mode 100644 index 000000000000..b56ff4c87b0a --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/input.js @@ -0,0 +1,198 @@ +import { CustomEvent } from 'global'; +import { LitElement, html, css } from 'lit-element'; + +const demoWcCardStyle = css` + :host { + display: block; + position: relative; + width: 250px; + height: 200px; + border-radius: 10px; + transform-style: preserve-3d; + transition: all 0.8s ease; + } + + .header { + font-weight: bold; + font-size: var(--demo-wc-card-header-font-size, 16px); + text-align: center; + } + + .content { + padding: 20px 10px 0 10px; + flex-grow: 1; + } + + .footer { + display: flex; + } + + dl { + margin: 0; + text-align: left; + } + + dd { + margin-left: 15px; + } + + button { + border-radius: 15px; + width: 30px; + height: 30px; + background: #fff; + border: 1px solid #ccc; + color: #000; + font-size: 21px; + line-height: 27px; + font-weight: bold; + cursor: pointer; + margin: 5px; + } + + .note { + flex-grow: 1; + color: #666; + font-size: 16px; + font-weight: bold; + text-align: left; + padding-top: 15px; + } + + :host([back-side]) { + transform: rotateY(180deg); + } + + #front, + #back { + position: absolute; + width: 250px; + box-sizing: border-box; + box-shadow: #ccc 3px 3px 2px 1px; + padding: 10px; + display: flex; + flex-flow: column; + top: 0; + left: 0; + height: 100%; + border-radius: 10px; + backface-visibility: hidden; + overflow: hidden; + } + + #front { + background: linear-gradient(141deg, #aaa 25%, #eee 40%, #ddd 55%); + color: var(--demo-wc-card-front-color, #000); + } + + #back { + background: linear-gradient(141deg, #333 25%, #aaa 40%, #666 55%); + color: var(--demo-wc-card-back-color, #fff); + text-align: center; + transform: rotateY(180deg) translate3d(0px, 0, 1px); + } + + #back .note { + color: #fff; + } +`; + +/** + * This is a container looking like a card with a back and front side you can switch + * + * @slot - This is an unnamed slot (the default slot) + * @fires side-changed - Fires whenever it switches between front/back + * @cssprop --demo-wc-card-header-font-size - Header font size + * @cssprop --demo-wc-card-front-color - Font color for front + * @cssprop --demo-wc-card-back-color - Font color for back + */ +export class DemoWcCard extends LitElement { + static get properties() { + return { + backSide: { + type: Boolean, + reflect: true, + attribute: 'back-side', + }, + header: { type: String }, + rows: { type: Object }, + }; + } + + static get styles() { + return demoWcCardStyle; + } + + constructor() { + super(); + + /** + * Indicates that the back of the card is shown + */ + this.backSide = false; + + /** + * Header message + */ + this.header = 'Your Message'; + + /** + * Data rows + */ + this.rows = []; + } + + toggle() { + this.backSide = !this.backSide; + } + + render() { + return html` + <div id="front"> + <div class="header"> + ${this.header} + </div> + <div class="content"> + <slot></slot> + </div> + <div class="footer"> + <div class="note">A</div> + <button @click=${this.toggle}>></button> + </div> + </div> + <div id="back"> + <div class="header"> + ${this.header} + </div> + + <div class="content"> + ${this.rows.length === 0 + ? html`` + : html` + <dl> + ${this.rows.map( + (row) => html` + <dt>${row.header}</dt> + <dd>${row.value}</dd> + ` + )} + </dl> + `} + </div> + <div class="footer"> + <div class="note">B</div> + <button @click=${this.toggle}>></button> + </div> + </div> + `; + } + + updated(changedProperties) { + if (changedProperties.has('backSide') && changedProperties.get('backSide') !== undefined) { + this.dispatchEvent(new CustomEvent('side-changed')); + } + } +} + +// eslint-disable-next-line no-undef +customElements.define('input', DemoWcCard); diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot new file mode 100644 index 000000000000..39817364930f --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-element-demo-card/properties.snapshot @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web-components component properties lit-element-demo-card 1`] = ` +Object { + "": Object { + "description": "This is an unnamed slot (the default slot)", + "name": "", + "required": false, + "table": Object { + "category": "slots", + "defaultValue": Object { + "summary": undefined, + }, + "type": Object { + "summary": undefined, + }, + }, + "type": Object { + "name": "void", + }, + }, + "--demo-wc-card-back-color": Object { + "description": "Font color for back", + "name": "--demo-wc-card-back-color", + "required": false, + "table": Object { + "category": "css", + "defaultValue": Object { + "summary": undefined, + }, + "type": Object { + "summary": undefined, + }, + }, + "type": Object { + "name": "void", + }, + }, + "--demo-wc-card-front-color": Object { + "description": "Font color for front", + "name": "--demo-wc-card-front-color", + "required": false, + "table": Object { + "category": "css", + "defaultValue": Object { + "summary": undefined, + }, + "type": Object { + "summary": undefined, + }, + }, + "type": Object { + "name": "void", + }, + }, + "--demo-wc-card-header-font-size": Object { + "description": "Header font size", + "name": "--demo-wc-card-header-font-size", + "required": false, + "table": Object { + "category": "css", + "defaultValue": Object { + "summary": undefined, + }, + "type": Object { + "summary": undefined, + }, + }, + "type": Object { + "name": "void", + }, + }, + "back-side": Object { + "description": "Indicates that the back of the card is shown", + "name": "back-side", + "required": false, + "table": Object { + "category": "attributes", + "defaultValue": Object { + "summary": "false", + }, + "type": Object { + "summary": "boolean", + }, + }, + "type": Object { + "name": "void", + }, + }, + "backSide": Object { + "description": "Indicates that the back of the card is shown", + "name": "backSide", + "required": false, + "table": Object { + "category": "properties", + "defaultValue": Object { + "summary": "false", + }, + "type": Object { + "summary": "boolean", + }, + }, + "type": Object { + "name": "boolean", + }, + }, + "header": Object { + "description": "Header message", + "name": "header", + "required": false, + "table": Object { + "category": "properties", + "defaultValue": Object { + "summary": "\\"Your Message\\"", + }, + "type": Object { + "summary": "string", + }, + }, + "type": Object { + "name": "string", + }, + }, + "rows": Object { + "description": "Data rows", + "name": "rows", + "required": false, + "table": Object { + "category": "properties", + "defaultValue": Object { + "summary": "[]", + }, + "type": Object { + "summary": "object", + }, + }, + "type": Object { + "name": "object", + }, + }, + "side-changed": Object { + "description": "Fires whenever it switches between front/back", + "name": "side-changed", + "required": false, + "table": Object { + "category": "events", + "defaultValue": Object { + "summary": undefined, + }, + "type": Object { + "summary": undefined, + }, + }, + "type": Object { + "name": "void", + }, + }, +} +`; diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/custom-elements.snapshot b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/custom-elements.snapshot new file mode 100644 index 000000000000..c1416386b1b1 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/custom-elements.snapshot @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web-components component properties lit-html-welcome 1`] = ` +Object { + "tags": Array [ + Object { + "name": "input", + "path": "dummy-path-to-component", + }, + ], + "version": "experimental", +} +`; diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/input.js b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/input.js new file mode 100644 index 000000000000..30e6edec7cf2 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/input.js @@ -0,0 +1,61 @@ +import { html } from 'lit-html'; + +export const Welcome = () => html` + <div class="main"> + <h1>Welcome to Storybook for Web Components</h1> + <p>This is a UI component dev environment for your plain HTML snippets.</p> + <p> + We've added some basic stories inside the <code class="code">stories</code> directory. + <br /> + A story is a single state of one or more UI components. You can have as many stories as you + want. + <br /> + (Basically a story is like a visual test case.) + </p> + <p> + See these sample + <a class="link" href="#" data-sb-kind="Demo Card" data-sb-story="Front">stories</a> + </p> + <p> + Just like that, you can add your own snippets as stories. + <br /> + You can also edit those snippets and see changes right away. + <br /> + </p> + <p> + Usually we create stories with smaller UI components in the app.<br /> + Have a look at the + <a class="link" href="https://storybook.js.org/basics/writing-stories" target="_blank"> + Writing Stories + </a> + section in our documentation. + </p> + </div> + + <style> + .main { + padding: 15px; + line-height: 1.4; + font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + background-color: #ffffff; + } + + .logo { + width: 256px; + margin: 15px; + } + + .code { + font-size: 15px; + font-weight: 600; + padding: 2px 5px; + border: 1px solid #eae9e9; + border-radius: 4px; + background-color: #f3f2f2; + color: #3a3a3a; + } + </style> +`; + +// eslint-disable-next-line no-undef +customElements.define('input', Welcome); diff --git a/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/properties.snapshot b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/properties.snapshot new file mode 100644 index 000000000000..a0d337eb0697 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/__testfixtures__/lit-html-welcome/properties.snapshot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web-components component properties lit-html-welcome 1`] = `Object {}`; diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 76083ba119f6..18eec305f12e 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -1,66 +1,16 @@ /* global window */ /* eslint-disable import/no-extraneous-dependencies */ import { addParameters } from '@storybook/client-api'; -import { getCustomElements, isValidComponent, isValidMetaData } from '@storybook/web-components'; import React from 'react'; import { render } from 'lit-html'; - -function mapData(data) { - return data.map(item => ({ - name: item.name, - type: { summary: item.type }, - required: '', - description: item.description, - defaultValue: { summary: item.defaultValue }, - })); -} - -function isEmpty(obj) { - return Object.entries(obj).length === 0 && obj.constructor === Object; -} +import { extractArgTypes, extractComponentDescription } from './custom-elements'; addParameters({ docs: { - extractProps: tagName => { - const customElements = getCustomElements(); - if (isValidComponent(tagName) && isValidMetaData(customElements)) { - const metaData = customElements.tags.find( - tag => tag.name.toUpperCase() === tagName.toUpperCase() - ); - const sections = {}; - if (metaData.attributes) { - sections.attributes = mapData(metaData.attributes); - } - if (metaData.properties) { - sections.props = mapData(metaData.properties); - } - if (metaData.events) { - sections.events = mapData(metaData.events); - } - if (metaData.slots) { - sections.slots = mapData(metaData.slots); - } - if (metaData.cssProperties) { - sections.css = mapData(metaData.cssProperties); - } - return isEmpty(sections) ? false : { sections }; - } - return false; - }, - extractComponentDescription: tagName => { - const customElements = getCustomElements(); - if (isValidComponent(tagName) && isValidMetaData(customElements)) { - const metaData = customElements.tags.find( - tag => tag.name.toUpperCase() === tagName.toUpperCase() - ); - if (metaData && metaData.description) { - return metaData.description; - } - } - return false; - }, + extractArgTypes, + extractComponentDescription, inlineStories: true, - prepareForInline: storyFn => { + prepareForInline: (storyFn) => { class Story extends React.Component { constructor(props) { super(props); diff --git a/addons/docs/src/frameworks/web-components/custom-elements.ts b/addons/docs/src/frameworks/web-components/custom-elements.ts new file mode 100644 index 000000000000..e46edcb7fdf6 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/custom-elements.ts @@ -0,0 +1,91 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { getCustomElements, isValidComponent, isValidMetaData } from '@storybook/web-components'; +import { ArgTypes } from '@storybook/api'; + +interface TagItem { + name: string; + type: string; + description: string; + default?: any; + defaultValue?: any; +} + +interface Tag { + name: string; + description: string; + attributes?: TagItem[]; + properties?: TagItem[]; + events?: TagItem[]; + slots?: TagItem[]; + cssProperties?: TagItem[]; +} + +interface CustomElements { + tags: Tag[]; +} + +interface Sections { + attributes?: any; + properties?: any; + events?: any; + slots?: any; + css?: any; +} + +function mapData(data: TagItem[], category: string) { + return ( + data && + data.reduce((acc, item) => { + const type = category === 'properties' ? { name: item.type } : { name: 'void' }; + acc[item.name] = { + name: item.name, + required: false, + description: item.description, + type, + table: { + category, + type: { summary: item.type }, + defaultValue: { summary: item.default !== undefined ? item.default : item.defaultValue }, + }, + }; + return acc; + }, {} as ArgTypes) + ); +} + +function isEmpty(obj: object) { + return Object.entries(obj).length === 0 && obj.constructor === Object; +} + +export const extractArgTypesFromElements = (tagName: string, customElements: CustomElements) => { + if (!isValidComponent(tagName) || !isValidMetaData(customElements)) { + return null; + } + const metaData = customElements.tags.find( + (tag) => tag.name.toUpperCase() === tagName.toUpperCase() + ); + const argTypes = { + ...mapData(metaData.attributes, 'attributes'), + ...mapData(metaData.properties, 'properties'), + ...mapData(metaData.events, 'events'), + ...mapData(metaData.slots, 'slots'), + ...mapData(metaData.cssProperties, 'css'), + }; + return argTypes; +}; + +export const extractArgTypes = (tagName: string) => { + const customElements: CustomElements = getCustomElements(); + return extractArgTypesFromElements(tagName, customElements); +}; + +export const extractComponentDescription = (tagName: string) => { + const customElements: CustomElements = getCustomElements(); + if (!isValidComponent(tagName) || !isValidMetaData(customElements)) { + return null; + } + const metaData = customElements.tags.find( + (tag) => tag.name.toUpperCase() === tagName.toUpperCase() + ); + return metaData && metaData.description; +}; diff --git a/addons/docs/src/frameworks/web-components/web-components-properties.test.ts b/addons/docs/src/frameworks/web-components/web-components-properties.test.ts new file mode 100644 index 000000000000..9f4581fdcb57 --- /dev/null +++ b/addons/docs/src/frameworks/web-components/web-components-properties.test.ts @@ -0,0 +1,61 @@ +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs'; +import tmp from 'tmp'; +import { sync as spawnSync } from 'cross-spawn'; + +// File hierarchy: +// __testfixtures__ / some-test-case / input.* +const inputRegExp = /^input\..*$/; + +const runWebComponentsAnalyzer = (inputPath: string) => { + const { name: tmpDir, removeCallback } = tmp.dirSync(); + const customElementsFile = `${tmpDir}/custom-elements.json`; + spawnSync('wca', ['analyze', inputPath, '--outFile', customElementsFile], { + stdio: 'inherit', + }); + const output = fs.readFileSync(customElementsFile, 'utf8'); + try { + removeCallback(); + } catch (e) { + // + } + return output; +}; + +describe('web-components component properties', () => { + // we need to mock lit-html and dynamically require custom-elements + // because lit-html is distributed as ESM not CJS + // https://github.com/Polymer/lit-html/issues/516 + jest.mock('lit-html', () => {}); + // eslint-disable-next-line global-require + const { extractArgTypesFromElements } = require('./custom-elements'); + + const fixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(fixturesDir, { withFileTypes: true }).forEach((testEntry) => { + if (testEntry.isDirectory()) { + const testDir = path.join(fixturesDir, testEntry.name); + const testFile = fs.readdirSync(testDir).find((fileName) => inputRegExp.test(fileName)); + if (testFile) { + it(testEntry.name, () => { + const inputPath = path.join(testDir, testFile); + + // snapshot the output of wca + const customElementsJson = runWebComponentsAnalyzer(inputPath); + const customElements = JSON.parse(customElementsJson); + customElements.tags.forEach((tag: any) => { + // eslint-disable-next-line no-param-reassign + tag.path = 'dummy-path-to-component'; + }); + expect(customElements).toMatchSpecificSnapshot( + path.join(testDir, 'custom-elements.snapshot') + ); + + // snapshot the properties + const properties = extractArgTypesFromElements('input', customElements); + expect(properties).toMatchSpecificSnapshot(path.join(testDir, 'properties.snapshot')); + }); + } + } + }); +}); diff --git a/addons/docs/src/lib/docgen/createPropDef.ts b/addons/docs/src/lib/docgen/createPropDef.ts index 9d844cf00fb5..2fb88bd66d78 100644 --- a/addons/docs/src/lib/docgen/createPropDef.ts +++ b/addons/docs/src/lib/docgen/createPropDef.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash'; import { PropDef, PropDefaultValue } from '@storybook/components'; import { TypeSystem, DocgenInfo, DocgenType, DocgenPropDefaultValue } from './types'; import { JsDocParsingResult } from '../jsdocParser'; @@ -6,6 +5,7 @@ import { createSummaryValue } from '../utils'; import { createFlowPropDef } from './flow/createPropDef'; import { isDefaultValueBlacklisted } from './utils/defaultValue'; import { createTsPropDef } from './typeScript/createPropDef'; +import { convert } from '../sbtypes'; export type PropDefFactory = ( propName: string, @@ -15,11 +15,11 @@ export type PropDefFactory = ( function createType(type: DocgenType) { // A type could be null if a defaultProp has been provided without a type definition. - return !isNil(type) ? createSummaryValue(type.name) : null; + return type != null ? createSummaryValue(type.name) : null; } function createDefaultValue(defaultValue: DocgenPropDefaultValue): PropDefaultValue { - if (!isNil(defaultValue)) { + if (defaultValue != null) { const { value } = defaultValue; if (!isDefaultValueBlacklisted(value)) { @@ -46,20 +46,23 @@ function applyJsDocResult(propDef: PropDef, jsDocParsingResult: JsDocParsingResu if (jsDocParsingResult.includesJsDoc) { const { description, extractedTags } = jsDocParsingResult; - if (!isNil(description)) { + if (description != null) { // eslint-disable-next-line no-param-reassign propDef.description = jsDocParsingResult.description; } - const hasParams = !isNil(extractedTags.params); - const hasReturns = !isNil(extractedTags.returns) && !isNil(extractedTags.returns.type); + const hasParams = extractedTags.params != null; + const hasReturns = extractedTags.returns != null && extractedTags.returns.type != null; if (hasParams || hasReturns) { // eslint-disable-next-line no-param-reassign propDef.jsDocTags = { params: hasParams && - extractedTags.params.map(x => ({ name: x.getPrettyName(), description: x.description })), + extractedTags.params.map((x) => ({ + name: x.getPrettyName(), + description: x.description, + })), returns: hasReturns && { description: extractedTags.returns.description }, }; } @@ -70,18 +73,21 @@ function applyJsDocResult(propDef: PropDef, jsDocParsingResult: JsDocParsingResu export const javaScriptFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { const propDef = createBasicPropDef(propName, docgenInfo.type, docgenInfo); + propDef.sbType = convert(docgenInfo); return applyJsDocResult(propDef, jsDocParsingResult); }; export const tsFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { const propDef = createTsPropDef(propName, docgenInfo); + propDef.sbType = convert(docgenInfo); return applyJsDocResult(propDef, jsDocParsingResult); }; export const flowFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { const propDef = createFlowPropDef(propName, docgenInfo); + propDef.sbType = convert(docgenInfo); return applyJsDocResult(propDef, jsDocParsingResult); }; diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.test.ts b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts index d2b7c57aa0a5..921d329893f8 100644 --- a/addons/docs/src/lib/docgen/extractDocgenProps.test.ts +++ b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts @@ -1,6 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import { Component } from '../../blocks/shared'; +import { Component } from '../../blocks/types'; import { extractComponentProps } from './extractDocgenProps'; const DOCGEN_SECTION = 'props'; @@ -53,7 +53,7 @@ function createComponent(docgenInfo: Record<string, any>): Component { return component; } -TypeSystems.forEach(x => { +TypeSystems.forEach((x) => { describe(`${x.name}`, () => { it('should map defaults docgen info properly', () => { const component = createComponent({ diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.ts b/addons/docs/src/lib/docgen/extractDocgenProps.ts index 939b522d6f09..e66468b02b90 100644 --- a/addons/docs/src/lib/docgen/extractDocgenProps.ts +++ b/addons/docs/src/lib/docgen/extractDocgenProps.ts @@ -1,6 +1,5 @@ -import { isNil } from 'lodash'; import { PropDef } from '@storybook/components'; -import { Component } from '../../blocks/shared'; +import { Component } from '../../blocks/types'; import { ExtractedJsDoc, parseJsDoc } from '../jsdocParser'; import { DocgenInfo, TypeSystem } from './types'; import { getDocgenSection, isValidDocgenSection, getDocgenDescription } from './utils'; @@ -16,15 +15,15 @@ export interface ExtractedProp { export type ExtractProps = (component: Component, section: string) => ExtractedProp[]; const getTypeSystem = (docgenInfo: DocgenInfo): TypeSystem => { - if (!isNil(docgenInfo.type)) { + if (docgenInfo.type != null) { return TypeSystem.JAVASCRIPT; } - if (!isNil(docgenInfo.flowType)) { + if (docgenInfo.flowType != null) { return TypeSystem.FLOW; } - if (!isNil(docgenInfo.tsType)) { + if (docgenInfo.tsType != null) { return TypeSystem.TYPESCRIPT; } @@ -46,10 +45,10 @@ export const extractComponentSectionObject = (docgenSection: any) => { const createPropDef = getPropDefFactory(typeSystem); return docgenPropsKeys - .map(propName => { + .map((propName) => { const docgenInfo = docgenSection[propName]; - return !isNil(docgenInfo) + return docgenInfo != null ? extractProp(propName, docgenInfo, typeSystem, createPropDef) : null; }) @@ -93,5 +92,5 @@ function extractProp( } export function extractComponentDescription(component?: Component): string { - return !isNil(component) && getDocgenDescription(component); + return component != null && getDocgenDescription(component); } diff --git a/addons/docs/src/lib/docgen/flow/createDefaultValue.ts b/addons/docs/src/lib/docgen/flow/createDefaultValue.ts index 0bec4162d388..a9be5f0fe9d4 100644 --- a/addons/docs/src/lib/docgen/flow/createDefaultValue.ts +++ b/addons/docs/src/lib/docgen/flow/createDefaultValue.ts @@ -1,5 +1,4 @@ import { PropDefaultValue } from '@storybook/components'; -import { isNil } from 'lodash'; import { DocgenPropDefaultValue, DocgenPropType } from '../types'; import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../utils'; import { isDefaultValueBlacklisted } from '../utils/defaultValue'; @@ -8,7 +7,7 @@ export function createDefaultValue( defaultValue: DocgenPropDefaultValue, type: DocgenPropType ): PropDefaultValue { - if (!isNil(defaultValue)) { + if (defaultValue != null) { const { value } = defaultValue; if (!isDefaultValueBlacklisted(value)) { diff --git a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts index 0b6a4e7156df..be904ce7af72 100644 --- a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts +++ b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts @@ -13,7 +13,7 @@ function createDocgenInfo({ flowType, ...others }: Partial<DocgenInfo>): DocgenI describe('type', () => { ['string', 'number', 'boolean', 'any', 'void', 'Object', 'String', 'MyClass', 'literal'].forEach( - x => { + (x) => { it(`should support ${x}`, () => { const docgenInfo = createDocgenInfo({ flowType: { name: x }, @@ -27,7 +27,7 @@ describe('type', () => { } ); - ['Array', 'Class', 'MyClass'].forEach(x => { + ['Array', 'Class', 'MyClass'].forEach((x) => { it(`should support untyped ${x}`, () => { const docgenInfo = createDocgenInfo({ flowType: { name: x }, diff --git a/addons/docs/src/lib/docgen/flow/createType.ts b/addons/docs/src/lib/docgen/flow/createType.ts index 24530648f4e7..5cf193fb53bf 100644 --- a/addons/docs/src/lib/docgen/flow/createType.ts +++ b/addons/docs/src/lib/docgen/flow/createType.ts @@ -1,5 +1,4 @@ import { PropType } from '@storybook/components'; -import { isNil } from 'lodash'; import { DocgenFlowType } from '../types'; import { createSummaryValue, isTooLongForTypeSummary } from '../../utils'; @@ -13,19 +12,19 @@ interface DocgenFlowUnionType extends DocgenFlowType { } function generateUnion({ name, raw, elements }: DocgenFlowUnionType): PropType { - if (!isNil(raw)) { + if (raw != null) { return createSummaryValue(raw); } - if (!isNil(elements)) { - return createSummaryValue(elements.map(x => x.value).join(' | ')); + if (elements != null) { + return createSummaryValue(elements.map((x) => x.value).join(' | ')); } return createSummaryValue(name); } function generateFuncSignature({ type, raw }: DocgenFlowType): PropType { - if (!isNil(raw)) { + if (raw != null) { return createSummaryValue(raw); } @@ -33,7 +32,7 @@ function generateFuncSignature({ type, raw }: DocgenFlowType): PropType { } function generateObjectSignature({ type, raw }: DocgenFlowType): PropType { - if (!isNil(raw)) { + if (raw != null) { return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(type, raw); } @@ -47,7 +46,7 @@ function generateSignature(flowType: DocgenFlowType): PropType { } function generateDefault({ name, raw }: DocgenFlowType): PropType { - if (!isNil(raw)) { + if (raw != null) { return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(name, raw); } @@ -56,7 +55,7 @@ function generateDefault({ name, raw }: DocgenFlowType): PropType { export function createType(type: DocgenFlowType): PropType { // A type could be null if a defaultProp has been provided without a type definition. - if (isNil(type)) { + if (type == null) { return null; } diff --git a/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts b/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts index f63067c49b3a..14bfcf67b687 100644 --- a/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts +++ b/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts @@ -1,11 +1,10 @@ import { PropDefaultValue } from '@storybook/components'; -import { isNil } from 'lodash'; import { DocgenInfo } from '../types'; import { createSummaryValue } from '../../utils'; import { isDefaultValueBlacklisted } from '../utils/defaultValue'; export function createDefaultValue({ defaultValue }: DocgenInfo): PropDefaultValue { - if (!isNil(defaultValue)) { + if (defaultValue != null) { const { value } = defaultValue; if (!isDefaultValueBlacklisted(value)) { diff --git a/addons/docs/src/lib/docgen/typeScript/createType.ts b/addons/docs/src/lib/docgen/typeScript/createType.ts index a1e1edbafef9..c2022c90d4f3 100644 --- a/addons/docs/src/lib/docgen/typeScript/createType.ts +++ b/addons/docs/src/lib/docgen/typeScript/createType.ts @@ -1,11 +1,10 @@ import { PropType } from '@storybook/components'; -import { isNil } from 'lodash'; import { DocgenInfo } from '../types'; import { createSummaryValue } from '../../utils'; export function createType({ tsType, required }: DocgenInfo): PropType { // A type could be null if a defaultProp has been provided without a type definition. - if (isNil(tsType)) { + if (tsType == null) { return null; } diff --git a/addons/docs/src/lib/docgen/types.ts b/addons/docs/src/lib/docgen/types.ts index b59af2b3ec99..f95a54e84641 100644 --- a/addons/docs/src/lib/docgen/types.ts +++ b/addons/docs/src/lib/docgen/types.ts @@ -1,8 +1,11 @@ import { PropsTableProps } from '@storybook/components'; -import { Component } from '../../blocks/shared'; +import { ArgTypes } from '@storybook/api'; +import { Component } from '../../blocks/types'; export type PropsExtractor = (component: Component) => PropsTableProps | null; +export type ArgTypesExtractor = (component: Component) => ArgTypes | null; + export interface DocgenType { name: string; description?: string; diff --git a/addons/docs/src/lib/docgen/utils/defaultValue.ts b/addons/docs/src/lib/docgen/utils/defaultValue.ts index 0757f9eef6e6..e85efb4546a2 100644 --- a/addons/docs/src/lib/docgen/utils/defaultValue.ts +++ b/addons/docs/src/lib/docgen/utils/defaultValue.ts @@ -1,5 +1,5 @@ const BLACKLIST = ['null', 'undefined']; export function isDefaultValueBlacklisted(value: string): boolean { - return BLACKLIST.some(x => x === value); + return BLACKLIST.some((x) => x === value); } diff --git a/addons/docs/src/lib/docgen/utils/docgenInfo.ts b/addons/docs/src/lib/docgen/utils/docgenInfo.ts index 623047c7e380..da7f21f7a06d 100644 --- a/addons/docs/src/lib/docgen/utils/docgenInfo.ts +++ b/addons/docs/src/lib/docgen/utils/docgenInfo.ts @@ -1,7 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import { isNil } from 'lodash'; -import { Component } from '../../../blocks/shared'; +import { Component } from '../../../blocks/types'; import { str } from './string'; export function hasDocgen(component: Component): boolean { @@ -9,7 +8,7 @@ export function hasDocgen(component: Component): boolean { } export function isValidDocgenSection(docgenSection: any) { - return !isNil(docgenSection) && Object.keys(docgenSection).length > 0; + return docgenSection != null && Object.keys(docgenSection).length > 0; } export function getDocgenSection(component: Component, section: string): any { diff --git a/addons/docs/src/lib/jsdocParser.test.ts b/addons/docs/src/lib/jsdocParser.test.ts index eb2b539d3bb3..e3ec359b9b94 100644 --- a/addons/docs/src/lib/jsdocParser.test.ts +++ b/addons/docs/src/lib/jsdocParser.test.ts @@ -121,7 +121,7 @@ describe('parseJsDoc', () => { expect(extractedTags.params[1].name).toBe('anotherEvent'); }); - ['@arg', '@argument'].forEach(x => { + ['@arg', '@argument'].forEach((x) => { it(`should support ${x} alias`, () => { const { extractedTags } = parseJsDoc(`${x} {SyntheticEvent} event - React event`); diff --git a/addons/docs/src/lib/jsdocParser.ts b/addons/docs/src/lib/jsdocParser.ts index d489baf76d23..1b9f971856e9 100644 --- a/addons/docs/src/lib/jsdocParser.ts +++ b/addons/docs/src/lib/jsdocParser.ts @@ -1,5 +1,4 @@ import doctrine, { Annotation } from 'doctrine'; -import { isNil } from 'lodash'; export interface ExtractedJsDocParam { name: string; @@ -35,7 +34,7 @@ export interface JsDocParsingResult { export type ParseJsDoc = (value?: string, options?: JsDocParsingOptions) => JsDocParsingResult; function containsJsDoc(value?: string): boolean { - return !isNil(value) && value.includes('@'); + return value != null && value.includes('@'); } function parse(content: string, tags: string[]): Annotation { @@ -112,8 +111,8 @@ function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDoc { case 'arg': case 'argument': { const paramTag = extractParam(tag); - if (!isNil(paramTag)) { - if (isNil(extractedTags.params)) { + if (paramTag != null) { + if (extractedTags.params == null) { extractedTags.params = []; } extractedTags.params.push(paramTag); @@ -122,7 +121,7 @@ function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDoc { } case 'returns': { const returnsTag = extractReturns(tag); - if (!isNil(returnsTag)) { + if (returnsTag != null) { extractedTags.returns = returnsTag; } break; @@ -140,7 +139,7 @@ function extractParam(tag: doctrine.Tag): ExtractedJsDocParam { const paramName = tag.name; // When the @param doesn't have a name but have a type and a description, "null-null" is returned. - if (!isNil(paramName) && paramName !== 'null-null') { + if (paramName != null && paramName !== 'null-null') { return { name: tag.name, type: tag.type, @@ -156,7 +155,7 @@ function extractParam(tag: doctrine.Tag): ExtractedJsDocParam { return tag.name; }, getTypeName: () => { - return !isNil(tag.type) ? extractTypeName(tag.type) : null; + return tag.type != null ? extractTypeName(tag.type) : null; }, }; } @@ -165,7 +164,7 @@ function extractParam(tag: doctrine.Tag): ExtractedJsDocParam { } function extractReturns(tag: doctrine.Tag): ExtractedJsDocReturns { - if (!isNil(tag.type)) { + if (tag.type != null) { return { type: tag.type, description: tag.description, @@ -185,7 +184,7 @@ function extractTypeName(type: doctrine.Type): string { if (type.type === 'RecordType') { const recordFields = type.fields.map((field: doctrine.type.FieldType) => { - if (!isNil(field.value)) { + if (field.value != null) { const valueTypeName = extractTypeName(field.value); return `${field.key}: ${valueTypeName}`; @@ -209,7 +208,7 @@ function extractTypeName(type: doctrine.Type): string { } if (type.type === 'TypeApplication') { - if (!isNil(type.expression)) { + if (type.expression != null) { if ((type.expression as doctrine.type.NameExpression).name === 'Array') { const arrayType = extractTypeName(type.applications[0]); diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/arrays.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/arrays.js new file mode 100644 index 000000000000..0f1ebcdae092 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/arrays.js @@ -0,0 +1,13 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + optionalArray: PropTypes.array, + arrayOfStrings: PropTypes.arrayOf(PropTypes.string), + arrayOfShape: PropTypes.arrayOf( + PropTypes.shape({ + active: PropTypes.bool, + }) + ), +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/enums.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/enums.js new file mode 100644 index 000000000000..60f51359b6f5 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/enums.js @@ -0,0 +1,8 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + oneOfNumber: PropTypes.oneOf([1, 2, 3]), + oneOfString: PropTypes.oneOf(['static', 'timed']), +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/misc.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/misc.js new file mode 100644 index 000000000000..e662474d1ab1 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/misc.js @@ -0,0 +1,15 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + // An object that could be one of many types + optionalUnion: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Object), + ]), + optionalMessage: PropTypes.instanceOf(Object), + // A value of any data type + requiredAny: PropTypes.any.isRequired, +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/objects.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/objects.js new file mode 100644 index 000000000000..09036cf88088 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/objects.js @@ -0,0 +1,16 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + optionalObject: PropTypes.object, + optionalObjectOf: PropTypes.objectOf(PropTypes.number), + optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number, + }), + optionalObjectWithStrictShape: PropTypes.exact({ + name: PropTypes.string, + quantity: PropTypes.number, + }), +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/react.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/react.js new file mode 100644 index 000000000000..fd54e9307663 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/react.js @@ -0,0 +1,13 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + // Anything that can be rendered: numbers, strings, elements or an array + // (or fragment) containing these types. + optionalNode: PropTypes.node, + // A React element. + optionalElement: PropTypes.element, + // A React element type (ie. MyComponent). + optionalElementType: PropTypes.elementType, +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/scalars.js b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/scalars.js new file mode 100644 index 000000000000..7e74151596b0 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/scalars.js @@ -0,0 +1,11 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const Component = (props) => <>JSON.stringify(props)</>; +Component.propTypes = { + optionalBool: PropTypes.bool, + optionalFunc: PropTypes.func, + optionalNumber: PropTypes.number, + optionalString: PropTypes.string, + optionalSymbol: PropTypes.symbol, +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/aliases.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/aliases.tsx new file mode 100644 index 000000000000..a5ec67cd4724 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/aliases.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; + +type StringAlias = string; +type NumberAlias = number; +type AliasesIntersection = StringAlias & NumberAlias; +type AliasesUnion = StringAlias | NumberAlias; +interface GenericAlias<T> { + value: T; +} +interface Props { + typeAlias: StringAlias; + aliasesIntersection: AliasesIntersection; + aliasesUnion: AliasesUnion; + genericAlias: GenericAlias<string>; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/arrays.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/arrays.tsx new file mode 100644 index 000000000000..74c8a4d56a07 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/arrays.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface Point { + x: number; + y: number; +} +interface Props { + arrayOfPoints: Point[]; + arrayOfInlineObjects: { w: number; h: number }[]; + arrayOfPrimitive: string[]; + arrayOfComplexObject: ItemInterface[]; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/enums.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/enums.tsx new file mode 100644 index 000000000000..9cf04170aab9 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/enums.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; + +enum DefaultEnum { + TopLeft, + TopRight, + TopCenter, +} +enum NumericEnum { + TopLeft = 0, + TopRight, + TopCenter, +} +enum StringEnum { + TopLeft = 'top-left', + TopRight = 'top-right', + TopCenter = 'top-center', +} +interface Props { + defaultEnum: DefaultEnum; + numericEnum: NumericEnum; + stringEnum: StringEnum; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/functions.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/functions.tsx new file mode 100644 index 000000000000..cbe35a3ac0fc --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/functions.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface Props { + onClick?: () => void; + voidFunc: () => void; + funcWithArgsAndReturns: (a: string, b: string) => string; + funcWithUnionArg: (a: string | number) => string; + funcWithMultipleUnionReturns: () => string | ItemInterface; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/interfaces.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/interfaces.tsx new file mode 100644 index 000000000000..3f931b09c087 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/interfaces.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface GenericInterface<T> { + value: T; +} +interface Props { + interface: ItemInterface; + genericInterface: GenericInterface<string>; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/intersections.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/intersections.tsx new file mode 100644 index 000000000000..a075158cd580 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/intersections.tsx @@ -0,0 +1,15 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface PersonInterface { + name: string; +} +type InterfaceIntersection = ItemInterface & PersonInterface; +interface Props { + intersectionType: InterfaceIntersection; + intersectionWithInlineType: ItemInterface & { inlineValue: string }; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/records.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/records.tsx new file mode 100644 index 000000000000..b8e541f35aba --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/records.tsx @@ -0,0 +1,11 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface Props { + recordOfPrimitive: Record<string, number>; + recordOfComplexObject: Record<string, ItemInterface>; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/scalars.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/scalars.tsx new file mode 100644 index 000000000000..44f533a01b49 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/scalars.tsx @@ -0,0 +1,11 @@ +import React, { FC } from 'react'; + +interface Props { + any: any; + string: string; + bool: boolean; + number: number; + symbol: symbol; + readonly readonlyPrimitive: string; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/tuples.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/tuples.tsx new file mode 100644 index 000000000000..490fbd61aa66 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/tuples.tsx @@ -0,0 +1,11 @@ +import React, { FC } from 'react'; + +interface ItemInterface { + text: string; + value: string; +} +interface Props { + tupleOfPrimitive: [string, number]; + tupleWithComplexType: [string, ItemInterface]; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/unions.tsx b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/unions.tsx new file mode 100644 index 000000000000..eb8bbfc406ff --- /dev/null +++ b/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/unions.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; + +type Kind = 'default' | 'action'; +enum DefaultEnum { + TopLeft, + TopRight, + TopCenter, +} +enum NumericEnum { + TopLeft = 0, + TopRight, + TopCenter, +} +type EnumUnion = DefaultEnum | NumericEnum; +interface Props { + kind?: Kind; + inlinedNumericLiteralUnion: 0 | 1; + enumUnion: EnumUnion; +} +export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; diff --git a/addons/docs/src/lib/sbtypes/convert.test.ts b/addons/docs/src/lib/sbtypes/convert.test.ts new file mode 100644 index 000000000000..337586d82f4f --- /dev/null +++ b/addons/docs/src/lib/sbtypes/convert.test.ts @@ -0,0 +1,806 @@ +import 'jest-specific-snapshot'; +import mapValues from 'lodash/mapValues'; +import { transformSync } from '@babel/core'; +import requireFromString from 'require-from-string'; +import fs from 'fs'; + +import { convert } from './convert'; +import { normalizeNewlines } from '../utils'; + +expect.addSnapshotSerializer({ + print: (val: any) => JSON.stringify(val, null, 2), + test: (val) => typeof val !== 'string', +}); + +describe('storybook type system', () => { + describe('TypeScript', () => { + it('scalars', () => { + const input = readFixture('typescript/functions.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface Props { + onClick?: () => void; + voidFunc: () => void; + funcWithArgsAndReturns: (a: string, b: string) => string; + funcWithUnionArg: (a: string | number) => string; + funcWithMultipleUnionReturns: () => string | ItemInterface; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "onClick": { + "raw": "() => void", + "name": "function" + }, + "voidFunc": { + "raw": "() => void", + "name": "function" + }, + "funcWithArgsAndReturns": { + "raw": "(a: string, b: string) => string", + "name": "function" + }, + "funcWithUnionArg": { + "raw": "(a: string | number) => string", + "name": "function" + }, + "funcWithMultipleUnionReturns": { + "raw": "() => string | ItemInterface", + "name": "function" + } + } + `); + }); + it('functions', () => { + const input = readFixture('typescript/functions.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface Props { + onClick?: () => void; + voidFunc: () => void; + funcWithArgsAndReturns: (a: string, b: string) => string; + funcWithUnionArg: (a: string | number) => string; + funcWithMultipleUnionReturns: () => string | ItemInterface; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "onClick": { + "raw": "() => void", + "name": "function" + }, + "voidFunc": { + "raw": "() => void", + "name": "function" + }, + "funcWithArgsAndReturns": { + "raw": "(a: string, b: string) => string", + "name": "function" + }, + "funcWithUnionArg": { + "raw": "(a: string | number) => string", + "name": "function" + }, + "funcWithMultipleUnionReturns": { + "raw": "() => string | ItemInterface", + "name": "function" + } + } + `); + }); + it('enums', () => { + const input = readFixture('typescript/enums.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + enum DefaultEnum { + TopLeft, + TopRight, + TopCenter, + } + enum NumericEnum { + TopLeft = 0, + TopRight, + TopCenter, + } + enum StringEnum { + TopLeft = 'top-left', + TopRight = 'top-right', + TopCenter = 'top-center', + } + interface Props { + defaultEnum: DefaultEnum; + numericEnum: NumericEnum; + stringEnum: StringEnum; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "defaultEnum": { + "name": "other", + "value": "DefaultEnum" + }, + "numericEnum": { + "name": "other", + "value": "NumericEnum" + }, + "stringEnum": { + "name": "other", + "value": "StringEnum" + } + } + `); + }); + it('unions', () => { + const input = readFixture('typescript/unions.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + type Kind = 'default' | 'action'; + enum DefaultEnum { + TopLeft, + TopRight, + TopCenter, + } + enum NumericEnum { + TopLeft = 0, + TopRight, + TopCenter, + } + type EnumUnion = DefaultEnum | NumericEnum; + interface Props { + kind?: Kind; + inlinedNumericLiteralUnion: 0 | 1; + enumUnion: EnumUnion; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "kind": { + "raw": "'default' | 'action'", + "name": "union", + "value": [ + { + "name": "other", + "value": "literal" + }, + { + "name": "other", + "value": "literal" + } + ] + }, + "inlinedNumericLiteralUnion": { + "raw": "0 | 1", + "name": "union", + "value": [ + { + "name": "other", + "value": "literal" + }, + { + "name": "other", + "value": "literal" + } + ] + }, + "enumUnion": { + "raw": "DefaultEnum | NumericEnum", + "name": "union", + "value": [ + { + "name": "other", + "value": "DefaultEnum" + }, + { + "name": "other", + "value": "NumericEnum" + } + ] + } + } + `); + }); + it('intersections', () => { + const input = readFixture('typescript/intersections.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface PersonInterface { + name: string; + } + type InterfaceIntersection = ItemInterface & PersonInterface; + interface Props { + intersectionType: InterfaceIntersection; + intersectionWithInlineType: ItemInterface & { inlineValue: string }; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "intersectionType": { + "raw": "ItemInterface & PersonInterface", + "name": "intersection", + "value": [ + { + "name": "other", + "value": "ItemInterface" + }, + { + "name": "other", + "value": "PersonInterface" + } + ] + }, + "intersectionWithInlineType": { + "raw": "ItemInterface & { inlineValue: string }", + "name": "intersection", + "value": [ + { + "name": "other", + "value": "ItemInterface" + }, + { + "raw": "{ inlineValue: string }", + "name": "object", + "value": { + "inlineValue": { + "name": "string" + } + } + } + ] + } + } + `); + }); + it('arrays', () => { + const input = readFixture('typescript/arrays.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface Point { + x: number; + y: number; + } + interface Props { + arrayOfPoints: Point[]; + arrayOfInlineObjects: { w: number; h: number }[]; + arrayOfPrimitive: string[]; + arrayOfComplexObject: ItemInterface[]; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "arrayOfPoints": { + "raw": "Point[]", + "name": "array", + "value": [ + { + "name": "other", + "value": "Point" + } + ] + }, + "arrayOfInlineObjects": { + "raw": "{ w: number; h: number }[]", + "name": "array", + "value": [ + { + "raw": "{ w: number; h: number }", + "name": "object", + "value": { + "w": { + "name": "number" + }, + "h": { + "name": "number" + } + } + } + ] + }, + "arrayOfPrimitive": { + "raw": "string[]", + "name": "array", + "value": [ + { + "name": "string" + } + ] + }, + "arrayOfComplexObject": { + "raw": "ItemInterface[]", + "name": "array", + "value": [ + { + "name": "other", + "value": "ItemInterface" + } + ] + } + } + `); + }); + it('interfaces', () => { + const input = readFixture('typescript/interfaces.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface GenericInterface<T> { + value: T; + } + interface Props { + interface: ItemInterface; + genericInterface: GenericInterface<string>; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "interface": { + "name": "other", + "value": "ItemInterface" + }, + "genericInterface": { + "raw": "GenericInterface<string>", + "name": "other", + "value": "GenericInterface" + } + } + `); + }); + it('records', () => { + const input = readFixture('typescript/records.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface Props { + recordOfPrimitive: Record<string, number>; + recordOfComplexObject: Record<string, ItemInterface>; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "recordOfPrimitive": { + "raw": "Record<string, number>", + "name": "other", + "value": "Record" + }, + "recordOfComplexObject": { + "raw": "Record<string, ItemInterface>", + "name": "other", + "value": "Record" + } + } + `); + }); + it('aliases', () => { + const input = readFixture('typescript/aliases.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + type StringAlias = string; + type NumberAlias = number; + type AliasesIntersection = StringAlias & NumberAlias; + type AliasesUnion = StringAlias | NumberAlias; + interface GenericAlias<T> { + value: T; + } + interface Props { + typeAlias: StringAlias; + aliasesIntersection: AliasesIntersection; + aliasesUnion: AliasesUnion; + genericAlias: GenericAlias<string>; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "typeAlias": { + "name": "string" + }, + "aliasesIntersection": { + "raw": "StringAlias & NumberAlias", + "name": "intersection", + "value": [ + { + "name": "string" + }, + { + "name": "number" + } + ] + }, + "aliasesUnion": { + "raw": "StringAlias | NumberAlias", + "name": "union", + "value": [ + { + "name": "string" + }, + { + "name": "number" + } + ] + }, + "genericAlias": { + "raw": "GenericAlias<string>", + "name": "other", + "value": "GenericAlias" + } + } + `); + }); + it('tuples', () => { + const input = readFixture('typescript/tuples.tsx'); + expect(input).toMatchInlineSnapshot(` + "import React, { FC } from 'react'; + + interface ItemInterface { + text: string; + value: string; + } + interface Props { + tupleOfPrimitive: [string, number]; + tupleWithComplexType: [string, ItemInterface]; + } + export const Component: FC<Props> = (props: Props) => <>JSON.stringify(props)</>; + " + `); + expect(convertTs(input)).toMatchInlineSnapshot(` + { + "tupleOfPrimitive": { + "raw": "[string, number]", + "name": "other", + "value": "tuple" + }, + "tupleWithComplexType": { + "raw": "[string, ItemInterface]", + "name": "other", + "value": "tuple" + } + } + `); + }); + }); + describe('PropTypes', () => { + it('scalars', () => { + const input = readFixture('proptypes/scalars.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + optionalBool: PropTypes.bool, + optionalFunc: PropTypes.func, + optionalNumber: PropTypes.number, + optionalString: PropTypes.string, + optionalSymbol: PropTypes.symbol, + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "optionalBool": { + "name": "boolean" + }, + "optionalFunc": { + "name": "function" + }, + "optionalNumber": { + "name": "number" + }, + "optionalString": { + "name": "string" + }, + "optionalSymbol": { + "name": "symbol" + } + } + `); + }); + it('arrays', () => { + const input = readFixture('proptypes/arrays.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + optionalArray: PropTypes.array, + arrayOfStrings: PropTypes.arrayOf(PropTypes.string), + arrayOfShape: PropTypes.arrayOf( + PropTypes.shape({ + active: PropTypes.bool, + }) + ), + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "optionalArray": { + "name": "array" + }, + "arrayOfStrings": { + "name": "array", + "value": { + "name": "string" + } + }, + "arrayOfShape": { + "name": "array", + "value": { + "name": "object", + "value": { + "active": { + "name": "boolean" + } + } + } + } + } + `); + }); + it('enums', () => { + const input = readFixture('proptypes/enums.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + oneOfNumber: PropTypes.oneOf([1, 2, 3]), + oneOfString: PropTypes.oneOf(['static', 'timed']), + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "oneOfNumber": { + "name": "enum", + "value": [ + "1", + "2", + "3" + ] + }, + "oneOfString": { + "name": "enum", + "value": [ + "static", + "timed" + ] + } + } + `); + }); + it('misc', () => { + const input = readFixture('proptypes/misc.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + // An object that could be one of many types + optionalUnion: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Object), + ]), + optionalMessage: PropTypes.instanceOf(Object), + // A value of any data type + requiredAny: PropTypes.any.isRequired, + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "optionalUnion": { + "name": "union", + "value": [ + { + "name": "string" + }, + { + "name": "number" + }, + { + "name": "other", + "value": "instanceOf(Object)" + } + ] + }, + "optionalMessage": { + "name": "other", + "value": "instanceOf(Object)" + }, + "requiredAny": { + "name": "other", + "value": "any" + } + } + `); + }); + it('objects', () => { + const input = readFixture('proptypes/objects.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + optionalObject: PropTypes.object, + optionalObjectOf: PropTypes.objectOf(PropTypes.number), + optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number, + }), + optionalObjectWithStrictShape: PropTypes.exact({ + name: PropTypes.string, + quantity: PropTypes.number, + }), + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "optionalObject": { + "name": "object" + }, + "optionalObjectOf": { + "name": "objectOf", + "value": { + "name": "number" + } + }, + "optionalObjectWithShape": { + "name": "object", + "value": { + "color": { + "name": "string" + }, + "fontSize": { + "name": "number" + } + } + }, + "optionalObjectWithStrictShape": { + "name": "object", + "value": { + "name": { + "name": "string" + }, + "quantity": { + "name": "number" + } + } + } + } + `); + }); + it('react', () => { + const input = readFixture('proptypes/react.js'); + expect(input).toMatchInlineSnapshot(` + "import React from 'react'; + import PropTypes from 'prop-types'; + + export const Component = (props) => <>JSON.stringify(props)</>; + Component.propTypes = { + // Anything that can be rendered: numbers, strings, elements or an array + // (or fragment) containing these types. + optionalNode: PropTypes.node, + // A React element. + optionalElement: PropTypes.element, + // A React element type (ie. MyComponent). + optionalElementType: PropTypes.elementType, + }; + " + `); + expect(convertJs(input)).toMatchInlineSnapshot(` + { + "optionalNode": { + "name": "other", + "value": "node" + }, + "optionalElement": { + "name": "other", + "value": "element" + }, + "optionalElementType": { + "name": "other", + "value": "elementType" + } + } + `); + }); + }); +}); + +const readFixture = (fixture: string) => + fs.readFileSync(`${__dirname}/__testfixtures__/${fixture}`).toString(); + +const transformToModule = (inputCode: string) => { + const options = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + esmodules: true, + }, + }, + ], + ], + }; + const { code } = transformSync(inputCode, options); + return normalizeNewlines(code); +}; + +const annotateWithDocgen = (inputCode: string, filename: string) => { + const options = { + presets: ['@babel/typescript', '@babel/react'], + plugins: ['babel-plugin-react-docgen', '@babel/plugin-proposal-class-properties'], + babelrc: false, + filename, + }; + const { code } = transformSync(inputCode, options); + return normalizeNewlines(code); +}; + +const convertCommon = (code: string, fileExt: string) => { + const docgenPretty = annotateWithDocgen(code, `temp.${fileExt}`); + const { Component } = requireFromString(transformToModule(docgenPretty)); + // eslint-disable-next-line no-underscore-dangle + const { props = {} } = Component.__docgenInfo || {}; + const types = mapValues(props, (prop) => convert(prop)); + return types; +}; + +const convertTs = (code: string) => convertCommon(code, 'tsx'); + +const convertJs = (code: string) => convertCommon(code, 'js'); diff --git a/addons/docs/src/lib/sbtypes/convert.ts b/addons/docs/src/lib/sbtypes/convert.ts new file mode 100644 index 000000000000..9b7b0fb4df27 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/convert.ts @@ -0,0 +1,11 @@ +import { DocgenInfo } from '../docgen/types'; +import { convert as tsConvert, TSType } from './typescript'; +import { convert as propTypesConvert } from './proptypes'; + +export const convert = (docgenInfo: DocgenInfo) => { + const { type, tsType } = docgenInfo; + if (type != null) return propTypesConvert(type); + if (tsType != null) return tsConvert(tsType as TSType); + + return null; +}; diff --git a/addons/docs/src/lib/sbtypes/index.ts b/addons/docs/src/lib/sbtypes/index.ts new file mode 100644 index 000000000000..0ef743fdb7ef --- /dev/null +++ b/addons/docs/src/lib/sbtypes/index.ts @@ -0,0 +1,2 @@ +export * from './convert'; +export * from './types'; diff --git a/addons/docs/src/lib/sbtypes/proptypes/convert.ts b/addons/docs/src/lib/sbtypes/proptypes/convert.ts new file mode 100644 index 000000000000..105e18477826 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/proptypes/convert.ts @@ -0,0 +1,49 @@ +/* eslint-disable no-case-declarations */ +import mapValues from 'lodash/mapValues'; +import { PTType } from './types'; +import { SBType } from '../types'; +import { trimQuotes } from '../utils'; + +const SIGNATURE_REGEXP = /^\(.*\) => /; + +export const convert = (type: PTType): SBType | any => { + const { name, raw, computed, value } = type; + const base: any = {}; + if (typeof raw !== 'undefined') base.raw = raw; + + switch (name) { + case 'enum': { + const values = computed ? value : value.map((v: PTType) => trimQuotes(v.value)); + return { ...base, name, value: values }; + } + case 'string': + case 'number': + case 'symbol': + return { ...base, name }; + case 'func': + return { ...base, name: 'function' }; + case 'bool': + case 'boolean': + return { ...base, name: 'boolean' }; + case 'arrayOf': + case 'array': + return { ...base, name: 'array', value: value && convert(value as PTType) }; + case 'object': + return { ...base, name }; + case 'objectOf': + return { ...base, name, value: convert(value as PTType) }; + case 'shape': + case 'exact': + const values = mapValues(value, (field) => convert(field)); + return { ...base, name: 'object', value: values }; + case 'union': + return { ...base, name: 'union', value: value.map((v: PTType) => convert(v)) }; + case 'instanceOf': + case 'element': + case 'elementType': + default: + const otherVal = value ? `${name}(${value})` : name; + const otherName = SIGNATURE_REGEXP.test(name) ? 'function' : 'other'; + return { ...base, name: otherName, value: otherVal }; + } +}; diff --git a/addons/docs/src/lib/sbtypes/proptypes/index.ts b/addons/docs/src/lib/sbtypes/proptypes/index.ts new file mode 100644 index 000000000000..0ef743fdb7ef --- /dev/null +++ b/addons/docs/src/lib/sbtypes/proptypes/index.ts @@ -0,0 +1,2 @@ +export * from './convert'; +export * from './types'; diff --git a/addons/docs/src/lib/sbtypes/proptypes/types.ts b/addons/docs/src/lib/sbtypes/proptypes/types.ts new file mode 100644 index 000000000000..8f5e2aff2483 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/proptypes/types.ts @@ -0,0 +1,11 @@ +interface PTBaseType { + name: string; + description?: string; + required?: boolean; +} + +export type PTType = PTBaseType & { + value?: any; + raw?: string; + computed?: boolean; +}; diff --git a/addons/docs/src/lib/sbtypes/types.ts b/addons/docs/src/lib/sbtypes/types.ts new file mode 100644 index 000000000000..d0fbed123f3f --- /dev/null +++ b/addons/docs/src/lib/sbtypes/types.ts @@ -0,0 +1,42 @@ +interface SBBaseType { + required?: boolean; + raw?: string; +} + +export type SBScalarType = SBBaseType & { + name: 'boolean' | 'string' | 'number' | 'function'; +}; + +export type SBArrayType = SBBaseType & { + name: 'array'; + value: SBType; +}; +export type SBObjectType = SBBaseType & { + name: 'object'; + value: Record<string, SBType>; +}; +export type SBEnumType = SBBaseType & { + name: 'enum'; + value: (string | number)[]; +}; +export type SBIntersectionType = SBBaseType & { + name: 'intersection'; + value: SBType[]; +}; +export type SBUnionType = SBBaseType & { + name: 'union'; + value: SBType[]; +}; +export type SBOtherType = SBBaseType & { + name: 'other'; + value: string; +}; + +export type SBType = + | SBScalarType + | SBEnumType + | SBArrayType + | SBObjectType + | SBIntersectionType + | SBUnionType + | SBOtherType; diff --git a/addons/docs/src/lib/sbtypes/typescript/convert.ts b/addons/docs/src/lib/sbtypes/typescript/convert.ts new file mode 100644 index 000000000000..a53d0d79cbce --- /dev/null +++ b/addons/docs/src/lib/sbtypes/typescript/convert.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-case-declarations */ +import { TSType, TSSigType } from './types'; +import { SBType } from '../types'; + +const convertSig = (type: TSSigType) => { + switch (type.type) { + case 'function': + return { name: 'function' }; + case 'object': + const values: any = {}; + type.signature.properties.forEach((prop) => { + values[prop.key] = convert(prop.value); + }); + return { + name: 'object', + value: values, + }; + default: + throw new Error(`Unknown: ${type}`); + } +}; + +export const convert = (type: TSType): SBType | void => { + const { name, raw } = type; + const base: any = {}; + if (typeof raw !== 'undefined') base.raw = raw; + switch (type.name) { + case 'string': + case 'number': + case 'symbol': + case 'boolean': { + return { ...base, name }; + } + case 'Array': { + return { ...base, name: 'array', value: type.elements.map(convert) }; + } + case 'signature': + return { ...base, ...convertSig(type) }; + case 'union': + case 'intersection': + return { ...base, name, value: type.elements.map(convert) }; + default: + return { ...base, name: 'other', value: name }; + } +}; diff --git a/addons/docs/src/lib/sbtypes/typescript/index.ts b/addons/docs/src/lib/sbtypes/typescript/index.ts new file mode 100644 index 000000000000..0ef743fdb7ef --- /dev/null +++ b/addons/docs/src/lib/sbtypes/typescript/index.ts @@ -0,0 +1,2 @@ +export * from './convert'; +export * from './types'; diff --git a/addons/docs/src/lib/sbtypes/typescript/types.ts b/addons/docs/src/lib/sbtypes/typescript/types.ts new file mode 100644 index 000000000000..dee016d76734 --- /dev/null +++ b/addons/docs/src/lib/sbtypes/typescript/types.ts @@ -0,0 +1,46 @@ +interface TSBaseType { + name: string; + type?: string; + raw?: string; + required?: boolean; +} + +type TSArgType = TSType; + +type TSCombinationType = TSBaseType & { + name: 'union' | 'intersection'; + elements: TSType[]; +}; + +type TSFuncSigType = TSBaseType & { + name: 'signature'; + type: 'function'; + signature: { + arguments: TSArgType[]; + return: TSType; + }; +}; + +type TSObjectSigType = TSBaseType & { + name: 'signature'; + type: 'object'; + signature: { + properties: { + key: string; + value: TSType; + }[]; + }; +}; + +type TSScalarType = TSBaseType & { + name: 'any' | 'boolean' | 'number' | 'void' | 'string' | 'symbol'; +}; + +type TSArrayType = TSBaseType & { + name: 'Array'; + elements: TSType[]; +}; + +export type TSSigType = TSObjectSigType | TSFuncSigType; + +export type TSType = TSScalarType | TSCombinationType | TSSigType | TSArrayType; diff --git a/addons/docs/src/lib/sbtypes/utils.ts b/addons/docs/src/lib/sbtypes/utils.ts new file mode 100644 index 000000000000..00d59c295b7f --- /dev/null +++ b/addons/docs/src/lib/sbtypes/utils.ts @@ -0,0 +1,2 @@ +const QUOTE_REGEX = /^['"]|['"]$/g; +export const trimQuotes = (str: string) => str.replace(QUOTE_REGEX, ''); diff --git a/addons/docs/src/lib/utils.ts b/addons/docs/src/lib/utils.ts index 4a0af2fb7a5b..44725a086956 100644 --- a/addons/docs/src/lib/utils.ts +++ b/addons/docs/src/lib/utils.ts @@ -14,3 +14,5 @@ export function isTooLongForDefaultValueSummary(value: string): boolean { export function createSummaryValue(summary: string, detail?: string): PropSummaryValue { return { summary, detail }; } + +export const normalizeNewlines = (string: string) => string.replace(/\\r\\n/g, '\\n'); diff --git a/addons/docs/src/mdx/__testfixtures__/component-args.mdx b/addons/docs/src/mdx/__testfixtures__/component-args.mdx new file mode 100644 index 000000000000..d9a1c2d8e8b9 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/component-args.mdx @@ -0,0 +1,10 @@ +import { Button } from '@storybook/react/demo'; +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +<Meta title="Button" args={{ a: 1, b: 2 }} argTypes={{ a: { name: 'A' }, b: { name: 'B' } }} /> + +# Args + +<Story name="component notes"> + <Button>Component notes</Button> +</Story> diff --git a/addons/docs/src/mdx/__testfixtures__/component-args.output.snapshot b/addons/docs/src/mdx/__testfixtures__/component-args.output.snapshot new file mode 100644 index 000000000000..b65eeb6621bd --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/component-args.output.snapshot @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin component-args.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Button } from '@storybook/react/demo'; +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +const makeShortcode = (name) => + function MDXDefaultShortcode(props) { + console.warn( + 'Component ' + + name + + ' was not imported, exported, or provided by MDXProvider as global scope' + ); + return <div {...props} />; + }; + +const layoutProps = {}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Meta + title=\\"Button\\" + args={{ + a: 1, + b: 2, + }} + argTypes={{ + a: { + name: 'A', + }, + b: { + name: 'B', + }, + }} + mdxType=\\"Meta\\" + /> + <h1>{\`Args\`}</h1> + <Story name=\\"component notes\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Component notes</Button> + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const componentNotes = () => <Button>Component notes</Button>; +componentNotes.storyName = 'component notes'; +componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } }; + +const componentMeta = { + title: 'Button', + args: { + a: 1, + b: 2, + }, + argTypes: { + a: { + name: 'A', + }, + b: { + name: 'B', + }, + }, + includeStories: ['componentNotes'], +}; + +const mdxStoryNameToKey = { 'component notes': 'componentNotes' }; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> + <MDXContent /> + </AddContext> + ), +}; + +export default componentMeta; +" +`; diff --git a/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot index 28cd4ed4165d..c0ce6f96d3fa 100644 --- a/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -33,19 +33,23 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const componentNotes = () => <Button>Component notes</Button>; -componentNotes.story = {}; -componentNotes.story.name = 'component notes'; -componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' }; - -const componentMeta = { title: 'Button', id: 'button-id', includeStories: ['componentNotes'] }; +componentNotes.storyName = 'component notes'; +componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } }; + +const componentMeta = { + title: 'Button', + id: 'button-id', + component: Button, + includeStories: ['componentNotes'], +}; -const mdxStoryNameToId = { 'component notes': 'button-id--component-notes' }; +const mdxStoryNameToKey = { 'component notes': 'componentNotes' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot index bded3c333d21..b79d26aa3598 100644 --- a/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -25,7 +25,7 @@ function MDXContent({ components, ...props }) { <Meta title=\\"Button\\" decorators={[ - storyFn => ( + (storyFn) => ( <div style={{ backgroundColor: 'yellow', @@ -40,7 +40,7 @@ function MDXContent({ components, ...props }) { <h1>{\`Decorated story\`}</h1> <Story name=\\"one\\" - decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]} + decorators={[(storyFn) => <div className=\\"local\\">{storyFn()}</div>]} mdxType=\\"Story\\" > <Button mdxType=\\"Button\\">One</Button> @@ -52,15 +52,14 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const one = () => <Button>One</Button>; -one.story = {}; -one.story.name = 'one'; -one.story.parameters = { mdxSource: '<Button>One</Button>' }; -one.story.decorators = [storyFn => <div className=\\"local\\">{storyFn()}</div>]; +one.storyName = 'one'; +one.parameters = { storySource: { source: '<Button>One</Button>' } }; +one.decorators = [(storyFn) => <div className=\\"local\\">{storyFn()}</div>]; const componentMeta = { title: 'Button', decorators: [ - storyFn => ( + (storyFn) => ( <div style={{ backgroundColor: 'yellow', @@ -73,13 +72,13 @@ const componentMeta = { includeStories: ['one'], }; -const mdxStoryNameToId = { one: 'button--one' }; +const mdxStoryNameToKey = { one: 'one' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot b/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot index 59f28b9c4b4b..9b920702c186 100644 --- a/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -38,17 +38,17 @@ export const __page = () => { throw new Error('Docs-only story'); }; -__page.story = { parameters: { docsOnly: true } }; +__page.parameters = { docsOnly: true }; const componentMeta = { title: 'docs-only', includeStories: ['__page'] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.output.snapshot b/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.output.snapshot index 64bb0d0c7608..48f6b3857e47 100644 --- a/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -32,17 +32,17 @@ export const __page = () => { throw new Error('Docs-only story'); }; -__page.story = { parameters: { docsOnly: true } }; +__page.parameters = { docsOnly: true }; const componentMeta = { title: \\"Addons/Docs/what's in a title?\\", includeStories: ['__page'] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot b/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot index 39a4e7fab4ae..10a7d2266b03 100644 --- a/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; export const two = 2; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -40,24 +40,22 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const one = () => <Button>One</Button>; -one.story = {}; -one.story.name = 'one'; -one.story.parameters = { mdxSource: '<Button>One</Button>' }; +one.storyName = 'one'; +one.parameters = { storySource: { source: '<Button>One</Button>' } }; export const helloStory = () => <Button>Hello button</Button>; -helloStory.story = {}; -helloStory.story.name = 'hello story'; -helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' }; +helloStory.storyName = 'hello story'; +helloStory.parameters = { storySource: { source: '<Button>Hello button</Button>' } }; const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] }; -const mdxStoryNameToId = { one: 'button--one', 'hello story': 'button--hello-story' }; +const mdxStoryNameToKey = { one: 'one', 'hello story': 'helloStory' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot index 600811ad42ad..15ef9b0d4cc2 100644 --- a/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -49,15 +49,13 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const componentNotes = () => <Button>Component notes</Button>; -componentNotes.story = {}; -componentNotes.story.name = 'component notes'; -componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' }; +componentNotes.storyName = 'component notes'; +componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } }; export const storyNotes = () => <Button>Story notes</Button>; -storyNotes.story = {}; -storyNotes.story.name = 'story notes'; -storyNotes.story.parameters = { - mdxSource: '<Button>Story notes</Button>', +storyNotes.storyName = 'story notes'; +storyNotes.parameters = { + storySource: { source: '<Button>Story notes</Button>' }, ...{ notes: 'story notes', }, @@ -68,19 +66,17 @@ const componentMeta = { parameters: { notes: 'component notes', }, + component: Button, includeStories: ['componentNotes', 'storyNotes'], }; -const mdxStoryNameToId = { - 'component notes': 'button--component-notes', - 'story notes': 'button--story-notes', -}; +const mdxStoryNameToKey = { 'component notes': 'componentNotes', 'story notes': 'storyNotes' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot index cc9e55f146d8..17b6711fa28d 100644 --- a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Preview, Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -53,30 +53,29 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const helloButton = () => <Button>Hello button</Button>; -helloButton.story = {}; -helloButton.story.name = 'hello button'; -helloButton.story.parameters = { mdxSource: '<Button>Hello button</Button>' }; +helloButton.storyName = 'hello button'; +helloButton.parameters = { storySource: { source: '<Button>Hello button</Button>' } }; export const two = () => <Button>Two</Button>; -two.story = {}; -two.story.name = 'two'; -two.story.parameters = { mdxSource: '<Button>Two</Button>' }; +two.storyName = 'two'; +two.parameters = { storySource: { source: '<Button>Two</Button>' } }; const componentMeta = { title: 'Button', parameters: { notes: 'component notes', }, + component: Button, includeStories: ['helloButton', 'two'], }; -const mdxStoryNameToId = { 'hello button': 'button--hello-button', two: 'button--two' }; +const mdxStoryNameToKey = { 'hello button': 'helloButton', two: 'two' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-args.mdx b/addons/docs/src/mdx/__testfixtures__/story-args.mdx new file mode 100644 index 000000000000..57a20578d9ec --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-args.mdx @@ -0,0 +1,10 @@ +import { Button } from '@storybook/react/demo'; +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +<Meta title="Button" /> + +# Args + +<Story name="component notes" args={{ a: 1, b: 2 }} argTypes={{ a: { name: 'A' }, b: { name: 'B' } }}> + <Button>Component notes</Button> +</Story> diff --git a/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot new file mode 100644 index 000000000000..ef73decd1034 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-args.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Button } from '@storybook/react/demo'; +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +const makeShortcode = (name) => + function MDXDefaultShortcode(props) { + console.warn( + 'Component ' + + name + + ' was not imported, exported, or provided by MDXProvider as global scope' + ); + return <div {...props} />; + }; + +const layoutProps = {}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Meta title=\\"Button\\" mdxType=\\"Meta\\" /> + <h1>{\`Args\`}</h1> + <Story + name=\\"component notes\\" + args={{ + a: 1, + b: 2, + }} + argTypes={{ + a: { + name: 'A', + }, + b: { + name: 'B', + }, + }} + mdxType=\\"Story\\" + > + <Button mdxType=\\"Button\\">Component notes</Button> + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const componentNotes = () => <Button>Component notes</Button>; +componentNotes.storyName = 'component notes'; +componentNotes.argTypes = { + a: { + name: 'A', + }, + b: { + name: 'B', + }, +}; +componentNotes.args = { + a: 1, + b: 2, +}; +componentNotes.parameters = { storySource: { source: '<Button>Component notes</Button>' } }; + +const componentMeta = { title: 'Button', includeStories: ['componentNotes'] }; + +const mdxStoryNameToKey = { 'component notes': 'componentNotes' }; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> + <MDXContent /> + </AddContext> + ), +}; + +export default componentMeta; +" +`; diff --git a/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot index ba937af8a3ee..870d3523fb66 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -31,13 +31,13 @@ MDXContent.isMDXComponent = true; const componentMeta = { includeStories: [] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot index a37003d15677..b8029e35582b 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -33,19 +33,18 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const text = () => 'Plain text'; -text.story = {}; -text.story.name = 'text'; -text.story.parameters = { mdxSource: \\"'Plain text'\\" }; +text.storyName = 'text'; +text.parameters = { storySource: { source: \\"'Plain text'\\" } }; const componentMeta = { title: 'Text', includeStories: ['text'] }; -const mdxStoryNameToId = { text: 'text--text' }; +const mdxStoryNameToKey = { text: 'text' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot index a0b17d0ba010..eb138416630f 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -43,42 +43,38 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const one = () => <Button>One</Button>; -one.story = {}; -one.story.name = 'one'; -one.story.parameters = { mdxSource: '<Button>One</Button>' }; +one.storyName = 'one'; +one.parameters = { storySource: { source: '<Button>One</Button>' } }; export const helloStory = () => <Button>Hello button</Button>; -helloStory.story = {}; -helloStory.story.name = 'hello story'; -helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' }; +helloStory.storyName = 'hello story'; +helloStory.parameters = { storySource: { source: '<Button>Hello button</Button>' } }; export const wPunctuation = () => <Button>with punctuation</Button>; -wPunctuation.story = {}; -wPunctuation.story.name = 'w/punctuation'; -wPunctuation.story.parameters = { mdxSource: '<Button>with punctuation</Button>' }; +wPunctuation.storyName = 'w/punctuation'; +wPunctuation.parameters = { storySource: { source: '<Button>with punctuation</Button>' } }; export const _1FineDay = () => <Button>starts with number</Button>; -_1FineDay.story = {}; -_1FineDay.story.name = '1 fine day'; -_1FineDay.story.parameters = { mdxSource: '<Button>starts with number</Button>' }; +_1FineDay.storyName = '1 fine day'; +_1FineDay.parameters = { storySource: { source: '<Button>starts with number</Button>' } }; const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory', 'wPunctuation', '_1FineDay'], }; -const mdxStoryNameToId = { - one: 'button--one', - 'hello story': 'button--hello-story', - 'w/punctuation': 'button--w-punctuation', - '1 fine day': 'button--1-fine-day', +const mdxStoryNameToKey = { + one: 'one', + 'hello story': 'helloStory', + 'w/punctuation': 'wPunctuation', + '1 fine day': '_1FineDay', }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot index 6f5d19b494d8..aa80e8d0d73d 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta, Story } from '@storybook/addon-docs/blocks'; export const basicFn = () => <Button mdxType=\\"Button\\" />; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -37,19 +37,18 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; export const basic = assertIsFn(basicFn); -basic.story = {}; -basic.story.name = 'basic'; -basic.story.parameters = { mdxSource: 'basicFn' }; +basic.storyName = 'basic'; +basic.parameters = { storySource: { source: 'basicFn' } }; const componentMeta = { title: 'story-function-var', includeStories: ['basic'] }; -const mdxStoryNameToId = { basic: 'story-function-var--basic' }; +const mdxStoryNameToKey = { basic: 'basic' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot index 495335bfdcd8..fd2ebef1d98d 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot @@ -4,7 +4,7 @@ exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = ` "/* @jsx mdx */ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -39,22 +39,23 @@ export const functionStory = () => { btn.addEventListener('click', action('Click')); return btn; }; -functionStory.story = {}; -functionStory.story.name = 'function'; -functionStory.story.parameters = { - mdxSource: - \\"() => {\\\\n const btn = document.createElement('button');\\\\n btn.innerHTML = 'Hello Button';\\\\n btn.addEventListener('click', action('Click'));\\\\n return btn;\\\\n}\\", +functionStory.storyName = 'function'; +functionStory.parameters = { + storySource: { + source: + \\"() => {\\\\n const btn = document.createElement('button');\\\\n btn.innerHTML = 'Hello Button';\\\\n btn.addEventListener('click', action('Click'));\\\\n return btn;\\\\n}\\", + }, }; const componentMeta = { includeStories: ['functionStory'] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = { function: 'functionStory' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-multiple-children.mdx b/addons/docs/src/mdx/__testfixtures__/story-multiple-children.mdx new file mode 100644 index 000000000000..02e3ebed2453 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-multiple-children.mdx @@ -0,0 +1,10 @@ +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +<Meta title="Multiple" /> + +# Multiple children + +<Story name="multiple children"> + <p>Hello Child #1</p> + <p>Hello Child #2</p> +</Story> diff --git a/addons/docs/src/mdx/__testfixtures__/story-multiple-children.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-multiple-children.output.snapshot new file mode 100644 index 000000000000..e760b63f1e60 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-multiple-children.output.snapshot @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-multiple-children.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +const makeShortcode = (name) => + function MDXDefaultShortcode(props) { + console.warn( + 'Component ' + + name + + ' was not imported, exported, or provided by MDXProvider as global scope' + ); + return <div {...props} />; + }; + +const layoutProps = {}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Meta title=\\"Multiple\\" mdxType=\\"Meta\\" /> + <h1>{\`Multiple children\`}</h1> + <Story name=\\"multiple children\\" mdxType=\\"Story\\"> + <p>Hello Child #1</p> + <p>Hello Child #2</p> + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const multipleChildren = () => ( + <> + <p>Hello Child #1</p> + <p>Hello Child #2</p> + </> +); +multipleChildren.storyName = 'multiple children'; +multipleChildren.parameters = { + storySource: { source: '<p>Hello Child #1</p>\\\\n<p>Hello Child #2</p>' }, +}; + +const componentMeta = { title: 'Multiple', includeStories: ['multipleChildren'] }; + +const mdxStoryNameToKey = { 'multiple children': 'multipleChildren' }; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> + <MDXContent /> + </AddContext> + ), +}; + +export default componentMeta; +" +`; diff --git a/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot index bcd7e17e4b99..aab6752d89d0 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot @@ -8,7 +8,7 @@ import { Story, Meta } from '@storybook/addon-docs/blocks'; import { Welcome, Button } from '@storybook/angular/demo'; import { linkTo } from '@storybook/addon-links'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -51,22 +51,23 @@ export const toStorybook = () => ({ declarations: [Welcome], }, }); -toStorybook.story = {}; -toStorybook.story.name = 'to storybook'; -toStorybook.story.parameters = { - mdxSource: - '{\\\\n template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,\\\\n props: {\\\\n showApp: linkTo(\\\\'Button\\\\')\\\\n },\\\\n moduleMetadata: {\\\\n declarations: [Welcome]\\\\n }\\\\n}', +toStorybook.storyName = 'to storybook'; +toStorybook.parameters = { + storySource: { + source: + '{\\\\n template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,\\\\n props: {\\\\n showApp: linkTo(\\\\'Button\\\\')\\\\n },\\\\n moduleMetadata: {\\\\n declarations: [Welcome]\\\\n }\\\\n}', + }, }; const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] }; -const mdxStoryNameToId = { 'to storybook': 'mdx-welcome--to-storybook' }; +const mdxStoryNameToKey = { 'to storybook': 'toStorybook' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot index a00522b285b8..831e749ecaa7 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story } from '@storybook/addon-docs/blocks'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -31,13 +31,13 @@ MDXContent.isMDXComponent = true; const componentMeta = { includeStories: [] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot b/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot index b6f807d4daf8..bf2d68da5617 100644 --- a/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot @@ -7,7 +7,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta, Story } from '@storybook/addon-docs/blocks'; import { titleFunction } from '../title-generators'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -33,17 +33,17 @@ export const __page = () => { throw new Error('Docs-only story'); }; -__page.story = { parameters: { docsOnly: true } }; +__page.parameters = { docsOnly: true }; const componentMeta = { title: \`\${titleFunction('template')}\`, includeStories: ['__page'] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot b/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot index 854a773b70cb..b9b591c2c0c0 100644 --- a/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; -const makeShortcode = name => +const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( 'Component ' + @@ -32,13 +32,13 @@ MDXContent.isMDXComponent = true; const componentMeta = { includeStories: [] }; -const mdxStoryNameToId = {}; +const mdxStoryNameToKey = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => ( - <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> <MDXContent /> </AddContext> ), diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.js b/addons/docs/src/mdx/mdx-compiler-plugin.js index 5c2f788143cc..be7d6952ba29 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.js @@ -3,7 +3,6 @@ const parser = require('@babel/parser'); const generate = require('@babel/generator').default; const camelCase = require('lodash/camelCase'); const jsStringEscape = require('js-string-escape'); -const { toId, storyNameFromExport } = require('@storybook/csf'); // Generate the MDX as is, but append named exports for every // story in the contents @@ -14,14 +13,14 @@ const META_REGEX = /^<Meta[\s>]/; const RESERVED = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/; function getAttr(elt, what) { - const attr = elt.attributes.find(n => n.name.name === what); + const attr = elt.attributes.find((n) => n.name.name === what); return attr && attr.value; } -const isReserved = name => RESERVED.exec(name); -const startsWithNumber = name => /^\d/.exec(name); +const isReserved = (name) => RESERVED.exec(name); +const startsWithNumber = (name) => /^\d/.exec(name); -const sanitizeName = name => { +const sanitizeName = (name) => { let key = camelCase(name); if (startsWithNumber(key)) { key = `_${key}`; @@ -33,6 +32,15 @@ const sanitizeName = name => { const getStoryKey = (name, counter) => (name ? sanitizeName(name) : `story${counter}`); +function genAttribute(key, element) { + const value = getAttr(element, key); + if (value && value.expression) { + const { code } = generate(value.expression, {}); + return code; + } + return undefined; +} + function genStoryExport(ast, context) { let storyName = getAttr(ast.openingElement, 'name'); let storyId = getAttr(ast.openingElement, 'id'); @@ -53,61 +61,73 @@ function genStoryExport(ast, context) { const statements = []; const storyKey = getStoryKey(storyName, context.counter); - let body = ast.children.find(n => n.type !== 'JSXText'); + const bodyNodes = ast.children.filter((n) => n.type !== 'JSXText'); let storyCode = null; - - if (!body) { + let storyVal = null; + if (!bodyNodes.length) { // plain text node const { code } = generate(ast.children[0], {}); storyCode = `'${code}'`; + storyVal = `() => ( + ${storyCode} + )`; } else { - if (body.type === 'JSXExpressionContainer') { - // FIXME: handle fragments - body = body.expression; + const bodyParts = bodyNodes.map((bodyNode) => { + const body = bodyNode.type === 'JSXExpressionContainer' ? bodyNode.expression : bodyNode; + const { code } = generate(body, {}); + return { code, body }; + }); + // if we have more than two children + // 1. Add line breaks + // 2. Enclose in <> ... </> + storyCode = bodyParts.map(({ code }) => code).join('\n'); + const storyReactCode = bodyParts.length > 1 ? `<>\n${storyCode}\n</>` : storyCode; + // keep track if an indentifier or function call + // avoid breaking change for 5.3 + switch (bodyParts.length === 1 && bodyParts[0].body.type) { + // We don't know what type the identifier is, but this code + // assumes it's a function from CSF. Let's see who complains! + case 'Identifier': + storyVal = `assertIsFn(${storyCode})`; + break; + case 'ArrowFunctionExpression': + storyVal = `(${storyCode})`; + break; + default: + storyVal = `() => ( + ${storyReactCode} + )`; + break; } - const { code } = generate(body, {}); - storyCode = code; - } - - let storyVal = null; - switch (body && body.type) { - // We don't know what type the identifier is, but this code - // assumes it's a function from CSF. Let's see who complains! - case 'Identifier': - storyVal = `assertIsFn(${storyCode})`; - break; - case 'ArrowFunctionExpression': - storyVal = `(${storyCode})`; - break; - default: - storyVal = `() => ( - ${storyCode} - )`; - break; } statements.push(`export const ${storyKey} = ${storyVal};`); - statements.push(`${storyKey}.story = {};`); // always preserve the name, since CSF exports can get modified by displayName - statements.push(`${storyKey}.story.name = '${storyName}';`); + statements.push(`${storyKey}.storyName = '${storyName}';`); + + const argTypes = genAttribute('argTypes', ast.openingElement); + if (argTypes) statements.push(`${storyKey}.argTypes = ${argTypes};`); + + const args = genAttribute('args', ast.openingElement); + if (args) statements.push(`${storyKey}.args = ${args};`); let parameters = getAttr(ast.openingElement, 'parameters'); parameters = parameters && parameters.expression; const source = jsStringEscape(storyCode); + const sourceParam = `storySource: { source: '${source}' }`; if (parameters) { const { code: params } = generate(parameters, {}); - // FIXME: hack in the story's source as a parameter - statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}', ...${params} };`); + statements.push(`${storyKey}.parameters = { ${sourceParam}, ...${params} };`); } else { - statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}' };`); + statements.push(`${storyKey}.parameters = { ${sourceParam} };`); } let decorators = getAttr(ast.openingElement, 'decorators'); decorators = decorators && decorators.expression; if (decorators) { const { code: decos } = generate(decorators, {}); - statements.push(`${storyKey}.story.decorators = ${decos};`); + statements.push(`${storyKey}.decorators = ${decos};`); } // eslint-disable-next-line no-param-reassign @@ -139,8 +159,6 @@ function genPreviewExports(ast, context) { function genMeta(ast, options) { let title = getAttr(ast.openingElement, 'title'); let id = getAttr(ast.openingElement, 'id'); - let parameters = getAttr(ast.openingElement, 'parameters'); - let decorators = getAttr(ast.openingElement, 'decorators'); if (title) { if (title.type === 'StringLiteral') { title = "'".concat(jsStringEscape(title.value), "'"); @@ -159,19 +177,22 @@ function genMeta(ast, options) { } } id = id && `'${id.value}'`; - if (parameters && parameters.expression) { - const { code: params } = generate(parameters.expression, {}); - parameters = params; - } - if (decorators && decorators.expression) { - const { code: decos } = generate(decorators.expression, {}); - decorators = decos; - } + const parameters = genAttribute('parameters', ast.openingElement); + const decorators = genAttribute('decorators', ast.openingElement); + const component = genAttribute('component', ast.openingElement); + const subcomponents = genAttribute('subcomponents', ast.openingElement); + const args = genAttribute('args', ast.openingElement); + const argTypes = genAttribute('argTypes', ast.openingElement); + return { title, id, parameters, decorators, + component, + subcomponents, + args, + argTypes, }; } @@ -198,13 +219,13 @@ function getExports(node, counter, options) { return null; } -// insert `mdxKind` into the context so that we can know what "kind" we're rendering into -// when we render <Story name="xxx">...</Story>, since this MDX can be attached to any `selectedKind`! +// insert `mdxStoryNameToKey` and `mdxComponentMeta` into the context so that we +// can reconstruct the Story ID dynamically from the `name` at render time const wrapperJs = ` componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), - page: () => <AddContext mdxStoryNameToId={mdxStoryNameToId}><MDXContent /></AddContext>, + page: () => <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}><MDXContent /></AddContext>, }; `.trim(); @@ -222,18 +243,18 @@ function stringifyMeta(meta) { return result; } -const hasStoryChild = node => { +const hasStoryChild = (node) => { if (node.openingElement && node.openingElement.name.name === 'Story') { return node; } if (node.children && node.children.length > 0) { - return node.children.find(child => hasStoryChild(child)); + return node.children.find((child) => hasStoryChild(child)); } return null; }; function extractExports(node, options) { - node.children.forEach(child => { + node.children.forEach((child) => { if (child.type === 'jsx') { try { const ast = parser.parseExpression(child.value, { plugins: ['jsx'] }); @@ -255,7 +276,7 @@ function extractExports(node, options) { value: encodeURI( ast.children .map( - el => + (el) => generate(el, { quotes: 'double', }).code @@ -293,7 +314,7 @@ function extractExports(node, options) { counter: 0, storyNameToKey: {}, }; - node.children.forEach(n => { + node.children.forEach((n) => { const exports = getExports(n, context, options); if (exports) { const { stories, meta } = exports; @@ -314,7 +335,7 @@ function extractExports(node, options) { if (metaExport) { if (!storyExports.length) { storyExports.push('export const __page = () => { throw new Error("Docs-only story"); };'); - storyExports.push('__page.story = { parameters: { docsOnly: true } };'); + storyExports.push('__page.parameters = { docsOnly: true };'); includeStories.push('__page'); } } else { @@ -322,23 +343,12 @@ function extractExports(node, options) { } metaExport.includeStories = JSON.stringify(includeStories); - const { title, id: componentId } = metaExport; - const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce( - (acc, [storyName, storyKey]) => { - if (title) { - acc[storyName] = toId(componentId || title, storyNameFromExport(storyKey)); - } - return acc; - }, - {} - ); - const fullJsx = [ 'import { assertIsFn, AddContext } from "@storybook/addon-docs/blocks";', defaultJsx, ...storyExports, `const componentMeta = ${stringifyMeta(metaExport)};`, - `const mdxStoryNameToId = ${JSON.stringify(mdxStoryNameToId)};`, + `const mdxStoryNameToKey = ${JSON.stringify(context.storyNameToKey)};`, wrapperJs, 'export default componentMeta;', ].join('\n\n'); @@ -348,7 +358,7 @@ function extractExports(node, options) { function createCompiler(mdxOptions) { return function compiler(options = {}) { - this.Compiler = tree => extractExports(tree, options, mdxOptions); + this.Compiler = (tree) => extractExports(tree, options, mdxOptions); }; } diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.test.js b/addons/docs/src/mdx/mdx-compiler-plugin.test.js index 5945010318bc..0a01ee227d3e 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.test.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.test.js @@ -28,9 +28,9 @@ const inputRegExp = /\.mdx$/; describe('docs-mdx-compiler-plugin', () => { const transformFixturesDir = path.join(__dirname, '__testfixtures__'); fs.readdirSync(transformFixturesDir) - .filter(fileName => inputRegExp.test(fileName)) - .filter(fileName => fileName !== 'story-missing-props.mdx') - .forEach(fixtureFile => { + .filter((fileName) => inputRegExp.test(fileName)) + .filter((fileName) => fileName !== 'story-missing-props.mdx') + .forEach((fixtureFile) => { it(fixtureFile, async () => { const inputPath = path.join(transformFixturesDir, fixtureFile); const code = await generate(inputPath); diff --git a/addons/docs/src/mdx/title-generators.js b/addons/docs/src/mdx/title-generators.js index 6f4c8b526ef2..ddab959fab21 100644 --- a/addons/docs/src/mdx/title-generators.js +++ b/addons/docs/src/mdx/title-generators.js @@ -1 +1 @@ -export const titleFunction = title => `Addons/Docs/${title}`; +export const titleFunction = (title) => `Addons/Docs/${title}`; diff --git a/addons/docs/src/preset.ts b/addons/docs/src/preset.ts index c41a4752e6d7..e7348adfb3a4 100644 --- a/addons/docs/src/preset.ts +++ b/addons/docs/src/preset.ts @@ -1,57 +1,12 @@ -import * as commonPreset from './frameworks/common/preset'; - -// Map a framework to a preset file that gets executed -type Preset = any; -type FrameworkPresetMapper = (framework: string) => string | void; - -const PRESET_METHODS = [ - 'babel', - 'babelDefault', - 'managerBabel', - 'webpack', - 'webpackFinal', - 'managerWebpack', - 'addons', - 'entries', - 'config', -]; - -function getFrameworkPreset(frameworkPresetFile: string): Preset { - // eslint-disable-next-line import/no-dynamic-require, global-require - return require(frameworkPresetFile); -} - -// Create a composite preset that applies the base preset & -// appends any framework-specific extensions as needed -function withFrameworkExtensions(basePreset: Preset, mapper: FrameworkPresetMapper): Preset { - const extended: Preset = {}; - PRESET_METHODS.forEach(method => { - extended[method] = (existing: any, options: any) => { - let updated = existing; - - const baseMethod = basePreset[method]; - if (baseMethod) { - updated = baseMethod(updated, options); - } - - const frameworkPresetFile = mapper(options.framework); - if (frameworkPresetFile) { - const frameworkPreset = getFrameworkPreset(frameworkPresetFile); - const frameworkMethod = frameworkPreset[method]; - if (frameworkMethod) updated = frameworkMethod(updated, options); - } - - return updated; - }; - }); - return extended; -} - -module.exports = withFrameworkExtensions(commonPreset, framework => { +const getFrameworkPresets = (framework: string) => { try { - return require.resolve(`./frameworks/${framework}/preset`) as string; + return [require.resolve(`./frameworks/${framework}/preset`)]; } catch (err) { // there is no custom config for the user's framework, do nothing - return null; + return []; } -}); +}; + +module.exports = ({ framework }: any) => { + return [require.resolve('./frameworks/common/preset'), ...getFrameworkPresets(framework)]; +}; diff --git a/addons/docs/src/register.ts b/addons/docs/src/register.ts index e241774a707c..b36d05086ddb 100644 --- a/addons/docs/src/register.ts +++ b/addons/docs/src/register.ts @@ -1,11 +1,11 @@ import addons, { types } from '@storybook/addons'; import { ADDON_ID, PANEL_ID } from './shared'; -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, () => { addons.add(PANEL_ID, { type: types.TAB, title: 'Docs', - route: ({ storyId }) => `/docs/${storyId}`, + route: ({ storyId, refId }) => (refId ? `/docs/${refId}_${storyId}` : `/docs/${storyId}`), match: ({ viewMode }) => viewMode === 'docs', render: () => null, }); diff --git a/addons/docs/src/typings.d.ts b/addons/docs/src/typings.d.ts index dc9928da3997..91ec717a4f9c 100644 --- a/addons/docs/src/typings.d.ts +++ b/addons/docs/src/typings.d.ts @@ -1,8 +1,9 @@ declare module '@mdx-js/react'; -declare module '@storybook/addon-docs/mdx-compiler-plugin'; -declare module '@storybook/addon-docs/blocks'; declare module 'global'; -declare module 'react-is'; declare module '@egoist/vue-to-react'; -declare module "remark-slug"; -declare module "remark-external-links"; +declare module 'remark-slug'; +declare module 'remark-external-links'; +declare module 'babel-plugin-react-docgen'; +declare module 'require-from-string'; +declare module 'styled-components'; +declare module 'acorn-jsx'; diff --git a/addons/docs/tsconfig.json b/addons/docs/tsconfig.json index eac4a67bed71..793e61d30a1e 100644 --- a/addons/docs/tsconfig.json +++ b/addons/docs/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env", "jest"] + "types": ["webpack-env", "jest", "node"] }, "include": ["src/**/*"], "exclude": ["src/**.test.ts"] diff --git a/addons/docs/vue/README.md b/addons/docs/vue/README.md index 0a322acaeb23..925e4c7a3f28 100644 --- a/addons/docs/vue/README.md +++ b/addons/docs/vue/README.md @@ -2,16 +2,20 @@ <img src="../docs/media/vue-hero.png" width="100%" /> </center> -# Storybook Docs for Vue +<h1>Storybook Docs for Vue</h1> + +> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. Storybook Docs transforms your Storybook stories into world-class component documentation. Storybook Docs for Vue supports [DocsPage](../docs/docspage.md) for auto-generated docs, and [MDX](../docs/mdx.md) for rich long-form docs. To learn more about Storybook Docs, read the [general documentation](../README.md). To learn the Vue specifics, read on! - [Installation](#installation) +- [Preset options](#preset-options) - [DocsPage](#docspage) +- [Props tables](#props-tables) - [MDX](#mdx) -- [Inline stories](#inline-stories) +- [Inline Stories](#inline-stories) - [More resources](#more-resources) ## Installation @@ -22,19 +26,46 @@ First add the package. Make sure that the versions for your `@storybook/*` packa yarn add -D @storybook/addon-docs@next ``` -Then add the following to your `.storybook/main.js` presets: +Then add the following to your `.storybook/main.js` addons: ```js module.exports = { - presets: ['@storybook/addon-docs/preset'], + addons: ['@storybook/addon-docs'], }; ``` +## Preset options + +The `addon-docs` preset for Vue has a configuration option that can be used to configure [`vue-docgen-api`](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api), a tool which extracts information from Vue components. Here's an example of how to use the preset with options for Vue app: + +```js +const path = require('path'); + +module.exports = { + addons: [ + { + name: '@storybook/addon-docs', + options: { + vueDocgenOptions: { + alias: { + '@': path.resolve(__dirname, '../'), + }, + }, + }, + }, + ], +}; +``` + +The `vueDocgenOptions` is an object for configuring `vue-docgen-api`. See [`vue-docgen-api`'s docs](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api#options-docgenoptions) for available configuration options. + ## DocsPage When you [install docs](#installation) you should get basic [DocsPage](../docs/docspage.md) documentation automagically for all your stories, available in the `Docs` tab of the Storybook UI. -Props tables for your components requires a few more steps. Docs for Vue relies on [Addon-vue-info](https://github.com/pocka/storybook-addon-vue-info)'s loader. It supports `props`, `events`, and `slots` as first class prop types. +## Props tables + +Getting [Props tables](../docs/props-tables.md) for your components requires a few more steps. Docs for Vue relies on [`vue-docgen-loader`](https://github.com/pocka/vue-docgen-loader). It supports `props`, `events`, and `slots` as first class prop types. Finally, be sure to fill in the `component` field in your story metadata: @@ -72,7 +103,7 @@ Then update your `.storybook/main.js` to make sure you load MDX files: ```js module.exports = { - stories: ['../src/stories/**/*.stories.(js|mdx)'], + stories: ['../src/stories/**/*.stories.@(js|mdx)'], }; ``` @@ -104,7 +135,7 @@ Yes, it's redundant to declare `component` twice. [Coming soon](https://github.c Storybook Docs renders all Vue stories inside IFrames, with a default height of `60px` (configurable using the `docs.iframeHeight` story parameter). -Starting in 5.3, you can also render stories inline, and in 6.0 this will become the default behavior. To render inline, update `.storybook/preview.js`: +Starting in 5.3, you can also render stories inline, and in 6.0 this has become the default behavior. To render inline, update `.storybook/preview.js`: ```js import { addParameters } from '@storybook/vue'; @@ -120,7 +151,6 @@ addParameters({ Want to learn more? Here are some more articles on Storybook Docs: -- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) -- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) -- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) / [Props](../docs/props-tables.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) - Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/vue/preset.js b/addons/docs/vue/preset.js deleted file mode 100644 index 09cb6c49e5e5..000000000000 --- a/addons/docs/vue/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/common/makePreset').default('vue'); diff --git a/addons/docs/web-components/README.md b/addons/docs/web-components/README.md index 9713f42e1fed..0f5cfb8c6410 100644 --- a/addons/docs/web-components/README.md +++ b/addons/docs/web-components/README.md @@ -1,4 +1,9 @@ -# Storybook Docs for Web Components +<h1>Storybook Docs for Web Components</h1> + +- [Installation](#installation) +- [Props tables](#props-tables) +- [Stories not inline](#stories-not-inline) +- [More resources](#more-resources) ## Installation @@ -22,9 +27,9 @@ }; ``` -### custom-elements.json +## Props tables -In order to get documentation for web-components you will need to have a [custom-elements.json](https://github.com/webcomponents/custom-elements-json) file. +In order to get [Props tables](..docs/../../docs/props-tables.md) documentation for web-components you will need to have a [custom-elements.json](https://github.com/webcomponents/custom-elements-json) file. You can hand write it or better generate it. Depending on the web components sugar you are choosing your milage may vary. @@ -35,7 +40,16 @@ Known analyzers that output `custom-elements.json`: - [stenciljs](https://stenciljs.com/) - Supports Stencil (but does not have all metadata) -It basically looks like this: +To generate this file with Stencil, add `docs-vscode` to outputTargets in `stencil.config.ts`: + +``` +{ + type: 'docs-vscode', + file: 'custom-elements.json' +}, +``` + +The file looks something like this: ```json { @@ -83,3 +97,11 @@ or add it to individual stories. ```js <Story inline={false} /> ``` + +## More resources + +Want to learn more? Here are some more articles on Storybook Docs: + +- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md) / [Props](../docs/props-tables.md) +- Announcements: [Vision](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) / [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) / [MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) / [Framework support](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea) +- Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/web-components/preset.js b/addons/docs/web-components/preset.js deleted file mode 100644 index bb92eefa1c08..000000000000 --- a/addons/docs/web-components/preset.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/common/makePreset').default('web-components'); diff --git a/addons/essentials/README.md b/addons/essentials/README.md index 1a09f22a8dbe..f89346d497da 100644 --- a/addons/essentials/README.md +++ b/addons/essentials/README.md @@ -8,7 +8,9 @@ Each addon is documented and maintained by the core team and will be upgraded al Storybook essentials includes the following addons. Addons can be disabled and re-configured as [described below](#configuration): +- [Actions](https://github.com/storybookjs/storybook/tree/next/addons/actions) - [Backgrounds](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds) +- [Docs](https://github.com/storybookjs/storybook/tree/next/addons/docs) - [Viewport](https://github.com/storybookjs/storybook/tree/next/addons/viewport) ## Installation @@ -23,7 +25,7 @@ And then add the following line to your `.storybook/main.js`: ```js module.exports = { - presets: ['@storybook/addon-essentials'], + addons: ['@storybook/addon-essentials'], }; ``` @@ -31,7 +33,7 @@ module.exports = { Essentials is "zero config." That means that comes with a recommended configuration out of the box. -If you want to reconfigure an addon, simply install that addon per that addon's installation instructions and configure it as normal. Essentials scans your project's `package.json` on startup and if detects one of its addons is already installed, it will skip that addon's configuration entirely. +If you want to reconfigure an addon, simply install that addon per that addon's installation instructions and configure it as normal. Essentials scans your project's `main.js` on startup and if detects one of its addons is already configured in the `addons` field, it will skip that addon's configuration entirely. ## Disabling addons @@ -39,8 +41,8 @@ Yuu can disable any of Essential's addons using the following configuration sche ```js module.exports = { - presets: [{ - name: '@storybook/addon-essentials'], + addons: [{ + name: '@storybook/addon-essentials', options: { <addon-key>: false, } @@ -48,4 +50,4 @@ module.exports = { }; ``` -Valid addon keys include: `backgrounds`, `viewport` +Valid addon keys include: `actions`, `backgrounds`, `docs`, `viewport`. diff --git a/addons/essentials/package.json b/addons/essentials/package.json index ffe5ceaf0984..c5cf193a3be2 100644 --- a/addons/essentials/package.json +++ b/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", @@ -17,33 +17,47 @@ "directory": "addons/essentials" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "README.md" + "README.md", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addon-backgrounds": "5.3.0-rc.0", - "@storybook/addon-viewport": "5.3.0-rc.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/node-logger": "5.3.0-rc.0", - "ts-dedent": "^1.1.0" + "@storybook/addon-actions": "6.0.0-beta.21", + "@storybook/addon-backgrounds": "6.0.0-beta.21", + "@storybook/addon-docs": "6.0.0-beta.21", + "@storybook/addon-viewport": "6.0.0-beta.21", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/node-logger": "6.0.0-beta.21", + "core-js": "^3.0.1", + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1" }, "devDependencies": { - "@types/jest": "^24.0.11" + "@types/jest": "^25.1.1", + "@types/webpack-env": "^1.15.2" }, "peerDependencies": { "babel-loader": "^8.0.0", "react": "^16.8.0", + "react-dom": "*", "react-is": "^16.8.0" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/essentials/src/index.ts b/addons/essentials/src/index.ts index fc9dbd0f1255..d95fb32acf88 100644 --- a/addons/essentials/src/index.ts +++ b/addons/essentials/src/index.ts @@ -1,32 +1,40 @@ -import fs from 'fs'; +import path from 'path'; import { logger } from '@storybook/node-logger'; -type PresetOptions = { +interface PresetOptions { + configDir?: string; backgrounds?: any; viewport?: any; -}; + docs?: any; +} -let packageJson: any = {}; -if (fs.existsSync('./package.json')) { +const requireMain = (configDir: string) => { + let main = {}; + const mainFile = path.join(process.cwd(), configDir, 'main'); try { - packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); + // eslint-disable-next-line global-require,import/no-dynamic-require + main = require(mainFile); } catch (err) { - logger.error(`Error reading package.json: ${err.message}`); + logger.warn(`Unable to find main.js: ${mainFile}`); } -} - -const isInstalled = (addon: string) => { - const { dependencies, devDependencies } = packageJson; - return (dependencies && dependencies[addon]) || (devDependencies && devDependencies[addon]); + return main; }; -const makeAddon = (key: string) => `@storybook/addon-${key}`; +export function addons(options: PresetOptions = {}) { + const checkInstalled = (addon: string, main: any) => { + const existingAddon = main.addons?.find((entry: string | { name: string }) => { + const name = typeof entry === 'string' ? entry : entry.name; + return name?.startsWith(addon); + }); + if (existingAddon) { + logger.warn(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`); + } + return !!existingAddon; + }; -export function addons(entry: any[] = [], options: PresetOptions = {}) { - const registerAddons = ['backgrounds', 'viewport'] - .filter(key => (options as any)[key] !== false) - .map(key => makeAddon(key)) - .filter(addon => !isInstalled(addon)) - .map(addon => `${addon}/register`); - return [...entry, ...registerAddons]; + const main = requireMain(options.configDir); + return ['actions', 'docs', 'backgrounds', 'viewport'] + .filter((key) => (options as any)[key] !== false) + .map((key) => `@storybook/addon-${key}`) + .filter((addon) => !checkInstalled(addon, main)); } diff --git a/addons/essentials/tsconfig.json b/addons/essentials/tsconfig.json index eac4a67bed71..793e61d30a1e 100644 --- a/addons/essentials/tsconfig.json +++ b/addons/essentials/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env", "jest"] + "types": ["webpack-env", "jest", "node"] }, "include": ["src/**/*"], "exclude": ["src/**.test.ts"] diff --git a/addons/events/README.md b/addons/events/README.md index bf2db1622938..1b7278e10943 100644 --- a/addons/events/README.md +++ b/addons/events/README.md @@ -9,14 +9,14 @@ This [storybook](https://storybooks.js.org) ([source](https://github.com/storybo ### Getting Started ```sh -npm i --save-dev @storybook/addon-events +npm i --save-dev @storybook/addon-events event-emitter ``` within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-events/register'] + addons: ['@storybook/addon-events'] } ``` @@ -24,13 +24,13 @@ Then write your stories like this: ```js import withEvents from '@storybook/addon-events'; -import EventEmiter from 'event-emiter'; +import EventEmitter from 'event-emitter'; import Logger from './Logger'; import * as EVENTS from './events'; -const emiter = new EventEmiter(); -const emit = emiter.emit.bind(emiter); +const emitter = new EventEmitter(); +const emit = emitter.emit.bind(emitter); export default { title: 'withEvents', @@ -89,6 +89,6 @@ export default { } export const defaultView = () => ( - <Logger emiter={emiter} /> + <Logger emitter={emitter} /> ); ``` diff --git a/addons/events/package.json b/addons/events/package.json index 307a205b75fe..1bbbadef3a12 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -18,38 +18,49 @@ "directory": "addons/events" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "format-json": "^1.0.3", "lodash": "^4.17.15", "prop-types": "^15.7.2", "react": "^16.8.3", "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^7.0.4", - "util-deprecate": "^1.0.2" + "react-textarea-autosize": "^8.0.1", + "regenerator-runtime": "^0.13.3" + }, + "devDependencies": { + "@types/webpack-env": "^1.15.2" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/events/src/components/Event.tsx b/addons/events/src/components/Event.tsx index 43759c0c3180..e1001465471d 100644 --- a/addons/events/src/components/Event.tsx +++ b/addons/events/src/components/Event.tsx @@ -1,5 +1,4 @@ import React, { ChangeEvent, Component } from 'react'; -import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; import isEqual from 'lodash/isEqual'; @@ -109,19 +108,7 @@ interface ItemState { } class Item extends Component<ItemProps, ItemState> { - static propTypes = { - name: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - onEmit: PropTypes.func.isRequired, - // eslint-disable-next-line react/forbid-prop-types - payload: PropTypes.any, - }; - - static defaultProps = { - payload: {}, - }; - - static getDerivedStateFromProps = ({ payload }: ItemProps, { prevPayload }: ItemState) => { + static getDerivedStateFromProps = ({ payload = {} }: ItemProps, { prevPayload }: ItemState) => { if (!isEqual(payload, prevPayload)) { const payloadString = json.plain(payload); const refinedPayload = getJSONFromString(payloadString); @@ -155,7 +142,7 @@ class Item extends Component<ItemProps, ItemState> { newState.failed = true; } - this.setState(state => ({ ...state, ...newState })); + this.setState((state) => ({ ...state, ...newState })); }; onEmitClick = () => { diff --git a/addons/events/src/components/Panel.tsx b/addons/events/src/components/Panel.tsx index ed1ff169fde5..29ddc47705a0 100644 --- a/addons/events/src/components/Panel.tsx +++ b/addons/events/src/components/Panel.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { styled } from '@storybook/theming'; import { API } from '@storybook/api'; @@ -24,15 +23,6 @@ interface EventsPanelState { } export default class EventsPanel extends Component<EventsPanelProps, EventsPanelState> { - static propTypes = { - active: PropTypes.bool.isRequired, - api: PropTypes.shape({ - emit: PropTypes.func, - off: PropTypes.func, - on: PropTypes.func, - }).isRequired, - }; - state: EventsPanelState = { events: [], }; @@ -63,7 +53,7 @@ export default class EventsPanel extends Component<EventsPanelProps, EventsPanel const { active } = this.props; return active ? ( <Wrapper> - {events.map(event => ( + {events.map((event) => ( <Event key={event.name} {...event} onEmit={this.onEmit} /> ))} </Wrapper> diff --git a/addons/events/src/deprecated.js b/addons/events/src/deprecated.js deleted file mode 100644 index 68b210effff7..000000000000 --- a/addons/events/src/deprecated.js +++ /dev/null @@ -1,37 +0,0 @@ -// TODO remove in 6.0 -import addons from '@storybook/addons'; -import CoreEvents from '@storybook/core-events'; -import deprecate from 'util-deprecate'; - -import { EVENTS } from './constants'; - -let prevEvents; -let currentEmit; - -const onEmit = event => { - currentEmit(event.name, event.payload); -}; - -const subscription = () => { - const channel = addons.getChannel(); - channel.on(EVENTS.EMIT, onEmit); - return () => { - prevEvents = null; - addons.getChannel().emit(EVENTS.ADD, []); - channel.removeListener(EVENTS.EMIT, onEmit); - }; -}; - -const addEvents = ({ emit, events }) => { - if (prevEvents !== events) { - addons.getChannel().emit(EVENTS.ADD, events); - prevEvents = events; - } - currentEmit = emit; - addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); -}; - -export const WithEvents = deprecate(({ children, ...options }) => { - addEvents(options); - return children; -}, `<WithEvents> usage is deprecated, use .addDecorator(withEvents({emit, events})) instead`); diff --git a/addons/events/src/index.ts b/addons/events/src/index.ts index db09b493cad1..9eb0633e7025 100644 --- a/addons/events/src/index.ts +++ b/addons/events/src/index.ts @@ -2,7 +2,6 @@ import { ReactNode } from 'react'; import addons from '@storybook/addons'; import CoreEvents from '@storybook/core-events'; -import deprecate from 'util-deprecate'; import { EVENTS } from './constants'; @@ -49,15 +48,7 @@ interface Options { events: Event[]; } -const WithEvents = deprecate(({ children, ...options }: Options) => { - addEvents(options); - return children; -}, `<WithEvents> usage is deprecated, use .addDecorator(withEvents({emit, events})) instead`); - export default (options: Options) => { - if (options.children) { - return WithEvents(options); - } return (storyFn: () => ReactNode) => { addEvents(options); return storyFn(); diff --git a/addons/events/src/manager.tsx b/addons/events/src/manager.tsx index 6cc77ebfdadc..c5d543fe7fd2 100644 --- a/addons/events/src/manager.tsx +++ b/addons/events/src/manager.tsx @@ -5,7 +5,7 @@ import Panel from './components/Panel'; import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; export function register() { - addons.register(ADDON_ID, api => { + addons.register(ADDON_ID, (api) => { addons.addPanel(PANEL_ID, { title: 'Events', render: ({ active, key }) => <Panel key={key} api={api} active={active} />, diff --git a/addons/google-analytics/README.md b/addons/google-analytics/README.md index 3026e4c6b69f..841bab0872ba 100644 --- a/addons/google-analytics/README.md +++ b/addons/google-analytics/README.md @@ -16,7 +16,7 @@ within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-google-analytics/register'] + addons: ['@storybook/addon-google-analytics'] } ``` diff --git a/addons/google-analytics/package.json b/addons/google-analytics/package.json index 3880c31ad0e2..6fa2c90aef1b 100644 --- a/addons/google-analytics/package.json +++ b/addons/google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-google-analytics", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook addon for google analytics", "keywords": [ "addon", @@ -20,14 +20,20 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", - "react-ga": "^2.5.7" + "react-ga": "^2.5.7", + "regenerator-runtime": "^0.13.3" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^15.6.2 || ^16.0", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/google-analytics/src/register.ts b/addons/google-analytics/src/register.ts index 297d9759de3d..b2c85d647726 100644 --- a/addons/google-analytics/src/register.ts +++ b/addons/google-analytics/src/register.ts @@ -4,7 +4,7 @@ import { STORY_CHANGED, STORY_ERRORED, STORY_MISSING } from '@storybook/core-eve import ReactGA from 'react-ga'; -addons.register('storybook/google-analytics', api => { +addons.register('storybook/google-analytics', (api) => { ReactGA.initialize(window.STORYBOOK_GA_ID, window.STORYBOOK_REACT_GA_OPTIONS); api.on(STORY_CHANGED, () => { diff --git a/addons/graphql/package.json b/addons/graphql/package.json index 4b6a679d1826..99e9781a06f2 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "addon", @@ -16,32 +16,46 @@ "directory": "addons/graphql" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", + "@babel/core": "^7.9.0", + "@babel/plugin-transform-classes": "^7.9.2", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@types/webpack": "^4.41.9", + "babel-loader": "^8.0.6", "core-js": "^3.0.1", "global": "^4.3.2", - "graphiql": "^0.16.0", - "graphql": "^14.2.1", - "prop-types": "^15.7.2" + "graphiql": "^0.17.5", + "graphql": "^15.0.0", + "prop-types": "^15.7.2", + "regenerator-runtime": "^0.13.3", + "webpack": "^4.43.0" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/graphql/preset.js b/addons/graphql/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/graphql/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/graphql/src/components/FullScreen/index.tsx b/addons/graphql/src/components/FullScreen/index.tsx index abec3ffcfe00..692b699be613 100644 --- a/addons/graphql/src/components/FullScreen/index.tsx +++ b/addons/graphql/src/components/FullScreen/index.tsx @@ -1,10 +1,6 @@ import React, { FunctionComponent } from 'react'; -import PropTypes from 'prop-types'; import { style } from './style'; export const FullScreen: FunctionComponent = ({ children }) => { return <div style={style.wrapper}>{children}</div>; }; - -FullScreen.defaultProps = { children: null }; -FullScreen.propTypes = { children: PropTypes.node }; diff --git a/addons/graphql/src/manager.tsx b/addons/graphql/src/manager.tsx index 255fc79d2037..1febdc1fce98 100644 --- a/addons/graphql/src/manager.tsx +++ b/addons/graphql/src/manager.tsx @@ -1,5 +1,4 @@ import React, { FunctionComponent } from 'react'; -import PropTypes from 'prop-types'; import GraphiQL from 'graphiql'; import 'graphiql/graphiql.css'; @@ -16,8 +15,15 @@ const GQL: FunctionComponent<GQLProps> = ({ active }) => { return active ? ( <Consumer> {({ api, state }: Combo) => { - const story = state.storiesHash[state.storyId]; - const parameters = story ? api.getParameters(story.id, PARAM_KEY) : null; + const story = api.getData(state.storyId); + const parameters = story + ? api.getCurrentParameter<{ + query: string; + variables: Record<string, any>; + url: string; + fetcher: ReturnType<typeof getDefaultFetcher>; + }>(PARAM_KEY) + : null; if (parameters) { const query = reIndentQuery(parameters.query); @@ -32,8 +38,5 @@ const GQL: FunctionComponent<GQLProps> = ({ active }) => { </Consumer> ) : null; }; -GQL.propTypes = { - active: PropTypes.bool.isRequired, -}; export default GQL; diff --git a/addons/graphql/src/preset.ts b/addons/graphql/src/preset.ts new file mode 100644 index 000000000000..ac617a523797 --- /dev/null +++ b/addons/graphql/src/preset.ts @@ -0,0 +1,26 @@ +import { join } from 'path'; +import { ContextReplacementPlugin, Configuration } from 'webpack'; + +export const managerWebpack = async (config: Configuration) => { + // See https://github.com/graphql/graphql-language-service/issues/111#issuecomment-306723400 + + config.plugins.push( + new ContextReplacementPlugin(/graphql-language-service-interface[/\\]dist/, /\.js$/) + ); + + config.module.rules.push({ + test: /\.js$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: { + sourceType: 'unambiguous', + plugins: [[require.resolve('@babel/plugin-transform-classes'), { loose: true }]], + }, + }, + ], + include: new RegExp(join('node_modules', 'graphql-')), + }); + + return config; +}; diff --git a/addons/info/README.md b/addons/info/README.md deleted file mode 100644 index 7a19479ec3f8..000000000000 --- a/addons/info/README.md +++ /dev/null @@ -1,338 +0,0 @@ -# Storybook Info Addon - -Storybook Info Addon will show additional information for your stories in [Storybook](https://storybook.js.org). -Useful when you want to display usage or other types of documentation alongside your story. - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/info/docs/home-screenshot.png) - -## Installation - -Install the following npm module: - -```sh -npm i -D @storybook/addon-info -``` - -## Basic usage - -Then, add `withInfo` as a decorator to your book of stories. -It is possible to add `info` by default to all or a subsection of stories by using a global or story decorator. - -It is important to declare this decorator as **the first decorator**, otherwise it won't work well. - -```js -// Globally in your .storybook/preview.js. -import { addDecorator } from '@storybook/react'; -import { withInfo } from '@storybook/addon-info'; - -addDecorator(withInfo); -``` - -or - -```js -export default { - title: 'Component', - decorators: [withInfo], -}; -``` - -Then, you can use the `info` parameter to either pass certain options or specific documentation text to your stories. -A complete list of possible configurations can be found [in a later section](#setting-global-options). -This can be done per book of stories: - -```js -import Component from './Component'; - -export default { - title: 'Component', - parameters: { - info: {}, - }, -}; -``` - -...or for each story individually: - -```js -import Component from './Component'; - -export default { - title: 'Component', -}; - -export const defaultView = () => <Component />; -defaultView = { - parameters: { - info: { inline: true }, - }, -}; -``` - -It is also possible to disable the `info` addon entirely. -Depending on the scope at which you want to disable the addon, pass the following parameters object either to an individual story or to an `addParameters` call. - -``` -info: { - disable: true, -} -``` - -## Markdown - -The `info` addon also supports markdown. -To use markdown as additional textual documentation for your stories, either pass it directly as a String to the `info` parameters, or use the `text` option. - -```js -info: { - text: ` - description or documentation about my component, supports markdown - - ~~~js - <Button>Click Here</Button> - ~~~ - `, -} -``` - -## Setting Global Options - -To configure default options for all usage of the info addon, pass a option object along with the decorator in `.storybook/preview.js`. - -```js -import { withInfo } from '@storybook/addon-info'; - -addDecorator( - withInfo({ - header: false, - }) -); -``` - -Configuration parameters can be set at 3 different locations: passed as default options along the `addDecorator` call, passed as an object of parameters to a book of stories to the `addParameters` call, and passed as direct parameters to each individual story. -In order, all of them will be combined together, with a later call overriding the previous set configurations on a per-key basis. - -## Options and Defaults - -```js -{ - /** - * Text to display with storybook component - */ - text?: string; - /** - * Displays info inline vs click button to view - * @default false - */ - inline: boolean, - /** - * Toggles display of header with component name and description - * @default true - */ - header: boolean, - /** - * Displays the source of story Component - * @default true - */ - source: boolean, - /** - * Components used in story - * Displays Prop Tables with these components - * @default [] - */ - propTables: Array<React.ComponentType>, - /** - * Exclude Components from being shown in Prop Tables section - * Accepts an array of component classes or functions - * @default [] - */ - propTablesExclude: Array<React.ComponentType>, - /** - * Overrides styles of addon. The object should follow this shape: - * https://github.com/storybookjs/storybook/blob/master/addons/info/src/components/Story.js#L19. - * This prop can also accept a function which has the default stylesheet passed as an argument - */ - styles: Object | Function, - /** - * Overrides components used to display markdown - * @default {} - */ - components: { [key: string]: React.ComponentType }, - /** - * Max props to display per line in source code - * @default 3 - */ - maxPropsIntoLine: number, - /** - * Displays the first 10 characters of the prop name - * @default 3 - */ - maxPropObjectKeys: number, - /** - * Displays the first 10 items in the default prop array - * @default 3 - */ - maxPropArrayLength: number, - /** - * Displays the first 100 characters in the default prop string - * @default 50 - */ - maxPropStringLength: number, - /** - * Override the component used to render the props table - * @default PropTable - */ - TableComponent: React.ComponentType, - /** - * Will exclude any respective properties whose name is included in array - * @default [] - */ - excludedPropTypes: Array<string>, -} -``` - -### Rendering a Custom Table - -The `TableComponent` option allows you to define how the prop table should be rendered. Your component will be rendered with the following props. - -```js - { - propDefinitions: Array<{ - property: string, // The name of the prop - propType: Object | string, // The prop type. TODO: info about what this object is... - required: boolean, // True if the prop is required - description: string, // The description of the prop - defaultValue: any // The default value of the prop - }> - } -``` - -Example: - -```js -// button.js -// @flow -import React from 'react'; - -const paddingStyles = { - small: '4px 8px', - medium: '8px 16px', -}; - -const Button = ({ - size, - ...rest -}: { - /** The size of the button */ - size: 'small' | 'medium', -}) => { - const style = { - padding: paddingStyles[size] || '', - }; - return <button style={style} {...rest} />; -}; -Button.defaultProps = { - size: 'medium', -}; - -export default Button; -``` - -```js -// stories.js -import React from 'react'; - -import { storiesOf } from '@storybook/react'; -import Button from './button'; - -export default { - title: 'Button', - component: Button, - parameters: { - info: TableComponent, - }, -}; - -const Red = props => <span style={{ color: 'red' }} {...props} />; - -const TableComponent = ({ propDefinitions }) => { - const props = propDefinitions.map( - ({ property, propType, required, description, defaultValue }) => { - return ( - <tr key={property}> - <td> - {property} - {required ? <Red>*</Red> : null} - </td> - <td>{propType.name}</td> - <td>{defaultValue}</td> - <td>{description}</td> - </tr> - ); - } - ); - - return ( - <table> - <thead> - <tr> - <th>name</th> - <th>type</th> - <th>default</th> - <th>description</th> - </tr> - </thead> - <tbody>{props}</tbody> - </table> - ); -}; - -export const defaultView = () => <Button />; -``` - -### React Docgen Integration - -React Docgen is included as part of the @storybook/react package through the use of `babel-plugin-react-docgen` during babel compile time. -When rendering a story with a React component commented in this supported format, the Addon Info description will render the comments above the component declaration and the prop table will display the prop's comment in the description column. - -```js -import React from 'react'; -import PropTypes from 'prop-types'; - -/** Button component description */ -const DocgenButton = ({ disabled, label, style, onClick }) => ( - <button disabled={disabled} style={style} onClick={onClick}> - {label} - </button> -); - -DocgenButton.defaultProps = { - disabled: false, - onClick: () => {}, - style: {}, -}; - -DocgenButton.propTypes = { - /** Boolean indicating whether the button should render as disabled */ - disabled: PropTypes.bool, - /** button label. */ - label: PropTypes.string.isRequired, - /** onClick handler */ - onClick: PropTypes.func, - /** component styles */ - style: PropTypes.shape, -}; - -export default DocgenButton; -``` - -Comments above flow types are also supported. Storybook Info Addon should now render all the correct types for your component if the PropTypes are in the same file as the React component. - -## The FAQ - -**Components lose their names on static build** - -Component names also get minified with other javascript code when building for production. -When creating components, set the `displayName` static property to show the correct component name on static builds. diff --git a/addons/info/docs/home-screenshot.png b/addons/info/docs/home-screenshot.png deleted file mode 100644 index e31189043753..000000000000 Binary files a/addons/info/docs/home-screenshot.png and /dev/null differ diff --git a/addons/info/package.json b/addons/info/package.json deleted file mode 100644 index e4cae57ca153..000000000000 --- a/addons/info/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@storybook/addon-info", - "version": "5.3.0-rc.0", - "description": "A Storybook addon to show additional information for your stories.", - "keywords": [ - "addon", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/info", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/info" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", - "core-js": "^3.0.1", - "global": "^4.3.2", - "marksy": "^7.0.0", - "nested-object-assign": "^1.0.3", - "prop-types": "^15.7.2", - "react": "^16.8.3", - "react-addons-create-fragment": "^15.6.2", - "react-element-to-jsx-string": "^14.0.2", - "react-is": "^16.8.3", - "react-lifecycles-compat": "^3.0.4", - "util-deprecate": "^1.0.2" - }, - "devDependencies": { - "react-test-renderer": "^16.8.3" - }, - "peerDependencies": { - "react": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/info/src/__snapshots__/index.test.js.snap b/addons/info/src/__snapshots__/index.test.js.snap deleted file mode 100644 index 7dc48103b4f2..000000000000 --- a/addons/info/src/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,10004 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`addon Info should render <Info /> and external markdown 1`] = ` -<Component> - <Story - PropTable={[Function]} - components={ - Object { - "a": [Function], - "code": [Function], - "h1": [Function], - "h2": [Function], - "h3": [Function], - "h4": [Function], - "h5": [Function], - "h6": [Function], - "li": [Function], - "p": [Function], - "ul": [Function], - } - } - context={Object {}} - excludedPropTypes={Array []} - info="<h1>HTML Mock</h1>" - maxPropArrayLength={3} - maxPropObjectKeys={3} - maxPropStringLength={50} - maxPropsIntoLine={3} - propTableCompare={[Function]} - propTables={Array []} - propTablesExclude={Array []} - showHeader={true} - showInline={false} - showSource={true} - styles={[Function]} - > - <div - style={ - Object { - "position": "relative", - "zIndex": 0, - } - } - > - <div> - It's a - story: - <TestComponent - array={ - Array [ - 1, - 2, - 3, - ] - } - bool={true} - func={[Function]} - number={7} - obj={ - Object { - "a": "a", - "b": "b", - } - } - string="seven" - > - <div> - <h1> - x => x + 1 - </h1> - <h2> - [object Object] - </h2> - <h3> - 1,2,3 - </h3> - <h4> - 7 - </h4> - <h5> - seven - </h5> - <h6> - true - </h6> - <p> - undefined - </p> - <a - href="#" - > - test - </a> - <code> - storiesOf - </code> - <ul> - <li> - 1 - </li> - <li> - 2 - </li> - </ul> - </div> - </TestComponent> - </div> - </div> - <button - className="info__show-button" - onClick={[Function]} - style={ - Object { - "background": "#027ac5", - "border": "none", - "borderRadius": "0 0 0 5px", - "color": "#fff", - "cursor": "pointer", - "display": "block", - "fontFamily": "sans-serif", - "fontSize": 12, - "padding": "5px 15px", - "position": "fixed", - "right": 0, - "top": 0, - } - } - type="button" - > - Show Info - </button> - </Story> -</Component> -`; - -exports[`addon Info should render <Info /> and markdown 1`] = ` -<Component> - <Story - PropTable={[Function]} - components={ - Object { - "a": [Function], - "code": [Function], - "h1": [Function], - "h2": [Function], - "h3": [Function], - "h4": [Function], - "h5": [Function], - "h6": [Function], - "li": [Function], - "p": [Function], - "ul": [Function], - } - } - context={Object {}} - excludedPropTypes={Array []} - info="# Test story -## with markdown info -containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)" - maxPropArrayLength={3} - maxPropObjectKeys={3} - maxPropStringLength={50} - maxPropsIntoLine={3} - propTableCompare={[Function]} - propTables={Array []} - propTablesExclude={Array []} - showHeader={true} - showInline={false} - showSource={true} - styles={[Function]} - > - <div - style={ - Object { - "position": "relative", - "zIndex": 0, - } - } - > - <div> - It's a - story: - <TestComponent - array={ - Array [ - 1, - 2, - 3, - ] - } - bool={true} - func={[Function]} - number={7} - obj={ - Object { - "a": "a", - "b": "b", - } - } - string="seven" - > - <div> - <h1> - x => x + 1 - </h1> - <h2> - [object Object] - </h2> - <h3> - 1,2,3 - </h3> - <h4> - 7 - </h4> - <h5> - seven - </h5> - <h6> - true - </h6> - <p> - undefined - </p> - <a - href="#" - > - test - </a> - <code> - storiesOf - </code> - <ul> - <li> - 1 - </li> - <li> - 2 - </li> - </ul> - </div> - </TestComponent> - </div> - </div> - <button - className="info__show-button" - onClick={[Function]} - style={ - Object { - "background": "#027ac5", - "border": "none", - "borderRadius": "0 0 0 5px", - "color": "#fff", - "cursor": "pointer", - "display": "block", - "fontFamily": "sans-serif", - "fontSize": 12, - "padding": "5px 15px", - "position": "fixed", - "right": 0, - "top": 0, - } - } - type="button" - > - Show Info - </button> - </Story> -</Component> -`; - -exports[`addon Info should render <Info /> for memoized component 1`] = ` -<Component> - <Story - PropTable={[Function]} - components={ - Object { - "a": [Function], - "code": [Function], - "h1": [Function], - "h2": [Function], - "h3": [Function], - "h4": [Function], - "h5": [Function], - "h6": [Function], - "li": [Function], - "p": [Function], - "ul": [Function], - } - } - context={Object {}} - excludedPropTypes={Array []} - info="" - maxPropArrayLength={3} - maxPropObjectKeys={3} - maxPropStringLength={50} - maxPropsIntoLine={3} - propTableCompare={[Function]} - propTables={null} - propTablesExclude={Array []} - showHeader={true} - showInline={false} - showSource={true} - styles={[Function]} - > - <div - style={ - Object { - "position": "relative", - "zIndex": 0, - } - } - > - <div> - It's a - story: - <Memo(TestComponent) - array={ - Array [ - 1, - 2, - 3, - ] - } - bool={true} - func={[Function]} - number={7} - obj={ - Object { - "a": "a", - "b": "b", - } - } - string="seven" - > - <div> - <h1> - x => x + 1 - </h1> - <h2> - [object Object] - </h2> - <h3> - 1,2,3 - </h3> - <h4> - 7 - </h4> - <h5> - seven - </h5> - <h6> - true - </h6> - <p> - undefined - </p> - <a - href="#" - > - test - </a> - <code> - storiesOf - </code> - <ul> - <li> - 1 - </li> - <li> - 2 - </li> - </ul> - </div> - </Memo(TestComponent)> - </div> - </div> - <button - className="info__show-button" - onClick={[Function]} - style={ - Object { - "background": "#027ac5", - "border": "none", - "borderRadius": "0 0 0 5px", - "color": "#fff", - "cursor": "pointer", - "display": "block", - "fontFamily": "sans-serif", - "fontSize": 12, - "padding": "5px 15px", - "position": "fixed", - "right": 0, - "top": 0, - } - } - type="button" - > - Show Info - </button> - </Story> -</Component> -`; - -exports[`addon Info should render component description if story kind matches component 1`] = ` -.emotion-10 { - position: relative; - overflow: hidden; - color: #333333; - border: 1px solid rgba(0,0,0,.1); - background: #FFFFFF; -} - -.emotion-5 { - position: relative; -} - -.emotion-5 code { - padding-right: 10px; -} - -.emotion-5 * .token { - font-family: "Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace; - -webkit-font-smoothing: antialiased; -} - -.emotion-5 * .token.comment { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.prolog { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.doctype { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.cdata { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.string { - color: #A31515; -} - -.emotion-5 * .token.punctuation { - color: #393A34; -} - -.emotion-5 * .token.operator { - color: #393A34; -} - -.emotion-5 * .token.url { - color: #36acaa; -} - -.emotion-5 * .token.symbol { - color: #36acaa; -} - -.emotion-5 * .token.number { - color: #36acaa; -} - -.emotion-5 * .token.boolean { - color: #36acaa; -} - -.emotion-5 * .token.variable { - color: #36acaa; -} - -.emotion-5 * .token.constant { - color: #36acaa; -} - -.emotion-5 * .token.inserted { - color: #36acaa; -} - -.emotion-5 * .token.atrule { - color: #0000ff; -} - -.emotion-5 * .token.keyword { - color: #0000ff; -} - -.emotion-5 * .token.attr-value { - color: #0000ff; -} - -.emotion-5 * .token.function { - color: #393A34; -} - -.emotion-5 * .token.deleted { - color: #9a050f; -} - -.emotion-5 * .token.important { - font-weight: bold; -} - -.emotion-5 * .token.bold { - font-weight: bold; -} - -.emotion-5 * .token.italic { - font-style: italic; -} - -.emotion-5 * .token.class-name { - color: #2B91AF; -} - -.emotion-5 * .token.tag { - color: #800000; -} - -.emotion-5 * .token.selector { - color: #800000; -} - -.emotion-5 * .token.attr-name { - color: #ff0000; -} - -.emotion-5 * .token.property { - color: #ff0000; -} - -.emotion-5 * .token.regex { - color: #ff0000; -} - -.emotion-5 * .token.entity { - color: #ff0000; -} - -.emotion-5 * .token.directive.tag .tag { - background: #ffff00; - color: #393A34; -} - -.emotion-5 * .language-json .token.boolean { - color: #0000ff; -} - -.emotion-5 * .language-json .token.number { - color: #0000ff; -} - -.emotion-5 * .language-json .token.property { - color: #2B91AF; -} - -.emotion-5 * .namespace { - opacity: 0.7; -} - -.emotion-2 { - overflow-y: auto; - height: 100%; - overflow-x: auto; - width: 100%; - position: relative; -} - -.emotion-2 code { - padding-right: 10px; -} - -.emotion-2 * .token { - font-family: "Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace; - -webkit-font-smoothing: antialiased; -} - -.emotion-2 * .token.comment { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.prolog { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.doctype { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.cdata { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.string { - color: #A31515; -} - -.emotion-2 * .token.punctuation { - color: #393A34; -} - -.emotion-2 * .token.operator { - color: #393A34; -} - -.emotion-2 * .token.url { - color: #36acaa; -} - -.emotion-2 * .token.symbol { - color: #36acaa; -} - -.emotion-2 * .token.number { - color: #36acaa; -} - -.emotion-2 * .token.boolean { - color: #36acaa; -} - -.emotion-2 * .token.variable { - color: #36acaa; -} - -.emotion-2 * .token.constant { - color: #36acaa; -} - -.emotion-2 * .token.inserted { - color: #36acaa; -} - -.emotion-2 * .token.atrule { - color: #0000ff; -} - -.emotion-2 * .token.keyword { - color: #0000ff; -} - -.emotion-2 * .token.attr-value { - color: #0000ff; -} - -.emotion-2 * .token.function { - color: #393A34; -} - -.emotion-2 * .token.deleted { - color: #9a050f; -} - -.emotion-2 * .token.important { - font-weight: bold; -} - -.emotion-2 * .token.bold { - font-weight: bold; -} - -.emotion-2 * .token.italic { - font-style: italic; -} - -.emotion-2 * .token.class-name { - color: #2B91AF; -} - -.emotion-2 * .token.tag { - color: #800000; -} - -.emotion-2 * .token.selector { - color: #800000; -} - -.emotion-2 * .token.attr-name { - color: #ff0000; -} - -.emotion-2 * .token.property { - color: #ff0000; -} - -.emotion-2 * .token.regex { - color: #ff0000; -} - -.emotion-2 * .token.entity { - color: #ff0000; -} - -.emotion-2 * .token.directive.tag .tag { - background: #ffff00; - color: #393A34; -} - -.emotion-2 * .language-json .token.boolean { - color: #0000ff; -} - -.emotion-2 * .language-json .token.number { - color: #0000ff; -} - -.emotion-2 * .language-json .token.property { - color: #2B91AF; -} - -.emotion-2 * .namespace { - opacity: 0.7; -} - -.emotion-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - margin: 0; - padding: 10px; -} - -.emotion-0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - padding-right: 0; - opacity: 1; -} - -.emotion-9 { - position: absolute; - bottom: 0; - right: 0; - max-width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: #FFFFFF; - z-index: 1; -} - -.emotion-8 { - border: 0 none; - padding: 4px 10px; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - color: #333333; - background: #FFFFFF; - font-size: 12px; - line-height: 16px; - font-family: "Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: 700; - border-top: 1px solid rgba(0,0,0,.1); - border-left: 1px solid rgba(0,0,0,.1); - margin-left: -1px; - border-radius: 4px 0 0 0; -} - -.emotion-8:not(:last-child) { - border-right: 1px solid rgba(0,0,0,.1); -} - -.emotion-8 + * { - border-left: 1px solid rgba(0,0,0,.1); - border-radius: 0; -} - -.emotion-8:focus { - box-shadow: #1EA7FD 0 -3px 0 0 inset; - outline: 0 none; -} - -<Info> - <Story - PropTable={[Function]} - components={ - Object { - "a": [Function], - "code": [Function], - "h1": [Function], - "h2": [Function], - "h3": [Function], - "h4": [Function], - "h5": [Function], - "h6": [Function], - "li": [Function], - "p": [Function], - "ul": [Function], - } - } - context={ - Object { - "kind": "TestComponent", - "name": "Basic test", - } - } - excludedPropTypes={Array []} - info="" - maxPropArrayLength={3} - maxPropObjectKeys={3} - maxPropStringLength={50} - maxPropsIntoLine={3} - propTableCompare={[Function]} - propTables={null} - propTablesExclude={Array []} - showHeader={true} - showInline={true} - showSource={true} - styles={[Function]} - > - <div> - <div - style={ - Object { - "backgroundColor": "#fff", - "borderRadius": "2px", - "color": "black", - "fontFamily": "Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif", - "fontSize": "15px", - "fontWeight": 300, - "lineHeight": 1.45, - "padding": "20px 40px 40px", - } - } - > - <div - style={ - Object { - "borderBottom": "1px solid #eee", - "marginBottom": 10, - "paddingTop": 10, - } - } - > - <h1 - style={ - Object { - "fontSize": "35px", - "margin": 0, - "padding": 0, - } - } - > - TestComponent - </h1> - <h2 - style={ - Object { - "fontSize": "22px", - "fontWeight": 400, - "margin": "0 0 10px 0", - "padding": 0, - } - } - > - Basic test - </h2> - </div> - </div> - </div> - <div - id="story-root" - style={Object {}} - > - <div> - It's a - Basic test - story: - <TestComponent - array={ - Array [ - 1, - 2, - 3, - ] - } - bool={true} - func={[Function]} - number={7} - obj={ - Object { - "a": "a", - "b": "b", - } - } - string="seven" - > - <div> - <h1> - x => x + 1 - </h1> - <h2> - [object Object] - </h2> - <h3> - 1,2,3 - </h3> - <h4> - 7 - </h4> - <h5> - seven - </h5> - <h6> - true - </h6> - <p> - undefined - </p> - <a - href="#" - > - test - </a> - <code> - storiesOf - </code> - <ul> - <li> - 1 - </li> - <li> - 2 - </li> - </ul> - </div> - </TestComponent> - </div> - </div> - <div> - <div - style={ - Object { - "backgroundColor": "#fff", - "borderRadius": "2px", - "color": "black", - "fontFamily": "Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif", - "fontSize": "15px", - "fontWeight": 300, - "lineHeight": 1.45, - "padding": "20px 40px 40px", - } - } - > - <H1 - context={Object {}} - id="awesome-test-component-description" - key="0" - > - <h1 - id="awesome-test-component-description" - style={ - Object { - "borderBottom": "1px solid #eee", - "fontSize": "40px", - "fontWeight": 600, - "margin": 0, - "padding": 0, - } - } - > - Awesome test component description - </h1> - </H1> - <H2 - context={Object {}} - id="awesome-test-component-description-with-markdown-support" - key="1" - > - <h2 - id="awesome-test-component-description-with-markdown-support" - style={ - Object { - "fontSize": "30px", - "fontWeight": 600, - "margin": 0, - "padding": 0, - } - } - > - with markdown support - </h2> - </H2> - <P - context={Object {}} - key="4" - > - <p> - <strong - key="2" - > - bold - </strong> - - <em - key="3" - > - cursive - </em> - </p> - </P> - <Code - code="a;" - key="5" - language="js" - > - <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": "1o7rzh8-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;", - "toString": [Function], - }, - "inlineGlow": Object { - "map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUNzQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */", - "name": "x4tfcc-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;", - "toString": [Function], - }, - "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, - }, - }, - } - } - > - <SyntaxHighlighter - bordered={true} - copyable={true} - format={false} - language="js" - > - <Styled(div) - bordered={true} - className={null} - padded={false} - > - <div - className="emotion-10" - > - <Styled(Component)> - <Component - className="emotion-5" - > - <ScrollArea - className="emotion-5" - horizontal={true} - vertical={true} - > - <ForwardRef(render) - styles={[Function]} - > - <InnerGlobal - cache={ - Object { - "insert": [Function], - "inserted": Object { - "11xgcgt": true, - "1fhpnuv": true, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - }, - "key": "css", - "nonce": undefined, - "registered": Object { - "emotion-2": "overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - "emotion-5": "position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - }, - "sheet": StyleSheet { - "before": null, - "container": <head> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style> - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style> - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style> - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style> - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style> - </head>, - "ctr": 82, - "isSpeedy": false, - "key": "css", - "nonce": undefined, - "tags": Array [ - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style>, - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style>, - ], - }, - } - } - serialized={ - Object { - "map": undefined, - "name": "nh5djz", - "next": undefined, - "styles": "[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;}.simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;}.simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;}.simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0;}.simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;}.simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;}.simplebar-scrollbar:before{position:absolute;content:\\"\\";border-radius:7px;left:0;right:0;opacity:0;transition:opacity 0.2s linear;background:#333333;}.simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;transition:opacity 0s linear;}.simplebar-track.simplebar-vertical{top:0;width:11px;}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;}.simplebar-track.simplebar-horizontal{left:0;height:11px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;}[data-simplebar-direction=\\"rtl\\"] .simplebar-track.simplebar-vertical{right:auto;left:0;}", - "toString": [Function], - } - } - /> - </ForwardRef(render)> - <Styled(Component) - className="emotion-5" - horizontal={true} - vertical={true} - > - <Component - className="emotion-2" - horizontal={true} - vertical={true} - > - <l - className="emotion-2" - scrollableNodeProps={ - Object { - "tabIndex": 0, - } - } - > - <div - className="emotion-2" - data-simplebar={true} - > - <div - className="simplebar-wrapper" - > - <div - className="simplebar-height-auto-observer-wrapper" - > - <div - className="simplebar-height-auto-observer" - /> - </div> - <div - className="simplebar-mask" - > - <div - className="simplebar-offset" - > - <div - className="simplebar-content-wrapper" - tabIndex={0} - > - <div - className="simplebar-content" - > - <SyntaxHighlighter - CodeTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "code", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - Object { - "flex": 1, - "opacity": 1, - "paddingRight": 0, - }, - ], - "defaultProps": undefined, - "displayName": "Styled(code)", - "render": [Function], - "withComponent": [Function], - } - } - PreTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "pre", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - [Function], - ], - "defaultProps": undefined, - "displayName": "Styled(pre)", - "render": [Function], - "withComponent": [Function], - } - } - language="js" - lineNumberContainerStyle={Object {}} - padded={true} - useInlineStyles={false} - > - <Styled(pre) - className="hljs" - padded={true} - > - <pre - className="hljs emotion-1" - > - <Styled(code)> - <code - className="emotion-0" - > - a - <span - className="token punctuation" - key="code-segement1" - > - ; - </span> - </code> - </Styled(code)> - </pre> - </Styled(pre)> - </SyntaxHighlighter> - </div> - </div> - </div> - </div> - <div - className="simplebar-placeholder" - /> - </div> - <div - className="simplebar-track simplebar-horizontal" - > - <div - className="simplebar-scrollbar" - /> - </div> - <div - className="simplebar-track simplebar-vertical" - > - <div - className="simplebar-scrollbar" - /> - </div> - </div> - </l> - </Component> - </Styled(Component)> - </ScrollArea> - </Component> - </Styled(Component)> - <ActionBar - actionItems={ - Array [ - Object { - "onClick": [Function], - "title": "Copy", - }, - ] - } - > - <Styled(div)> - <div - className="emotion-9" - > - <ActionButton - key="0" - onClick={[Function]} - > - <button - className="emotion-8" - onClick={[Function]} - > - Copy - </button> - </ActionButton> - </div> - </Styled(div)> - </ActionBar> - </div> - </Styled(div)> - </SyntaxHighlighter> - </ThemeProvider> - </Code> - <h1 - style={ - Object { - "borderBottom": "1px solid #EEE", - "fontSize": "25px", - "margin": "20px 0 0 0", - "padding": "0 0 5px 0", - } - } - > - Story Source - </h1> - <Code - code="<div> - It's a Basic test story: - <TestComponent - array={[ - 1, - 2, - 3 - ]} - bool - func={function noRefCheck() {}} - number={7} - obj={{ - a: 'a', - b: 'b' - }} - string=\\"seven\\" - /> -</div>" - format={false} - language="jsx" - > - <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": "1o7rzh8-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;", - "toString": [Function], - }, - "inlineGlow": Object { - "map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUNzQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */", - "name": "x4tfcc-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;", - "toString": [Function], - }, - "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, - }, - }, - } - } - > - <SyntaxHighlighter - bordered={true} - copyable={true} - format={false} - language="jsx" - > - <Styled(div) - bordered={true} - className={null} - padded={false} - > - <div - className="emotion-10" - > - <Styled(Component)> - <Component - className="emotion-5" - > - <ScrollArea - className="emotion-5" - horizontal={true} - vertical={true} - > - <ForwardRef(render) - styles={[Function]} - > - <InnerGlobal - cache={ - Object { - "insert": [Function], - "inserted": Object { - "11xgcgt": true, - "1fhpnuv": true, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - }, - "key": "css", - "nonce": undefined, - "registered": Object { - "emotion-2": "overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - "emotion-5": "position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - }, - "sheet": StyleSheet { - "before": null, - "container": <head> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style> - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style> - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style> - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style> - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style> - </head>, - "ctr": 82, - "isSpeedy": false, - "key": "css", - "nonce": undefined, - "tags": Array [ - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style>, - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style>, - ], - }, - } - } - serialized={ - Object { - "map": undefined, - "name": "nh5djz", - "next": undefined, - "styles": "[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;}.simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;}.simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;}.simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0;}.simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;}.simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;}.simplebar-scrollbar:before{position:absolute;content:\\"\\";border-radius:7px;left:0;right:0;opacity:0;transition:opacity 0.2s linear;background:#333333;}.simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;transition:opacity 0s linear;}.simplebar-track.simplebar-vertical{top:0;width:11px;}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;}.simplebar-track.simplebar-horizontal{left:0;height:11px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;}[data-simplebar-direction=\\"rtl\\"] .simplebar-track.simplebar-vertical{right:auto;left:0;}", - "toString": [Function], - } - } - /> - </ForwardRef(render)> - <Styled(Component) - className="emotion-5" - horizontal={true} - vertical={true} - > - <Component - className="emotion-2" - horizontal={true} - vertical={true} - > - <l - className="emotion-2" - scrollableNodeProps={ - Object { - "tabIndex": 0, - } - } - > - <div - className="emotion-2" - data-simplebar={true} - > - <div - className="simplebar-wrapper" - > - <div - className="simplebar-height-auto-observer-wrapper" - > - <div - className="simplebar-height-auto-observer" - /> - </div> - <div - className="simplebar-mask" - > - <div - className="simplebar-offset" - > - <div - className="simplebar-content-wrapper" - tabIndex={0} - > - <div - className="simplebar-content" - > - <SyntaxHighlighter - CodeTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "code", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - Object { - "flex": 1, - "opacity": 1, - "paddingRight": 0, - }, - ], - "defaultProps": undefined, - "displayName": "Styled(code)", - "render": [Function], - "withComponent": [Function], - } - } - PreTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "pre", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - [Function], - ], - "defaultProps": undefined, - "displayName": "Styled(pre)", - "render": [Function], - "withComponent": [Function], - } - } - language="jsx" - lineNumberContainerStyle={Object {}} - padded={true} - useInlineStyles={false} - > - <Styled(pre) - className="hljs" - padded={true} - > - <pre - className="hljs emotion-1" - > - <Styled(code)> - <code - className="emotion-0" - > - <span - className="token tag" - key="code-segement0" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - < - </span> - div - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - > - </span> - </span> - <span - className="token plain-text" - key="code-segement1" - > - - It's a Basic test story: - - </span> - <span - className="token tag" - key="code-segement2" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - < - </span> - <span - className="token class-name" - key="code-segment-1-1" - > - TestComponent - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-2" - > - array - </span> - <span - className="token script language-javascript" - key="code-segment-1-3" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-2" - > - [ - </span> - - - <span - className="token number" - key="code-segment-1-4" - > - 1 - </span> - <span - className="token punctuation" - key="code-segment-1-5" - > - , - </span> - - - <span - className="token number" - key="code-segment-1-7" - > - 2 - </span> - <span - className="token punctuation" - key="code-segment-1-8" - > - , - </span> - - - <span - className="token number" - key="code-segment-1-10" - > - 3 - </span> - - - <span - className="token punctuation" - key="code-segment-1-12" - > - ] - </span> - <span - className="token punctuation" - key="code-segment-1-13" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-5" - > - bool - </span> - - - <span - className="token attr-name" - key="code-segment-1-7" - > - func - </span> - <span - className="token script language-javascript" - key="code-segment-1-8" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token keyword" - key="code-segment-1-2" - > - function - </span> - - <span - className="token function" - key="code-segment-1-4" - > - noRefCheck - </span> - <span - className="token punctuation" - key="code-segment-1-5" - > - ( - </span> - <span - className="token punctuation" - key="code-segment-1-6" - > - ) - </span> - - <span - className="token punctuation" - key="code-segment-1-8" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-9" - > - } - </span> - <span - className="token punctuation" - key="code-segment-1-10" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-10" - > - number - </span> - <span - className="token script language-javascript" - key="code-segment-1-11" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token number" - key="code-segment-1-2" - > - 7 - </span> - <span - className="token punctuation" - key="code-segment-1-3" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-13" - > - obj - </span> - <span - className="token script language-javascript" - key="code-segment-1-14" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-2" - > - { - </span> - - a - <span - className="token punctuation" - key="code-segment-1-4" - > - : - </span> - - <span - className="token string" - key="code-segment-1-6" - > - 'a' - </span> - <span - className="token punctuation" - key="code-segment-1-7" - > - , - </span> - - b - <span - className="token punctuation" - key="code-segment-1-9" - > - : - </span> - - <span - className="token string" - key="code-segment-1-11" - > - 'b' - </span> - - - <span - className="token punctuation" - key="code-segment-1-13" - > - } - </span> - <span - className="token punctuation" - key="code-segment-1-14" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-16" - > - string - </span> - <span - className="token attr-value" - key="code-segment-1-17" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - " - </span> - seven - <span - className="token punctuation" - key="code-segment-1-3" - > - " - </span> - </span> - - - <span - className="token punctuation" - key="code-segment-1-19" - > - /> - </span> - </span> - <span - className="token plain-text" - key="code-segement3" - > - - - </span> - <span - className="token tag" - key="code-segement4" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - </ - </span> - div - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - > - </span> - </span> - </code> - </Styled(code)> - </pre> - </Styled(pre)> - </SyntaxHighlighter> - </div> - </div> - </div> - </div> - <div - className="simplebar-placeholder" - /> - </div> - <div - className="simplebar-track simplebar-horizontal" - > - <div - className="simplebar-scrollbar" - /> - </div> - <div - className="simplebar-track simplebar-vertical" - > - <div - className="simplebar-scrollbar" - /> - </div> - </div> - </l> - </Component> - </Styled(Component)> - </ScrollArea> - </Component> - </Styled(Component)> - <ActionBar - actionItems={ - Array [ - Object { - "onClick": [Function], - "title": "Copy", - }, - ] - } - > - <Styled(div)> - <div - className="emotion-9" - > - <ActionButton - key="0" - onClick={[Function]} - > - <button - className="emotion-8" - onClick={[Function]} - > - Copy - </button> - </ActionButton> - </div> - </Styled(div)> - </ActionBar> - </div> - </Styled(div)> - </SyntaxHighlighter> - </ThemeProvider> - </Code> - </div> - </div> - </Story> -</Info> -`; - -exports[`addon Info should render component description if story name matches component 1`] = ` -.emotion-10 { - position: relative; - overflow: hidden; - color: #333333; - border: 1px solid rgba(0,0,0,.1); - background: #FFFFFF; -} - -.emotion-5 { - position: relative; -} - -.emotion-5 code { - padding-right: 10px; -} - -.emotion-5 * .token { - font-family: "Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace; - -webkit-font-smoothing: antialiased; -} - -.emotion-5 * .token.comment { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.prolog { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.doctype { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.cdata { - color: #008000; - font-style: italic; -} - -.emotion-5 * .token.string { - color: #A31515; -} - -.emotion-5 * .token.punctuation { - color: #393A34; -} - -.emotion-5 * .token.operator { - color: #393A34; -} - -.emotion-5 * .token.url { - color: #36acaa; -} - -.emotion-5 * .token.symbol { - color: #36acaa; -} - -.emotion-5 * .token.number { - color: #36acaa; -} - -.emotion-5 * .token.boolean { - color: #36acaa; -} - -.emotion-5 * .token.variable { - color: #36acaa; -} - -.emotion-5 * .token.constant { - color: #36acaa; -} - -.emotion-5 * .token.inserted { - color: #36acaa; -} - -.emotion-5 * .token.atrule { - color: #0000ff; -} - -.emotion-5 * .token.keyword { - color: #0000ff; -} - -.emotion-5 * .token.attr-value { - color: #0000ff; -} - -.emotion-5 * .token.function { - color: #393A34; -} - -.emotion-5 * .token.deleted { - color: #9a050f; -} - -.emotion-5 * .token.important { - font-weight: bold; -} - -.emotion-5 * .token.bold { - font-weight: bold; -} - -.emotion-5 * .token.italic { - font-style: italic; -} - -.emotion-5 * .token.class-name { - color: #2B91AF; -} - -.emotion-5 * .token.tag { - color: #800000; -} - -.emotion-5 * .token.selector { - color: #800000; -} - -.emotion-5 * .token.attr-name { - color: #ff0000; -} - -.emotion-5 * .token.property { - color: #ff0000; -} - -.emotion-5 * .token.regex { - color: #ff0000; -} - -.emotion-5 * .token.entity { - color: #ff0000; -} - -.emotion-5 * .token.directive.tag .tag { - background: #ffff00; - color: #393A34; -} - -.emotion-5 * .language-json .token.boolean { - color: #0000ff; -} - -.emotion-5 * .language-json .token.number { - color: #0000ff; -} - -.emotion-5 * .language-json .token.property { - color: #2B91AF; -} - -.emotion-5 * .namespace { - opacity: 0.7; -} - -.emotion-2 { - overflow-y: auto; - height: 100%; - overflow-x: auto; - width: 100%; - position: relative; -} - -.emotion-2 code { - padding-right: 10px; -} - -.emotion-2 * .token { - font-family: "Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace; - -webkit-font-smoothing: antialiased; -} - -.emotion-2 * .token.comment { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.prolog { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.doctype { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.cdata { - color: #008000; - font-style: italic; -} - -.emotion-2 * .token.string { - color: #A31515; -} - -.emotion-2 * .token.punctuation { - color: #393A34; -} - -.emotion-2 * .token.operator { - color: #393A34; -} - -.emotion-2 * .token.url { - color: #36acaa; -} - -.emotion-2 * .token.symbol { - color: #36acaa; -} - -.emotion-2 * .token.number { - color: #36acaa; -} - -.emotion-2 * .token.boolean { - color: #36acaa; -} - -.emotion-2 * .token.variable { - color: #36acaa; -} - -.emotion-2 * .token.constant { - color: #36acaa; -} - -.emotion-2 * .token.inserted { - color: #36acaa; -} - -.emotion-2 * .token.atrule { - color: #0000ff; -} - -.emotion-2 * .token.keyword { - color: #0000ff; -} - -.emotion-2 * .token.attr-value { - color: #0000ff; -} - -.emotion-2 * .token.function { - color: #393A34; -} - -.emotion-2 * .token.deleted { - color: #9a050f; -} - -.emotion-2 * .token.important { - font-weight: bold; -} - -.emotion-2 * .token.bold { - font-weight: bold; -} - -.emotion-2 * .token.italic { - font-style: italic; -} - -.emotion-2 * .token.class-name { - color: #2B91AF; -} - -.emotion-2 * .token.tag { - color: #800000; -} - -.emotion-2 * .token.selector { - color: #800000; -} - -.emotion-2 * .token.attr-name { - color: #ff0000; -} - -.emotion-2 * .token.property { - color: #ff0000; -} - -.emotion-2 * .token.regex { - color: #ff0000; -} - -.emotion-2 * .token.entity { - color: #ff0000; -} - -.emotion-2 * .token.directive.tag .tag { - background: #ffff00; - color: #393A34; -} - -.emotion-2 * .language-json .token.boolean { - color: #0000ff; -} - -.emotion-2 * .language-json .token.number { - color: #0000ff; -} - -.emotion-2 * .language-json .token.property { - color: #2B91AF; -} - -.emotion-2 * .namespace { - opacity: 0.7; -} - -.emotion-1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - margin: 0; - padding: 10px; -} - -.emotion-0 { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - padding-right: 0; - opacity: 1; -} - -.emotion-9 { - position: absolute; - bottom: 0; - right: 0; - max-width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: #FFFFFF; - z-index: 1; -} - -.emotion-8 { - border: 0 none; - padding: 4px 10px; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - color: #333333; - background: #FFFFFF; - font-size: 12px; - line-height: 16px; - font-family: "Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: 700; - border-top: 1px solid rgba(0,0,0,.1); - border-left: 1px solid rgba(0,0,0,.1); - margin-left: -1px; - border-radius: 4px 0 0 0; -} - -.emotion-8:not(:last-child) { - border-right: 1px solid rgba(0,0,0,.1); -} - -.emotion-8 + * { - border-left: 1px solid rgba(0,0,0,.1); - border-radius: 0; -} - -.emotion-8:focus { - box-shadow: #1EA7FD 0 -3px 0 0 inset; - outline: 0 none; -} - -<Info> - <Story - PropTable={[Function]} - components={ - Object { - "a": [Function], - "code": [Function], - "h1": [Function], - "h2": [Function], - "h3": [Function], - "h4": [Function], - "h5": [Function], - "h6": [Function], - "li": [Function], - "p": [Function], - "ul": [Function], - } - } - context={ - Object { - "kind": "Test Components", - "name": "TestComponent", - } - } - excludedPropTypes={Array []} - info="" - maxPropArrayLength={3} - maxPropObjectKeys={3} - maxPropStringLength={50} - maxPropsIntoLine={3} - propTableCompare={[Function]} - propTables={null} - propTablesExclude={Array []} - showHeader={true} - showInline={true} - showSource={true} - styles={[Function]} - > - <div> - <div - style={ - Object { - "backgroundColor": "#fff", - "borderRadius": "2px", - "color": "black", - "fontFamily": "Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif", - "fontSize": "15px", - "fontWeight": 300, - "lineHeight": 1.45, - "padding": "20px 40px 40px", - } - } - > - <div - style={ - Object { - "borderBottom": "1px solid #eee", - "marginBottom": 10, - "paddingTop": 10, - } - } - > - <h1 - style={ - Object { - "fontSize": "35px", - "margin": 0, - "padding": 0, - } - } - > - Test Components - </h1> - <h2 - style={ - Object { - "fontSize": "22px", - "fontWeight": 400, - "margin": "0 0 10px 0", - "padding": 0, - } - } - > - TestComponent - </h2> - </div> - </div> - </div> - <div - id="story-root" - style={Object {}} - > - <div> - It's a - TestComponent - story: - <TestComponent - array={ - Array [ - 1, - 2, - 3, - ] - } - bool={true} - func={[Function]} - number={7} - obj={ - Object { - "a": "a", - "b": "b", - } - } - string="seven" - > - <div> - <h1> - x => x + 1 - </h1> - <h2> - [object Object] - </h2> - <h3> - 1,2,3 - </h3> - <h4> - 7 - </h4> - <h5> - seven - </h5> - <h6> - true - </h6> - <p> - undefined - </p> - <a - href="#" - > - test - </a> - <code> - storiesOf - </code> - <ul> - <li> - 1 - </li> - <li> - 2 - </li> - </ul> - </div> - </TestComponent> - </div> - </div> - <div> - <div - style={ - Object { - "backgroundColor": "#fff", - "borderRadius": "2px", - "color": "black", - "fontFamily": "Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif", - "fontSize": "15px", - "fontWeight": 300, - "lineHeight": 1.45, - "padding": "20px 40px 40px", - } - } - > - <H1 - context={Object {}} - id="awesome-test-component-description" - key="0" - > - <h1 - id="awesome-test-component-description" - style={ - Object { - "borderBottom": "1px solid #eee", - "fontSize": "40px", - "fontWeight": 600, - "margin": 0, - "padding": 0, - } - } - > - Awesome test component description - </h1> - </H1> - <H2 - context={Object {}} - id="awesome-test-component-description-with-markdown-support" - key="1" - > - <h2 - id="awesome-test-component-description-with-markdown-support" - style={ - Object { - "fontSize": "30px", - "fontWeight": 600, - "margin": 0, - "padding": 0, - } - } - > - with markdown support - </h2> - </H2> - <P - context={Object {}} - key="4" - > - <p> - <strong - key="2" - > - bold - </strong> - - <em - key="3" - > - cursive - </em> - </p> - </P> - <Code - code="a;" - key="5" - language="js" - > - <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": "1o7rzh8-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;", - "toString": [Function], - }, - "inlineGlow": Object { - "map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUNzQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */", - "name": "x4tfcc-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;", - "toString": [Function], - }, - "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, - }, - }, - } - } - > - <SyntaxHighlighter - bordered={true} - copyable={true} - format={false} - language="js" - > - <Styled(div) - bordered={true} - className={null} - padded={false} - > - <div - className="emotion-10" - > - <Styled(Component)> - <Component - className="emotion-5" - > - <ScrollArea - className="emotion-5" - horizontal={true} - vertical={true} - > - <ForwardRef(render) - styles={[Function]} - > - <InnerGlobal - cache={ - Object { - "insert": [Function], - "inserted": Object { - "11xgcgt": true, - "1fhpnuv": true, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - }, - "key": "css", - "nonce": undefined, - "registered": Object { - "emotion-2": "overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - "emotion-5": "position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - }, - "sheet": StyleSheet { - "before": null, - "container": <head> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style> - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style> - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style> - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style> - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style> - </head>, - "ctr": 82, - "isSpeedy": false, - "key": "css", - "nonce": undefined, - "tags": Array [ - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style>, - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style>, - ], - }, - } - } - serialized={ - Object { - "map": undefined, - "name": "nh5djz", - "next": undefined, - "styles": "[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;}.simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;}.simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;}.simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0;}.simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;}.simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;}.simplebar-scrollbar:before{position:absolute;content:\\"\\";border-radius:7px;left:0;right:0;opacity:0;transition:opacity 0.2s linear;background:#333333;}.simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;transition:opacity 0s linear;}.simplebar-track.simplebar-vertical{top:0;width:11px;}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;}.simplebar-track.simplebar-horizontal{left:0;height:11px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;}[data-simplebar-direction=\\"rtl\\"] .simplebar-track.simplebar-vertical{right:auto;left:0;}", - "toString": [Function], - } - } - /> - </ForwardRef(render)> - <Styled(Component) - className="emotion-5" - horizontal={true} - vertical={true} - > - <Component - className="emotion-2" - horizontal={true} - vertical={true} - > - <l - className="emotion-2" - scrollableNodeProps={ - Object { - "tabIndex": 0, - } - } - > - <div - className="emotion-2" - data-simplebar={true} - > - <div - className="simplebar-wrapper" - > - <div - className="simplebar-height-auto-observer-wrapper" - > - <div - className="simplebar-height-auto-observer" - /> - </div> - <div - className="simplebar-mask" - > - <div - className="simplebar-offset" - > - <div - className="simplebar-content-wrapper" - tabIndex={0} - > - <div - className="simplebar-content" - > - <SyntaxHighlighter - CodeTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "code", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - Object { - "flex": 1, - "opacity": 1, - "paddingRight": 0, - }, - ], - "defaultProps": undefined, - "displayName": "Styled(code)", - "render": [Function], - "withComponent": [Function], - } - } - PreTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "pre", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - [Function], - ], - "defaultProps": undefined, - "displayName": "Styled(pre)", - "render": [Function], - "withComponent": [Function], - } - } - language="js" - lineNumberContainerStyle={Object {}} - padded={true} - useInlineStyles={false} - > - <Styled(pre) - className="hljs" - padded={true} - > - <pre - className="hljs emotion-1" - > - <Styled(code)> - <code - className="emotion-0" - > - a - <span - className="token punctuation" - key="code-segement1" - > - ; - </span> - </code> - </Styled(code)> - </pre> - </Styled(pre)> - </SyntaxHighlighter> - </div> - </div> - </div> - </div> - <div - className="simplebar-placeholder" - /> - </div> - <div - className="simplebar-track simplebar-horizontal" - > - <div - className="simplebar-scrollbar" - /> - </div> - <div - className="simplebar-track simplebar-vertical" - > - <div - className="simplebar-scrollbar" - /> - </div> - </div> - </l> - </Component> - </Styled(Component)> - </ScrollArea> - </Component> - </Styled(Component)> - <ActionBar - actionItems={ - Array [ - Object { - "onClick": [Function], - "title": "Copy", - }, - ] - } - > - <Styled(div)> - <div - className="emotion-9" - > - <ActionButton - key="0" - onClick={[Function]} - > - <button - className="emotion-8" - onClick={[Function]} - > - Copy - </button> - </ActionButton> - </div> - </Styled(div)> - </ActionBar> - </div> - </Styled(div)> - </SyntaxHighlighter> - </ThemeProvider> - </Code> - <h1 - style={ - Object { - "borderBottom": "1px solid #EEE", - "fontSize": "25px", - "margin": "20px 0 0 0", - "padding": "0 0 5px 0", - } - } - > - Story Source - </h1> - <Code - code="<div> - It's a TestComponent story: - <TestComponent - array={[ - 1, - 2, - 3 - ]} - bool - func={function noRefCheck() {}} - number={7} - obj={{ - a: 'a', - b: 'b' - }} - string=\\"seven\\" - /> -</div>" - format={false} - language="jsx" - > - <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": "1o7rzh8-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;", - "toString": [Function], - }, - "inlineGlow": Object { - "map": "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9hbmltYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUNzQiIsImZpbGUiOiIuLi9zcmMvYW5pbWF0aW9uLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzLCBrZXlmcmFtZXMgfSBmcm9tICdAZW1vdGlvbi9jb3JlJztcblxuZXhwb3J0IGNvbnN0IGVhc2luZyA9IHtcbiAgcnViYmVyOiAnY3ViaWMtYmV6aWVyKDAuMTc1LCAwLjg4NSwgMC4zMzUsIDEuMDUpJyxcbn07XG5cbmNvbnN0IHJvdGF0ZTM2MCA9IGtleWZyYW1lc2Bcblx0ZnJvbSB7XG5cdFx0dHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG5cdH1cblx0dG8ge1xuXHRcdHRyYW5zZm9ybTogcm90YXRlKDM2MGRlZyk7XG5cdH1cbmA7XG5cbmNvbnN0IGdsb3cgPSBrZXlmcmFtZXNgXG4gIDAlLCAxMDAlIHsgb3BhY2l0eTogMTsgfVxuICA1MCUgeyBvcGFjaXR5OiAuNDsgfVxuYDtcblxuY29uc3QgZmxvYXQgPSBrZXlmcmFtZXNgXG4gIDAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDFweCk7IH1cbiAgMjUlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7IH1cbiAgNTAlIHsgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC0zcHgpOyB9XG4gIDEwMCUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuYDtcblxuY29uc3QgamlnZ2xlID0ga2V5ZnJhbWVzYFxuICAwJSwgMTAwJSB7IHRyYW5zZm9ybTp0cmFuc2xhdGUzZCgwLDAsMCk7IH1cbiAgMTIuNSUsIDYyLjUlIHsgdHJhbnNmb3JtOnRyYW5zbGF0ZTNkKC00cHgsMCwwKTsgfVxuICAzNy41JSwgODcuNSUgeyAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCg0cHgsMCwwKTsgIH1cbmA7XG5cbmNvbnN0IGlubGluZUdsb3cgPSBjc3NgXG4gIGFuaW1hdGlvbjogJHtnbG93fSAxLjVzIGVhc2UtaW4tb3V0IGluZmluaXRlO1xuICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gIGN1cnNvcjogcHJvZ3Jlc3M7XG5gO1xuXG4vLyBob3ZlciAmIGFjdGl2ZSBzdGF0ZSBmb3IgbGlua3MgYW5kIGJ1dHRvbnNcbmNvbnN0IGhvdmVyYWJsZSA9IGNzc2BcbiAgdHJhbnNpdGlvbjogYWxsIDE1MG1zIGVhc2Utb3V0O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsIDAsIDApO1xuXG4gICY6aG92ZXIge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgLTJweCwgMCk7XG4gIH1cblxuICAmOmFjdGl2ZSB7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLCAwLCAwKTtcbiAgfVxuYDtcblxuZXhwb3J0IGNvbnN0IGFuaW1hdGlvbiA9IHtcbiAgcm90YXRlMzYwLFxuICBnbG93LFxuICBmbG9hdCxcbiAgamlnZ2xlLFxuICBpbmxpbmVHbG93LFxuICBob3ZlcmFibGUsXG59O1xuIl19 */", - "name": "x4tfcc-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;", - "toString": [Function], - }, - "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, - }, - }, - } - } - > - <SyntaxHighlighter - bordered={true} - copyable={true} - format={false} - language="jsx" - > - <Styled(div) - bordered={true} - className={null} - padded={false} - > - <div - className="emotion-10" - > - <Styled(Component)> - <Component - className="emotion-5" - > - <ScrollArea - className="emotion-5" - horizontal={true} - vertical={true} - > - <ForwardRef(render) - styles={[Function]} - > - <InnerGlobal - cache={ - Object { - "insert": [Function], - "inserted": Object { - "11xgcgt": true, - "1fhpnuv": true, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - }, - "key": "css", - "nonce": undefined, - "registered": Object { - "emotion-2": "overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - "emotion-5": "position:relative;& code{padding-right:10px;}* .token{font-family:\\"Operator Mono\\", \\"Fira Code Retina\\", \\"Fira Code\\", \\"FiraCode-Retina\\", \\"Andale Mono\\", \\"Lucida Console\\", Consolas, Monaco, monospace;-webkit-font-smoothing:antialiased;&.comment{color:#008000;font-style:italic;}&.prolog{color:#008000;font-style:italic;}&.doctype{color:#008000;font-style:italic;}&.cdata{color:#008000;font-style:italic;}&.string{color:#A31515;}&.punctuation{color:#393A34;}&.operator{color:#393A34;}&.url{color:#36acaa;}&.symbol{color:#36acaa;}&.number{color:#36acaa;}&.boolean{color:#36acaa;}&.variable{color:#36acaa;}&.constant{color:#36acaa;}&.inserted{color:#36acaa;}&.atrule{color:#0000ff;}&.keyword{color:#0000ff;}&.attr-value{color:#0000ff;}&.function{color:#393A34;}&.deleted{color:#9a050f;}&.important{font-weight:bold;}&.bold{font-weight:bold;}&.italic{font-style:italic;}&.class-name{color:#2B91AF;}&.tag{color:#800000;}&.selector{color:#800000;}&.attr-name{color:#ff0000;}&.property{color:#ff0000;}&.regex{color:#ff0000;}&.entity{color:#ff0000;}&.directive.tag .tag{background:#ffff00;color:#393A34;}}* .language-json .token.boolean{color:#0000ff;}* .language-json .token.number{color:#0000ff;}* .language-json .token.property{color:#2B91AF;}* .namespace{opacity:0.7;}", - }, - "sheet": StyleSheet { - "before": null, - "container": <head> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar]{position:relative;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;-webkit-box-flex:inherit;-webkit-flex-grow:inherit;-ms-flex-positive:inherit;flex-grow:inherit;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-scrollbar:before{position:absolute;content:"";border-radius:7px;left:0;right:0;opacity:0;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear;background:#333333;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;-webkit-transition:opacity 0s linear;transition:opacity 0s linear;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical{top:0;width:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal{left:0;height:11px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;} - </style> - <style - data-emotion="css-global" - > - - .simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;} - </style> - <style - data-emotion="css-global" - > - - [data-simplebar-direction="rtl"] .simplebar-track.simplebar-vertical{right:auto;left:0;} - </style> - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style> - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style> - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style> - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style> - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style> - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style> - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style> - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style> - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style> - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style> - </head>, - "ctr": 82, - "isSpeedy": false, - "key": "css", - "nonce": undefined, - "tags": Array [ - <style - data-emotion="css" - > - - .emotion-10{position:relative;overflow:hidden;color:#333333;border:1px solid rgba(0,0,0,.1);background:#FFFFFF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5{position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-5 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-2{overflow-y:auto;height:100%;overflow-x:auto;width:100%;position:relative;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 code{padding-right:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token{font-family:"Operator Mono","Fira Code Retina","Fira Code","FiraCode-Retina","Andale Mono","Lucida Console",Consolas,Monaco,monospace;-webkit-font-smoothing:antialiased;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.comment{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.prolog{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.doctype{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.cdata{color:#008000;font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.string{color:#A31515;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.punctuation{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.operator{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.url{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.symbol{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.number{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.boolean{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.variable{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.constant{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.inserted{color:#36acaa;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.atrule{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.keyword{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-value{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.function{color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.deleted{color:#9a050f;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.important{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.bold{font-weight:bold;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.italic{font-style:italic;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.class-name{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.tag{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.selector{color:#800000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.attr-name{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.property{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.regex{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.entity{color:#ff0000;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .token.directive.tag .tag{background:#ffff00;color:#393A34;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.boolean{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.number{color:#0000ff;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .language-json .token.property{color:#2B91AF;} - </style>, - <style - data-emotion="css" - > - - .emotion-2 * .namespace{opacity:0.7;} - </style>, - <style - data-emotion="css" - > - - .emotion-1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;margin:0;padding:10px;} - </style>, - <style - data-emotion="css" - > - - .emotion-0{-webkit-flex:1;-ms-flex:1;flex:1;padding-right:0;opacity:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-9{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;z-index:1;} - </style>, - <style - data-emotion="css" - > - - .emotion-8{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-family:"Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:not(:last-child){border-right:1px solid rgba(0,0,0,.1);} - </style>, - <style - data-emotion="css" - > - - .emotion-8 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;} - </style>, - <style - data-emotion="css" - > - - .emotion-8:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;} - </style>, - ], - }, - } - } - serialized={ - Object { - "map": undefined, - "name": "nh5djz", - "next": undefined, - "styles": "[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start;}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit;}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto !important;height:auto !important;z-index:0;}.simplebar-offset{direction:inherit !important;resize:none !important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch;}.simplebar-content-wrapper{direction:inherit;position:relative;display:block;visibility:visible;}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none;}.simplebar-height-auto-observer-wrapper{height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0;}.simplebar-height-auto-observer{display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1;}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden;}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all;}.simplebar-scrollbar{position:absolute;right:2px;width:7px;min-height:10px;}.simplebar-scrollbar:before{position:absolute;content:\\"\\";border-radius:7px;left:0;right:0;opacity:0;transition:opacity 0.2s linear;background:#333333;}.simplebar-track .simplebar-scrollbar.simplebar-visible:before{opacity:0.5;transition:opacity 0s linear;}.simplebar-track.simplebar-vertical{top:0;width:11px;}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px;}.simplebar-track.simplebar-horizontal{left:0;height:11px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px;}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto;}[data-simplebar-direction=\\"rtl\\"] .simplebar-track.simplebar-vertical{right:auto;left:0;}", - "toString": [Function], - } - } - /> - </ForwardRef(render)> - <Styled(Component) - className="emotion-5" - horizontal={true} - vertical={true} - > - <Component - className="emotion-2" - horizontal={true} - vertical={true} - > - <l - className="emotion-2" - scrollableNodeProps={ - Object { - "tabIndex": 0, - } - } - > - <div - className="emotion-2" - data-simplebar={true} - > - <div - className="simplebar-wrapper" - > - <div - className="simplebar-height-auto-observer-wrapper" - > - <div - className="simplebar-height-auto-observer" - /> - </div> - <div - className="simplebar-mask" - > - <div - className="simplebar-offset" - > - <div - className="simplebar-content-wrapper" - tabIndex={0} - > - <div - className="simplebar-content" - > - <SyntaxHighlighter - CodeTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "code", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - Object { - "flex": 1, - "opacity": 1, - "paddingRight": 0, - }, - ], - "defaultProps": undefined, - "displayName": "Styled(code)", - "render": [Function], - "withComponent": [Function], - } - } - PreTag={ - Object { - "$$typeof": Symbol(react.forward_ref), - "__emotion_base": "pre", - "__emotion_forwardProp": undefined, - "__emotion_real": [Circular], - "__emotion_styles": Array [ - [Function], - ], - "defaultProps": undefined, - "displayName": "Styled(pre)", - "render": [Function], - "withComponent": [Function], - } - } - language="jsx" - lineNumberContainerStyle={Object {}} - padded={true} - useInlineStyles={false} - > - <Styled(pre) - className="hljs" - padded={true} - > - <pre - className="hljs emotion-1" - > - <Styled(code)> - <code - className="emotion-0" - > - <span - className="token tag" - key="code-segement0" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - < - </span> - div - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - > - </span> - </span> - <span - className="token plain-text" - key="code-segement1" - > - - It's a TestComponent story: - - </span> - <span - className="token tag" - key="code-segement2" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - < - </span> - <span - className="token class-name" - key="code-segment-1-1" - > - TestComponent - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-2" - > - array - </span> - <span - className="token script language-javascript" - key="code-segment-1-3" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-2" - > - [ - </span> - - - <span - className="token number" - key="code-segment-1-4" - > - 1 - </span> - <span - className="token punctuation" - key="code-segment-1-5" - > - , - </span> - - - <span - className="token number" - key="code-segment-1-7" - > - 2 - </span> - <span - className="token punctuation" - key="code-segment-1-8" - > - , - </span> - - - <span - className="token number" - key="code-segment-1-10" - > - 3 - </span> - - - <span - className="token punctuation" - key="code-segment-1-12" - > - ] - </span> - <span - className="token punctuation" - key="code-segment-1-13" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-5" - > - bool - </span> - - - <span - className="token attr-name" - key="code-segment-1-7" - > - func - </span> - <span - className="token script language-javascript" - key="code-segment-1-8" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token keyword" - key="code-segment-1-2" - > - function - </span> - - <span - className="token function" - key="code-segment-1-4" - > - noRefCheck - </span> - <span - className="token punctuation" - key="code-segment-1-5" - > - ( - </span> - <span - className="token punctuation" - key="code-segment-1-6" - > - ) - </span> - - <span - className="token punctuation" - key="code-segment-1-8" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-9" - > - } - </span> - <span - className="token punctuation" - key="code-segment-1-10" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-10" - > - number - </span> - <span - className="token script language-javascript" - key="code-segment-1-11" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token number" - key="code-segment-1-2" - > - 7 - </span> - <span - className="token punctuation" - key="code-segment-1-3" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-13" - > - obj - </span> - <span - className="token script language-javascript" - key="code-segment-1-14" - > - <span - className="token script-punctuation punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - { - </span> - <span - className="token punctuation" - key="code-segment-1-2" - > - { - </span> - - a - <span - className="token punctuation" - key="code-segment-1-4" - > - : - </span> - - <span - className="token string" - key="code-segment-1-6" - > - 'a' - </span> - <span - className="token punctuation" - key="code-segment-1-7" - > - , - </span> - - b - <span - className="token punctuation" - key="code-segment-1-9" - > - : - </span> - - <span - className="token string" - key="code-segment-1-11" - > - 'b' - </span> - - - <span - className="token punctuation" - key="code-segment-1-13" - > - } - </span> - <span - className="token punctuation" - key="code-segment-1-14" - > - } - </span> - </span> - - - <span - className="token attr-name" - key="code-segment-1-16" - > - string - </span> - <span - className="token attr-value" - key="code-segment-1-17" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - = - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - " - </span> - seven - <span - className="token punctuation" - key="code-segment-1-3" - > - " - </span> - </span> - - - <span - className="token punctuation" - key="code-segment-1-19" - > - /> - </span> - </span> - <span - className="token plain-text" - key="code-segement3" - > - - - </span> - <span - className="token tag" - key="code-segement4" - > - <span - className="token tag" - key="code-segment-1-0" - > - <span - className="token punctuation" - key="code-segment-1-0" - > - </ - </span> - div - </span> - <span - className="token punctuation" - key="code-segment-1-1" - > - > - </span> - </span> - </code> - </Styled(code)> - </pre> - </Styled(pre)> - </SyntaxHighlighter> - </div> - </div> - </div> - </div> - <div - className="simplebar-placeholder" - /> - </div> - <div - className="simplebar-track simplebar-horizontal" - > - <div - className="simplebar-scrollbar" - /> - </div> - <div - className="simplebar-track simplebar-vertical" - > - <div - className="simplebar-scrollbar" - /> - </div> - </div> - </l> - </Component> - </Styled(Component)> - </ScrollArea> - </Component> - </Styled(Component)> - <ActionBar - actionItems={ - Array [ - Object { - "onClick": [Function], - "title": "Copy", - }, - ] - } - > - <Styled(div)> - <div - className="emotion-9" - > - <ActionButton - key="0" - onClick={[Function]} - > - <button - className="emotion-8" - onClick={[Function]} - > - Copy - </button> - </ActionButton> - </div> - </Styled(div)> - </ActionBar> - </div> - </Styled(div)> - </SyntaxHighlighter> - </ThemeProvider> - </Code> - </div> - </div> - </Story> -</Info> -`; diff --git a/addons/info/src/components/PropTable.js b/addons/info/src/components/PropTable.js deleted file mode 100644 index f2ad6453a5de..000000000000 --- a/addons/info/src/components/PropTable.js +++ /dev/null @@ -1,122 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -import PropVal from './PropVal'; -import PrettyPropType from './types/PrettyPropType'; - -const Table = props => <table {...props} />; -const Td = props => <td style={{ paddingRight: 10, verticalAlign: 'top' }} {...props} />; -const Tr = props => <tr {...props} />; -const Th = props => <th style={{ textAlign: 'left', verticalAlign: 'top' }} {...props} />; -const Tbody = props => <tbody {...props} />; -const Thead = props => <thead {...props} />; - -export const multiLineText = input => { - if (!input) { - return input; - } - const text = String(input); - const arrayOfText = text.split(/\r?\n|\r/g); - const isSingleLine = arrayOfText.length < 2; - return isSingleLine - ? text - : arrayOfText.map((lineOfText, i) => ( - // eslint-disable-next-line react/no-array-index-key - <span key={`${lineOfText}.${i}`}> - {i > 0 && <br />} {lineOfText} - </span> - )); -}; - -const determineIncludedPropTypes = (propDefinitions, excludedPropTypes) => { - if (excludedPropTypes.length === 0) { - return propDefinitions; - } - - return propDefinitions.filter( - propDefinition => !excludedPropTypes.includes(propDefinition.property) - ); -}; - -export default function PropTable(props) { - const { - type, - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - propDefinitions, - excludedPropTypes, - } = props; - - if (!type) { - return null; - } - - const includedPropDefinitions = determineIncludedPropTypes(propDefinitions, excludedPropTypes); - - if (!includedPropDefinitions.length) { - return <small>No propTypes defined!</small>; - } - - const propValProps = { - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - }; - - return ( - <Table> - <Thead> - <Tr> - <Th>property</Th> - <Th>propType</Th> - <Th>required</Th> - <Th>default</Th> - <Th>description</Th> - </Tr> - </Thead> - <Tbody> - {includedPropDefinitions.map(row => ( - <Tr key={row.property}> - <Td>{row.property}</Td> - <Td> - <PrettyPropType propType={row.propType} /> - </Td> - <Td>{row.required ? 'yes' : '-'}</Td> - <Td> - {row.defaultValue === undefined ? ( - '-' - ) : ( - <PropVal val={row.defaultValue} {...propValProps} valueStyles={{}} /> - )} - </Td> - <Td>{multiLineText(row.description)}</Td> - </Tr> - ))} - </Tbody> - </Table> - ); -} - -PropTable.displayName = 'PropTable'; -PropTable.defaultProps = { - type: null, - propDefinitions: [], - excludedPropTypes: [], -}; -PropTable.propTypes = { - type: PropTypes.func, - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, - excludedPropTypes: PropTypes.arrayOf(PropTypes.string), - propDefinitions: PropTypes.arrayOf( - PropTypes.shape({ - property: PropTypes.string.isRequired, - propType: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - required: PropTypes.bool, - description: PropTypes.string, - defaultValue: PropTypes.any, - }) - ), -}; diff --git a/addons/info/src/components/PropTable.test.js b/addons/info/src/components/PropTable.test.js deleted file mode 100644 index 43547f6b6eb7..000000000000 --- a/addons/info/src/components/PropTable.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { shallow } from 'enzyme'; - -import PropTable, { multiLineText } from './PropTable'; - -describe('PropTable', () => { - describe('multiLineText', () => { - const singleLine = 'Foo bar baz'; - const unixMultiLineText = 'foo \n bar \n baz'; - const windowsMultiLineText = 'foo \r bar \r baz'; - const duplicatedMultiLine = 'foo\nfoo\nfoo'; - const propDefinitions = [ - { - defaultValue: undefined, - description: '', - propType: { name: 'string' }, - property: 'foo', - required: false, - }, - ]; - const FooComponent = () => <div />; - const propTableProps = { - type: FooComponent, - maxPropArrayLength: 5, - maxPropObjectKeys: 5, - maxPropStringLength: 5, - propDefinitions, - }; - - it('should include all propTypes by default', () => { - const wrapper = shallow(<PropTable {...propTableProps} />); - expect(wrapper).toMatchSnapshot(); - }); - - it('should exclude excluded propTypes', () => { - const props = { ...propTableProps, excludedPropTypes: ['foo'] }; - const wrapper = shallow(<PropTable {...props} />); - expect(wrapper).toMatchSnapshot(); - }); - - it('should return a blank string for a null input', () => { - expect(multiLineText(null)).toBe(null); - }); - it('should return a blank string for an undefined input', () => { - expect(multiLineText(undefined)).toBe(undefined); - }); - it('should cast a number to a string', () => { - expect(multiLineText(1)).toBe('1'); - }); - it('should return its input for a single line of text', () => { - expect(multiLineText(singleLine)).toBe(singleLine); - }); - it('should return an array for unix multiline text', () => { - expect(multiLineText(unixMultiLineText)).toHaveLength(3); - }); - it('should return an array for windows multiline text', () => { - expect(multiLineText(windowsMultiLineText)).toHaveLength(3); - }); - it('should return an array with unique keys for duplicated multiline text', () => { - const wrappers = multiLineText(duplicatedMultiLine).map(tag => shallow(tag)); - const keys = wrappers.map(wrapper => wrapper.key()); - const deDup = new Set(keys); - expect(keys).toHaveLength(deDup.size); - }); - it('should have 2 br tags for 3 lines of text', () => { - const tree = renderer.create(multiLineText(unixMultiLineText)).toJSON(); - expect(tree).toMatchSnapshot(); - }); - }); -}); diff --git a/addons/info/src/components/PropTable/__snapshots__/index.test.js.snap b/addons/info/src/components/PropTable/__snapshots__/index.test.js.snap deleted file mode 100644 index d4aef4cc698a..000000000000 --- a/addons/info/src/components/PropTable/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,86 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable multiLineText should exclude excluded propTypes 1`] = ` -<small> - No propTypes defined! -</small> -`; - -exports[`PropTable multiLineText should have 2 br tags for 3 lines of text 1`] = ` -Array [ - <span> - - foo - </span>, - <span> - <br /> - - bar - </span>, - <span> - <br /> - - baz - </span>, -] -`; - -exports[`PropTable multiLineText should include all propTypes by default 1`] = ` -<Table> - <Thead> - <Tr> - <Th> - property - </Th> - <Th> - propType - </Th> - <Th> - required - </Th> - <Th> - default - </Th> - <Th> - description - </Th> - </Tr> - </Thead> - <Tbody> - <Tr - key="foo" - > - <Td - isMonospace={true} - > - foo - </Td> - <Td - isMonospace={true} - > - <PrettyPropType - depth={1} - propType={ - Object { - "name": "string", - } - } - /> - </Td> - <Td - isMonospace={false} - > - - - </Td> - <Td - isMonospace={false} - > - - - </Td> - <Td - isMonospace={false} - /> - </Tr> - </Tbody> -</Table> -`; diff --git a/addons/info/src/components/PropTable/components/Table.js b/addons/info/src/components/PropTable/components/Table.js deleted file mode 100644 index 893470b04ab3..000000000000 --- a/addons/info/src/components/PropTable/components/Table.js +++ /dev/null @@ -1,12 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import '../style.css'; - -const Table = ({ children }) => <table className="info-table">{children}</table>; - -Table.propTypes = { - children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]) - .isRequired, -}; - -export default Table; diff --git a/addons/info/src/components/PropTable/components/Table.test.js b/addons/info/src/components/PropTable/components/Table.test.js deleted file mode 100644 index 6910f2548e7c..000000000000 --- a/addons/info/src/components/PropTable/components/Table.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Table from './Table'; - -describe('PropTable/Table', () => { - it('renders a table html node with one child element', () => { - const wrapper = shallow( - <Table> - <div>foo bar</div> - </Table> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a table html node with multiple children elements', () => { - const wrapper = shallow( - <Table> - <div>foo bar</div> - <div>baz</div> - </Table> - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/Tbody.js b/addons/info/src/components/PropTable/components/Tbody.js deleted file mode 100644 index ef77ac517bef..000000000000 --- a/addons/info/src/components/PropTable/components/Tbody.js +++ /dev/null @@ -1,11 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const Tbody = ({ children }) => <tbody>{children}</tbody>; - -Tbody.propTypes = { - children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]) - .isRequired, -}; - -export default Tbody; diff --git a/addons/info/src/components/PropTable/components/Tbody.test.js b/addons/info/src/components/PropTable/components/Tbody.test.js deleted file mode 100644 index 901a7900ac59..000000000000 --- a/addons/info/src/components/PropTable/components/Tbody.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Tbody from './Tbody'; - -describe('PropTable/Tbody', () => { - it('renders a tbody html node with children', () => { - const wrapper = shallow( - <Tbody> - <div>foo bar</div> - </Tbody> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a tbody html node with multiple children elements', () => { - const wrapper = shallow( - <Tbody> - <div>foo bar</div> - <div>baz</div> - </Tbody> - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/Td.js b/addons/info/src/components/PropTable/components/Td.js deleted file mode 100644 index a429e6bc633e..000000000000 --- a/addons/info/src/components/PropTable/components/Td.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import '../style.css'; - -const Td = ({ isMonospace, children }) => ( - <td className={isMonospace ? 'info-table-monospace' : null}>{children}</td> -); - -Td.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.element, - PropTypes.arrayOf(PropTypes.node), - PropTypes.arrayOf(PropTypes.element), - ]), - isMonospace: PropTypes.bool, -}; - -Td.defaultProps = { - isMonospace: false, - children: null, -}; - -export default Td; diff --git a/addons/info/src/components/PropTable/components/Td.test.js b/addons/info/src/components/PropTable/components/Td.test.js deleted file mode 100644 index e5b8dc663253..000000000000 --- a/addons/info/src/components/PropTable/components/Td.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Td from './Td'; - -describe('PropTable/Td', () => { - it('renders a td html node child element', () => { - const wrapper = shallow( - <Td> - <div>foo bar</div> - </Td> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a monospace td html node child element', () => { - const wrapper = shallow( - <Td isMonospace> - <div>foo bar</div> - </Td> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a td html node with multiple children elements', () => { - const wrapper = shallow( - <Td> - <div>foo bar</div> - <div>baz</div> - </Td> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a monospace td html node with multiple children elements', () => { - const wrapper = shallow( - <Td isMonospace> - <div>foo bar</div> - <div>baz</div> - </Td> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a td html node with one child node', () => { - const wrapper = shallow(<Td>foo bar</Td>); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a monospace td html node with one child node', () => { - const wrapper = shallow(<Td isMonospace>foo bar</Td>); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/Th.js b/addons/info/src/components/PropTable/components/Th.js deleted file mode 100644 index acf43b6664e0..000000000000 --- a/addons/info/src/components/PropTable/components/Th.js +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const Th = ({ children }) => <th>{children}</th>; - -Th.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.element, - PropTypes.arrayOf(PropTypes.node), - PropTypes.arrayOf(PropTypes.element), - ]).isRequired, -}; - -export default Th; diff --git a/addons/info/src/components/PropTable/components/Th.test.js b/addons/info/src/components/PropTable/components/Th.test.js deleted file mode 100644 index cd84814d1721..000000000000 --- a/addons/info/src/components/PropTable/components/Th.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Th from './Th'; - -describe('PropTable/Th', () => { - it('renders a th html node with react element children', () => { - const wrapper = shallow( - <Th> - <div>foo bar</div> - <div>baz</div> - </Th> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a th html node with html node children', () => { - const wrapper = shallow(<Th>foo bar baz</Th>); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a th html node with one child node', () => { - const wrapper = shallow(<Th>foo bar</Th>); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/Thead.js b/addons/info/src/components/PropTable/components/Thead.js deleted file mode 100644 index 0abe57156f86..000000000000 --- a/addons/info/src/components/PropTable/components/Thead.js +++ /dev/null @@ -1,11 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const Thead = ({ children }) => <thead>{children}</thead>; - -Thead.propTypes = { - children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]) - .isRequired, -}; - -export default Thead; diff --git a/addons/info/src/components/PropTable/components/Thead.test.js b/addons/info/src/components/PropTable/components/Thead.test.js deleted file mode 100644 index d1a96fccd72c..000000000000 --- a/addons/info/src/components/PropTable/components/Thead.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Thead from './Thead'; - -describe('PropTable/Thead', () => { - it('renders a thead html node with children', () => { - const wrapper = shallow( - <Thead> - <div>foo bar</div> - </Thead> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a thead html node with multiple children elements', () => { - const wrapper = shallow( - <Thead> - <div>foo bar</div> - <div>baz</div> - </Thead> - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/Tr.js b/addons/info/src/components/PropTable/components/Tr.js deleted file mode 100644 index 103e8ad5449f..000000000000 --- a/addons/info/src/components/PropTable/components/Tr.js +++ /dev/null @@ -1,10 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const Tr = ({ children }) => <tr>{children}</tr>; - -Tr.propTypes = { - children: PropTypes.node.isRequired, -}; - -export default Tr; diff --git a/addons/info/src/components/PropTable/components/Tr.test.js b/addons/info/src/components/PropTable/components/Tr.test.js deleted file mode 100644 index 071e87001f25..000000000000 --- a/addons/info/src/components/PropTable/components/Tr.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Tr from './Tr'; -import Td from './Td'; - -describe('PropTable/Tr', () => { - it('renders a tr html node with react element children', () => { - const wrapper = shallow( - <Tr> - <Td>foo bar</Td> - <Td>baz</Td> - </Tr> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a tr html node with html node children', () => { - const wrapper = shallow(<Tr>foo bar baz</Tr>); - expect(wrapper).toMatchSnapshot(); - }); - - it('renders a tr html node with one child node', () => { - const wrapper = shallow(<Tr>foo bar</Tr>); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Table.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Table.test.js.snap deleted file mode 100644 index c245e98aca7a..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Table.test.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Table renders a table html node with multiple children elements 1`] = ` -<table - className="info-table" -> - <div> - foo bar - </div> - <div> - baz - </div> -</table> -`; - -exports[`PropTable/Table renders a table html node with one child element 1`] = ` -<table - className="info-table" -> - <div> - foo bar - </div> -</table> -`; diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Tbody.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Tbody.test.js.snap deleted file mode 100644 index 70840d14f0a8..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Tbody.test.js.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Tbody renders a tbody html node with children 1`] = ` -<tbody> - <div> - foo bar - </div> -</tbody> -`; - -exports[`PropTable/Tbody renders a tbody html node with multiple children elements 1`] = ` -<tbody> - <div> - foo bar - </div> - <div> - baz - </div> -</tbody> -`; diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Td.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Td.test.js.snap deleted file mode 100644 index 4e2d98f83b5f..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Td.test.js.snap +++ /dev/null @@ -1,63 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Td renders a monospace td html node child element 1`] = ` -<td - className="info-table-monospace" -> - <div> - foo bar - </div> -</td> -`; - -exports[`PropTable/Td renders a monospace td html node with multiple children elements 1`] = ` -<td - className="info-table-monospace" -> - <div> - foo bar - </div> - <div> - baz - </div> -</td> -`; - -exports[`PropTable/Td renders a monospace td html node with one child node 1`] = ` -<td - className="info-table-monospace" -> - foo bar -</td> -`; - -exports[`PropTable/Td renders a td html node child element 1`] = ` -<td - className={null} -> - <div> - foo bar - </div> -</td> -`; - -exports[`PropTable/Td renders a td html node with multiple children elements 1`] = ` -<td - className={null} -> - <div> - foo bar - </div> - <div> - baz - </div> -</td> -`; - -exports[`PropTable/Td renders a td html node with one child node 1`] = ` -<td - className={null} -> - foo bar -</td> -`; diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Th.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Th.test.js.snap deleted file mode 100644 index c7c55c291314..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Th.test.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Th renders a th html node with html node children 1`] = ` -<th> - foo bar baz -</th> -`; - -exports[`PropTable/Th renders a th html node with one child node 1`] = ` -<th> - foo bar -</th> -`; - -exports[`PropTable/Th renders a th html node with react element children 1`] = ` -<th> - <div> - foo bar - </div> - <div> - baz - </div> -</th> -`; diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Thead.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Thead.test.js.snap deleted file mode 100644 index a5ba8a5330f7..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Thead.test.js.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Thead renders a thead html node with children 1`] = ` -<thead> - <div> - foo bar - </div> -</thead> -`; - -exports[`PropTable/Thead renders a thead html node with multiple children elements 1`] = ` -<thead> - <div> - foo bar - </div> - <div> - baz - </div> -</thead> -`; diff --git a/addons/info/src/components/PropTable/components/__snapshots__/Tr.test.js.snap b/addons/info/src/components/PropTable/components/__snapshots__/Tr.test.js.snap deleted file mode 100644 index 9ba2d695e37b..000000000000 --- a/addons/info/src/components/PropTable/components/__snapshots__/Tr.test.js.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable/Tr renders a tr html node with html node children 1`] = ` -<tr> - foo bar baz -</tr> -`; - -exports[`PropTable/Tr renders a tr html node with one child node 1`] = ` -<tr> - foo bar -</tr> -`; - -exports[`PropTable/Tr renders a tr html node with react element children 1`] = ` -<tr> - <Td - isMonospace={false} - > - foo bar - </Td> - <Td - isMonospace={false} - > - baz - </Td> -</tr> -`; diff --git a/addons/info/src/components/PropTable/index.js b/addons/info/src/components/PropTable/index.js deleted file mode 100644 index 6e6a033943ee..000000000000 --- a/addons/info/src/components/PropTable/index.js +++ /dev/null @@ -1,121 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -import PrettyPropType from '../types/PrettyPropType'; -import PropVal from '../PropVal'; -import Table from './components/Table'; -import Tbody from './components/Tbody'; -import Td from './components/Td'; -import Th from './components/Th'; -import Thead from './components/Thead'; -import Tr from './components/Tr'; - -export const multiLineText = input => { - if (!input) { - return input; - } - const text = String(input); - const arrayOfText = text.split(/\r?\n|\r/g); - const isSingleLine = arrayOfText.length < 2; - return isSingleLine - ? text - : arrayOfText.map((lineOfText, i) => ( - // eslint-disable-next-line react/no-array-index-key - <span key={`${lineOfText}.${i}`}> - {i > 0 && <br />} {lineOfText} - </span> - )); -}; - -const determineIncludedPropTypes = (propDefinitions, excludedPropTypes) => { - if (excludedPropTypes.length === 0) { - return propDefinitions; - } - - return propDefinitions.filter( - propDefinition => !excludedPropTypes.includes(propDefinition.property) - ); -}; - -export default function PropTable(props) { - const { - type, - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - propDefinitions, - excludedPropTypes, - } = props; - - if (!type) { - return null; - } - - const includedPropDefinitions = determineIncludedPropTypes(propDefinitions, excludedPropTypes); - - if (!includedPropDefinitions.length) { - return <small>No propTypes defined!</small>; - } - - const propValProps = { - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - }; - - return ( - <Table> - <Thead> - <Tr> - <Th>property</Th> - <Th>propType</Th> - <Th>required</Th> - <Th>default</Th> - <Th>description</Th> - </Tr> - </Thead> - <Tbody> - {includedPropDefinitions.map(row => ( - <Tr key={row.property}> - <Td isMonospace>{row.property}</Td> - <Td isMonospace> - <PrettyPropType propType={row.propType} /> - </Td> - <Td>{row.required ? 'yes' : '-'}</Td> - <Td> - {row.defaultValue === undefined ? ( - '-' - ) : ( - <PropVal val={row.defaultValue} {...propValProps} valueStyles={{}} /> - )} - </Td> - <Td>{multiLineText(row.description)}</Td> - </Tr> - ))} - </Tbody> - </Table> - ); -} - -PropTable.displayName = 'PropTable'; -PropTable.defaultProps = { - type: null, - propDefinitions: [], - excludedPropTypes: [], -}; -PropTable.propTypes = { - type: PropTypes.func, - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, - excludedPropTypes: PropTypes.arrayOf(PropTypes.string), - propDefinitions: PropTypes.arrayOf( - PropTypes.shape({ - property: PropTypes.string.isRequired, - propType: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - required: PropTypes.bool, - description: PropTypes.string, - defaultValue: PropTypes.any, - }) - ), -}; diff --git a/addons/info/src/components/PropTable/index.test.js b/addons/info/src/components/PropTable/index.test.js deleted file mode 100644 index 0a6bb05eb1a7..000000000000 --- a/addons/info/src/components/PropTable/index.test.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; -import { shallow } from 'enzyme'; - -import PropTable, { multiLineText } from './index'; - -describe('PropTable', () => { - describe('multiLineText', () => { - const singleLine = 'Foo bar baz'; - const unixMultiLineText = 'foo \n bar \n baz'; - const windowsMultiLineText = 'foo \r bar \r baz'; - const duplicatedMultiLine = 'foo\nfoo\nfoo'; - const propDefinitions = [ - { - defaultValue: undefined, - description: '', - propType: { name: 'string' }, - property: 'foo', - required: false, - }, - ]; - const FooComponent = () => <div />; - const propTableProps = { - type: FooComponent, - maxPropArrayLength: 5, - maxPropObjectKeys: 5, - maxPropStringLength: 5, - propDefinitions, - }; - - it('should include all propTypes by default', () => { - const wrapper = shallow(<PropTable {...propTableProps} />); - expect(wrapper).toMatchSnapshot(); - }); - - it('should exclude excluded propTypes', () => { - const props = { ...propTableProps, excludedPropTypes: ['foo'] }; - const wrapper = shallow(<PropTable {...props} />); - expect(wrapper).toMatchSnapshot(); - }); - - it('should return a blank string for a null input', () => { - expect(multiLineText(null)).toBe(null); - }); - - it('should return a blank string for an undefined input', () => { - expect(multiLineText(undefined)).toBe(undefined); - }); - - it('should cast a number to a string', () => { - expect(multiLineText(1)).toBe('1'); - }); - - it('should return its input for a single line of text', () => { - expect(multiLineText(singleLine)).toBe(singleLine); - }); - - it('should return an array for unix multiline text', () => { - expect(multiLineText(unixMultiLineText)).toHaveLength(3); - }); - - it('should return an array for windows multiline text', () => { - expect(multiLineText(windowsMultiLineText)).toHaveLength(3); - }); - - it('should return an array with unique keys for duplicated multiline text', () => { - const wrappers = multiLineText(duplicatedMultiLine).map(tag => shallow(tag)); - const keys = wrappers.map(wrapper => wrapper.key()); - const deDup = new Set(keys); - expect(keys).toHaveLength(deDup.size); - }); - - it('should have 2 br tags for 3 lines of text', () => { - const tree = renderer.create(multiLineText(unixMultiLineText)).toJSON(); - expect(tree).toMatchSnapshot(); - }); - }); -}); diff --git a/addons/info/src/components/PropTable/style.css b/addons/info/src/components/PropTable/style.css deleted file mode 100644 index 010f200df8b4..000000000000 --- a/addons/info/src/components/PropTable/style.css +++ /dev/null @@ -1,19 +0,0 @@ -.info-table { - width: 100%; -} - -.info-table, .info-table td, .info-table th { - border-collapse: collapse; - border: 1px solid #cccccc; - color: #444444; - margin-top: 0.25rem; - padding-right: 0.5rem; - padding: 0.25rem; - text-align: left; - vertical-align: top; -} - -.info-table-monospace { - font-family: Menlo, Monaco, "Courier New", monospace; - font-size: 0.88em; -} diff --git a/addons/info/src/components/PropVal.js b/addons/info/src/components/PropVal.js deleted file mode 100644 index e7527d749566..000000000000 --- a/addons/info/src/components/PropVal.js +++ /dev/null @@ -1,274 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import createFragment from 'react-addons-create-fragment'; - -const getValueStyles = (codeColors = {}) => ({ - func: { - color: codeColors.func || '#170', - }, - - attr: { - color: codeColors.attr || '#666', - }, - - object: { - color: codeColors.object || '#666', - }, - - array: { - color: codeColors.array || '#666', - }, - - number: { - color: codeColors.number || '#a11', - }, - - string: { - color: codeColors.string || '#22a', - wordBreak: 'break-word', - }, - - bool: { - color: codeColors.bool || '#a11', - }, - - empty: { - color: '#444', - }, -}); - -function indent(breakIntoNewLines, level, isBlock) { - return ( - breakIntoNewLines && ( - <span> - <br /> - {`${Array(level).join(' ')} `} - {!isBlock && ' '} - </span> - ) - ); -} - -function PreviewArray({ - val, - level, - maxPropArrayLength, - maxPropStringLength, - maxPropsIntoLine, - valueStyles, -}) { - const items = {}; - const breakIntoNewLines = val.length > maxPropsIntoLine; - val.slice(0, maxPropArrayLength).forEach((item, i) => { - items[`n${i}`] = ( - <span> - {indent(breakIntoNewLines, level)} - <PropVal - val={item} - level={level + 1} - valueStyles={valueStyles} - maxPropStringLength={maxPropStringLength} - maxPropsIntoLine={maxPropsIntoLine} - /> - </span> - ); - items[`c${i}`] = ','; - }); - if (val.length > maxPropArrayLength) { - items.last = <span>{indent(breakIntoNewLines, level)}…</span>; - } else { - delete items[`c${val.length - 1}`]; - } - - return ( - <span style={valueStyles.array}> - [{createFragment(items)} - {indent(breakIntoNewLines, level, true)}] - </span> - ); -} - -PreviewArray.propTypes = { - val: PropTypes.any, // eslint-disable-line - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, - maxPropsIntoLine: PropTypes.number.isRequired, - level: PropTypes.number.isRequired, - valueStyles: PropTypes.shape({ - func: PropTypes.object, - attr: PropTypes.object, - object: PropTypes.object, - array: PropTypes.object, - number: PropTypes.object, - string: PropTypes.object, - bool: PropTypes.object, - empty: PropTypes.object, - }).isRequired, -}; - -function PreviewObject({ - val, - level, - maxPropObjectKeys, - maxPropStringLength, - maxPropsIntoLine, - valueStyles, -}) { - const names = Object.keys(val); - const items = {}; - const breakIntoNewLines = names.length > maxPropsIntoLine; - names.slice(0, maxPropObjectKeys).forEach((name, i) => { - items[`k${i}`] = ( - <span> - {indent(breakIntoNewLines, level)} - <span style={valueStyles.attr}>{name}</span> - </span> - ); - items[`c${i}`] = ': '; - items[`v${i}`] = ( - <PropVal - val={val[name]} - level={level + 1} - valueStyles={valueStyles} - maxPropStringLength={maxPropStringLength} - maxPropsIntoLine={maxPropsIntoLine} - /> - ); - items[`m${i}`] = ','; - }); - if (names.length > maxPropObjectKeys) { - items.rest = <span>{indent(breakIntoNewLines, level)}…</span>; - } else { - delete items[`m${names.length - 1}`]; - } - return ( - <span style={valueStyles.object}> - {'{'} - {createFragment(items)} - {indent(breakIntoNewLines, level, true)} - {'}'} - </span> - ); -} - -PreviewObject.propTypes = { - val: PropTypes.any, // eslint-disable-line - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, - maxPropsIntoLine: PropTypes.number.isRequired, - level: PropTypes.number.isRequired, - valueStyles: PropTypes.shape({ - func: PropTypes.object, - attr: PropTypes.object, - object: PropTypes.object, - array: PropTypes.object, - number: PropTypes.object, - string: PropTypes.object, - bool: PropTypes.object, - empty: PropTypes.object, - }).isRequired, -}; - -function PropVal(props) { - const { - level, - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - maxPropsIntoLine, - theme, - } = props; - let { val } = props; - const { codeColors } = theme || {}; - let content = null; - const valueStyles = props.valueStyles || getValueStyles(codeColors); - - if (typeof val === 'number') { - content = <span style={valueStyles.number}>{val}</span>; - } else if (typeof val === 'string') { - if (val.length > maxPropStringLength) { - val = `${val.slice(0, maxPropStringLength)}…`; - } - if (level > 1) { - val = `'${val}'`; - } - content = <span style={valueStyles.string}>{val}</span>; - } else if (typeof val === 'boolean') { - content = <span style={valueStyles.bool}>{`${val}`}</span>; - } else if (Array.isArray(val)) { - content = ( - <PreviewArray - {...{ - val, - level, - maxPropArrayLength, - maxPropStringLength, - maxPropsIntoLine, - valueStyles, - }} - /> - ); - } else if (typeof val === 'function') { - content = <span style={valueStyles.func}>{val.name || 'anonymous'}</span>; - } else if (!val) { - content = <span style={valueStyles.empty}>{`${val}`}</span>; - } else if (typeof val !== 'object') { - content = <span>…</span>; - } else if (React.isValidElement(val)) { - content = ( - <span style={valueStyles.object}> - {`<${val.type.displayName || val.type.name || val.type} />`} - </span> - ); - } else { - content = ( - <PreviewObject - {...{ - val, - level, - maxPropObjectKeys, - maxPropStringLength, - maxPropsIntoLine, - valueStyles, - }} - /> - ); - } - - return content; -} - -PropVal.defaultProps = { - val: null, - maxPropObjectKeys: 3, - maxPropArrayLength: 3, - maxPropStringLength: 50, - maxPropsIntoLine: 3, - level: 1, - theme: {}, - valueStyles: null, -}; - -PropVal.propTypes = { - val: PropTypes.any, // eslint-disable-line - maxPropObjectKeys: PropTypes.number, - maxPropArrayLength: PropTypes.number, - maxPropStringLength: PropTypes.number, - maxPropsIntoLine: PropTypes.number, - level: PropTypes.number, - theme: PropTypes.shape({ - codeColors: PropTypes.object, - }), - valueStyles: PropTypes.shape({ - func: PropTypes.object, - attr: PropTypes.object, - object: PropTypes.object, - array: PropTypes.object, - number: PropTypes.object, - string: PropTypes.object, - bool: PropTypes.object, - empty: PropTypes.object, - }), -}; - -export default PropVal; diff --git a/addons/info/src/components/Props.js b/addons/info/src/components/Props.js deleted file mode 100644 index 722c4aa8be28..000000000000 --- a/addons/info/src/components/Props.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import PropVal from './PropVal'; -import { getType } from '../react-utils'; - -const stylesheet = { - propStyle: {}, - propNameStyle: {}, - propValueStyle: {}, -}; - -export default function Props(props) { - const { - maxPropsIntoLine, - maxPropArrayLength, - maxPropObjectKeys, - maxPropStringLength, - node, - singleLine, - } = props; - const nodeProps = node.props; - const { defaultProps } = getType(node.type); - if (!nodeProps || typeof nodeProps !== 'object') { - return <span />; - } - - const { propValueStyle, propNameStyle } = stylesheet; - - const names = Object.keys(nodeProps).filter( - name => - name[0] !== '_' && - name !== 'children' && - (!defaultProps || nodeProps[name] !== defaultProps[name]) - ); - - const breakIntoNewLines = names.length > maxPropsIntoLine; - const endingSpace = singleLine ? ' ' : ''; - - const items = []; - names.forEach((name, i) => { - items.push( - <span key={name}> - {breakIntoNewLines ? ( - <span> - <br /> -    - </span> - ) : ( - ' ' - )} - <span style={propNameStyle}>{name}</span> - {/* Use implicit true: */} - {(!nodeProps[name] || typeof nodeProps[name] !== 'boolean') && ( - <span> - = - <span style={propValueStyle}> - {typeof nodeProps[name] === 'string' ? '"' : '{'} - <PropVal - val={nodeProps[name]} - maxPropObjectKeys={maxPropObjectKeys} - maxPropArrayLength={maxPropArrayLength} - maxPropStringLength={maxPropStringLength} - maxPropsIntoLine={maxPropsIntoLine} - /> - {typeof nodeProps[name] === 'string' ? '"' : '}'} - </span> - </span> - )} - - {i === names.length - 1 && (breakIntoNewLines ? <br /> : endingSpace)} - </span> - ); - }); - - return <span>{items}</span>; -} - -Props.defaultProps = { - singleLine: false, -}; - -Props.propTypes = { - node: PropTypes.node.isRequired, - singleLine: PropTypes.bool, - maxPropsIntoLine: PropTypes.number.isRequired, - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, -}; diff --git a/addons/info/src/components/Story.js b/addons/info/src/components/Story.js deleted file mode 100644 index 42f5026f0808..000000000000 --- a/addons/info/src/components/Story.js +++ /dev/null @@ -1,442 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ - -import React, { Fragment, Component, createElement } from 'react'; -import { isForwardRef } from 'react-is'; -import { polyfill } from 'react-lifecycles-compat'; -import PropTypes from 'prop-types'; -import global from 'global'; - -import marksy from 'marksy'; -import jsxToString from 'react-element-to-jsx-string'; -import { Code } from './markdown'; -import { getDisplayName, getType } from '../react-utils'; - -global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || []; -const { STORYBOOK_REACT_CLASSES } = global; - -const stylesheetBase = { - button: { - base: { - fontFamily: 'sans-serif', - fontSize: 12, - display: 'block', - position: 'fixed', - border: 'none', - background: '#027ac5', - color: '#fff', - padding: '5px 15px', - cursor: 'pointer', - }, - topRight: { - top: 0, - right: 0, - borderRadius: '0 0 0 5px', - }, - }, - info: { - position: 'fixed', - background: 'white', - top: 0, - left: 0, - height: '100vh', - width: '100vw', - overflow: 'auto', - zIndex: 99999, - }, - children: { - position: 'relative', - zIndex: 0, - }, - infoBody: { - fontFamily: 'Helvetica Neue, Helvetica, Segoe UI, Arial, freesans, sans-serif', - color: 'black', - fontWeight: 300, - lineHeight: 1.45, - fontSize: '15px', - padding: '20px 40px 40px', - borderRadius: '2px', - backgroundColor: '#fff', - }, - infoContent: { - marginBottom: 0, - }, - infoStory: {}, - jsxInfoContent: { - borderTop: '1px solid #eee', - margin: '20px 0 0 0', - }, - header: { - h1: { - margin: 0, - padding: 0, - fontSize: '35px', - }, - h2: { - margin: '0 0 10px 0', - padding: 0, - fontWeight: 400, - fontSize: '22px', - }, - h3: { - margin: '0 0 10px 0', - padding: 0, - fontWeight: 400, - fontSize: '18px', - }, - body: { - borderBottom: '1px solid #eee', - paddingTop: 10, - marginBottom: 10, - }, - }, - source: { - h1: { - margin: '20px 0 0 0', - padding: '0 0 5px 0', - fontSize: '25px', - borderBottom: '1px solid #EEE', - }, - }, - propTableHead: { - margin: '20px 0 0 0', - }, -}; - -class Story extends Component { - constructor(props, ...args) { - super(props, ...args); - this.state = { - open: false, - }; - this.marksy = marksy({ - createElement, - elements: props.components, - }); - } - - _renderStory() { - const { stylesheet } = this.state; - const { children } = this.props; - - return ( - <div id="story-root" style={stylesheet.infoStory}> - {children} - </div> - ); - } - - _renderInline() { - const { stylesheet } = this.state; - - return ( - <Fragment> - {this._renderInlineHeader()} - {this._renderStory()} - <div style={stylesheet.infoPage}> - <div style={stylesheet.infoBody}> - {this._getInfoContent()} - {this._getComponentDescription()} - {this._getSourceCode()} - {this._getPropTables()} - </div> - </div> - </Fragment> - ); - } - - _renderInlineHeader() { - const { stylesheet } = this.state; - - const infoHeader = this._getInfoHeader(); - - return ( - infoHeader && ( - <div style={stylesheet.infoPage}> - <div style={stylesheet.infoBody}>{infoHeader}</div> - </div> - ) - ); - } - - _renderOverlay() { - const { stylesheet, open } = this.state; - const { children } = this.props; - - const buttonStyle = { - ...stylesheet.button.base, - ...stylesheet.button.topRight, - }; - - const infoStyle = { ...stylesheet.info }; - if (!open) { - infoStyle.display = 'none'; - } - - const openOverlay = () => { - this.setState({ open: true }); - return false; - }; - - const closeOverlay = () => { - this.setState({ open: false }); - return false; - }; - - return ( - <Fragment> - <div style={stylesheet.children}>{children}</div> - <button - type="button" - style={buttonStyle} - onClick={openOverlay} - className="info__show-button" - > - Show Info - </button> - {open ? ( - <div style={infoStyle} className="info__overlay"> - <button - type="button" - style={buttonStyle} - onClick={closeOverlay} - className="info__close-button" - > - × - </button> - <div style={stylesheet.infoPage}> - <div style={stylesheet.infoBody}> - {this._getInfoHeader()} - {this._getInfoContent()} - {this._getComponentDescription()} - {this._getSourceCode()} - {this._getPropTables()} - </div> - </div> - </div> - ) : null} - </Fragment> - ); - } - - _getInfoHeader() { - const { stylesheet } = this.state; - const { context, showHeader } = this.props; - - if (!context || !showHeader) { - return null; - } - - return ( - <div style={stylesheet.header.body}> - <h1 style={stylesheet.header.h1}>{context.kind}</h1> - <h2 style={stylesheet.header.h2}>{context.name}</h2> - </div> - ); - } - - _getInfoContent() { - const { info, showInline } = this.props; - const { stylesheet } = this.state; - - if (!info) { - return ''; - } - - if (React.isValidElement(info)) { - return ( - <div style={showInline ? stylesheet.jsxInfoContent : stylesheet.infoContent}>{info}</div> - ); - } - - const lines = info.split('\n'); - while (lines[0].trim() === '') { - lines.shift(); - } - let padding = 0; - const matches = lines[0].match(/^ */); - if (matches) { - padding = matches[0].length; - } - const source = lines.map(s => s.slice(padding)).join('\n'); - - return <Fragment>{this.marksy(source).tree}</Fragment>; - } - - _getComponentDescription() { - const { - context: { kind, name }, - } = this.props; - let retDiv = null; - - const validMatches = [kind, name]; - - if (Object.keys(STORYBOOK_REACT_CLASSES).length) { - Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => { - if (validMatches.includes(STORYBOOK_REACT_CLASSES[key].name)) { - const componentDescription = STORYBOOK_REACT_CLASSES[key].docgenInfo.description; - retDiv = <Fragment>{this.marksy(componentDescription).tree}</Fragment>; - } - }); - } - - return retDiv; - } - - _getSourceCode() { - const { showSource, children } = this.props; - const { stylesheet } = this.state; - - if (!showSource) { - return null; - } - - return ( - <Fragment> - <h1 style={stylesheet.source.h1}>Story Source</h1> - <Code code={jsxToString(children)} language="jsx" format={false} /> - </Fragment> - ); - } - - _getPropTables() { - const { - children, - propTablesExclude, - propTableCompare, - maxPropObjectKeys, - maxPropArrayLength, - maxPropStringLength, - excludedPropTypes, - } = this.props; - let { propTables } = this.props; - const { stylesheet } = this.state; - const types = new Map(); - - if (!propTables) { - return null; - } - - if (!children) { - return null; - } - - propTables.forEach(type => { - types.set(type, true); - }); - - // depth-first traverse and collect types - const extract = innerChildren => { - if (!innerChildren) { - return; - } - if (Array.isArray(innerChildren)) { - innerChildren.forEach(extract); - return; - } - if (innerChildren.props && innerChildren.props.children) { - extract(innerChildren.props.children); - } - if (isForwardRef(innerChildren)) { - try { - // this might fail because of hooks being used - extract(innerChildren.type.render(innerChildren.props)); - } catch (e) { - // do nothing - } - } - if ( - typeof innerChildren === 'string' || - typeof innerChildren.type === 'string' || - (propTables.length > 0 && // if propTables is set and has items in it - !propTables.includes(innerChildren.type)) || // ignore types that are missing from propTables - (Array.isArray(propTablesExclude) && // also ignore excluded types - propTablesExclude.some(Comp => propTableCompare(innerChildren, Comp))) - ) { - return; - } - - if (innerChildren.type && !types.has(innerChildren.type)) { - types.set(innerChildren.type, true); - } - }; - - // extract components from children - extract(children); - - const array = Array.from(types.keys()); - array.sort((a, b) => (getDisplayName(a) > getDisplayName(b) ? 1 : -1)); - - propTables = array.map((type, i) => ( - // eslint-disable-next-line react/no-array-index-key - <div key={`${getDisplayName(type)}_${i}`}> - <h3 style={stylesheet.propTableHead}>"{getDisplayName(type)}" Component</h3> - <this.props.PropTable - type={getType(type)} - maxPropObjectKeys={maxPropObjectKeys} - maxPropArrayLength={maxPropArrayLength} - maxPropStringLength={maxPropStringLength} - excludedPropTypes={excludedPropTypes} - /> - </div> - )); - - if (!propTables || propTables.length === 0) { - return null; - } - - return ( - <Fragment> - <h1 style={stylesheet.source.h1}>Prop Types</h1> - {propTables} - </Fragment> - ); - } - - render() { - const { showInline } = this.props; - return showInline ? this._renderInline() : this._renderOverlay(); - } -} - -Story.getDerivedStateFromProps = ({ styles }) => ({ stylesheet: styles(stylesheetBase) }); - -Story.displayName = 'Story'; - -Story.propTypes = { - context: PropTypes.shape({ - kind: PropTypes.string, - name: PropTypes.string, - }), - info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - propTables: PropTypes.arrayOf(PropTypes.func), - propTablesExclude: PropTypes.arrayOf(PropTypes.func), - propTableCompare: PropTypes.func.isRequired, - showInline: PropTypes.bool, - showHeader: PropTypes.bool, - showSource: PropTypes.bool, - // eslint-disable-next-line react/no-unused-prop-types - styles: PropTypes.func.isRequired, - children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), - components: PropTypes.shape({}), - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, - excludedPropTypes: PropTypes.arrayOf(PropTypes.string), -}; - -Story.defaultProps = { - context: null, - info: '', - children: null, - propTables: null, - propTablesExclude: [], - showInline: false, - showHeader: true, - showSource: true, - components: {}, - excludedPropTypes: [], -}; - -polyfill(Story); - -export default Story; diff --git a/addons/info/src/components/__snapshots__/PropTable.test.js.snap b/addons/info/src/components/__snapshots__/PropTable.test.js.snap deleted file mode 100644 index d153afdfd080..000000000000 --- a/addons/info/src/components/__snapshots__/PropTable.test.js.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PropTable multiLineText should exclude excluded propTypes 1`] = ` -<small> - No propTypes defined! -</small> -`; - -exports[`PropTable multiLineText should have 2 br tags for 3 lines of text 1`] = ` -Array [ - <span> - - foo - </span>, - <span> - <br /> - - bar - </span>, - <span> - <br /> - - baz - </span>, -] -`; - -exports[`PropTable multiLineText should include all propTypes by default 1`] = ` -<Table> - <Thead> - <Tr> - <Th> - property - </Th> - <Th> - propType - </Th> - <Th> - required - </Th> - <Th> - default - </Th> - <Th> - description - </Th> - </Tr> - </Thead> - <Tbody> - <Tr - key="foo" - > - <Td> - foo - </Td> - <Td> - <PrettyPropType - depth={1} - propType={ - Object { - "name": "string", - } - } - /> - </Td> - <Td> - - - </Td> - <Td> - - - </Td> - <Td /> - </Tr> - </Tbody> -</Table> -`; diff --git a/addons/info/src/components/makeTableComponent.js b/addons/info/src/components/makeTableComponent.js deleted file mode 100644 index 8842bcbcaaef..000000000000 --- a/addons/info/src/components/makeTableComponent.js +++ /dev/null @@ -1,98 +0,0 @@ -/* eslint-disable no-underscore-dangle,react/forbid-foreign-prop-types */ -import PropTypes from 'prop-types'; -import React from 'react'; - -const PropTypesMap = new Map(); - -Object.keys(PropTypes).forEach(typeName => { - const type = PropTypes[typeName]; - - PropTypesMap.set(type, typeName); - PropTypesMap.set(type.isRequired, typeName); -}); - -const isNotEmpty = obj => obj && obj.props && Object.keys(obj.props).length > 0; - -const hasDocgen = type => isNotEmpty(type.__docgenInfo); - -const propsFromDocgen = type => { - const props = {}; - const docgenInfoProps = type.__docgenInfo.props; - - Object.keys(docgenInfoProps).forEach(property => { - const docgenInfoProp = docgenInfoProps[property]; - const defaultValueDesc = docgenInfoProp.defaultValue || {}; - const propType = docgenInfoProp.flowType || docgenInfoProp.type || 'other'; - - props[property] = { - property, - propType, - required: docgenInfoProp.required, - description: docgenInfoProp.description, - defaultValue: defaultValueDesc.value, - }; - }); - - return props; -}; - -const propsFromPropTypes = type => { - const props = {}; - - if (type.propTypes) { - Object.keys(type.propTypes).forEach(property => { - const typeInfo = type.propTypes[property]; - const required = typeInfo.isRequired === undefined; - const docgenInfo = - type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property]; - const description = docgenInfo ? docgenInfo.description : null; - let propType = PropTypesMap.get(typeInfo) || 'other'; - - if (propType === 'other') { - if (docgenInfo && docgenInfo.type) { - propType = docgenInfo.type.name; - } - } - - props[property] = { property, propType, required, description }; - }); - } - - if (type.defaultProps) { - Object.keys(type.defaultProps).forEach(property => { - const value = type.defaultProps[property]; - - if (value === undefined) { - return; - } - - if (!props[property]) { - props[property] = { property }; - } - - props[property].defaultValue = value; - }); - } - - return props; -}; - -export default function makeTableComponent(Component) { - const TableComponent = props => { - const { type } = props; - if (!type) { - return null; - } - - const propDefinitionsMap = hasDocgen(type) ? propsFromDocgen(type) : propsFromPropTypes(type); - const propDefinitions = Object.values(propDefinitionsMap); - - return <Component propDefinitions={propDefinitions} {...props} />; - }; - - TableComponent.propTypes = { - type: PropTypes.func.isRequired, - }; - - return TableComponent; -} diff --git a/addons/info/src/components/markdown/code.js b/addons/info/src/components/markdown/code.js deleted file mode 100644 index 4e6b24ba7de2..000000000000 --- a/addons/info/src/components/markdown/code.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { SyntaxHighlighter } from '@storybook/components'; -import { ThemeProvider, convert } from '@storybook/theming'; - -const Code = ({ code, language = 'plaintext', ...rest }) => ( - <ThemeProvider theme={convert()}> - <SyntaxHighlighter bordered copyable format={false} language={language} {...rest}> - {code} - </SyntaxHighlighter> - </ThemeProvider> -); -Code.propTypes = { - language: PropTypes.string.isRequired, - code: PropTypes.string.isRequired, -}; - -export { Code }; - -export function Blockquote({ children }) { - const style = { - fontSize: '1.88em', - fontFamily: 'Menlo, Monaco, "Courier New", monospace', - borderLeft: '8px solid #fafafa', - padding: '1rem', - }; - return <blockquote style={style}>{children}</blockquote>; -} - -Blockquote.propTypes = { children: PropTypes.node }; -Blockquote.defaultProps = { children: null }; - -export { default as Pre } from './pre/pre'; diff --git a/addons/info/src/components/markdown/htags.js b/addons/info/src/components/markdown/htags.js deleted file mode 100644 index 57da398d6422..000000000000 --- a/addons/info/src/components/markdown/htags.js +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const defaultProps = { - children: null, - id: null, -}; -const propTypes = { - children: PropTypes.node, - id: PropTypes.string, -}; - -export function H1({ id, children }) { - const styles = { - borderBottom: '1px solid #eee', - fontWeight: 600, - margin: 0, - padding: 0, - fontSize: '40px', - }; - return ( - <h1 id={id} style={styles}> - {children} - </h1> - ); -} - -H1.defaultProps = defaultProps; -H1.propTypes = propTypes; - -export function H2({ id, children }) { - const styles = { - fontWeight: 600, - margin: 0, - padding: 0, - fontSize: '30px', - }; - return ( - <h2 id={id} style={styles}> - {children} - </h2> - ); -} - -H2.defaultProps = defaultProps; -H2.propTypes = propTypes; - -export function H3({ id, children }) { - const styles = { - fontWeight: 600, - margin: 0, - padding: 0, - fontSize: '22px', - textTransform: 'uppercase', - }; - return ( - <h3 id={id} style={styles}> - {children} - </h3> - ); -} - -H3.defaultProps = defaultProps; -H3.propTypes = propTypes; - -export function H4({ id, children }) { - const styles = { - fontWeight: 600, - margin: 0, - padding: 0, - fontSize: '20px', - }; - return ( - <h4 id={id} style={styles}> - {children} - </h4> - ); -} - -H4.defaultProps = defaultProps; -H4.propTypes = propTypes; - -export function H5({ id, children }) { - const styles = { - fontWeight: 600, - margin: 0, - padding: 0, - fontSize: '18px', - }; - return ( - <h5 id={id} style={styles}> - {children} - </h5> - ); -} - -H5.defaultProps = defaultProps; -H5.propTypes = propTypes; - -export function H6({ id, children }) { - const styles = { - fontWeight: 400, - margin: 0, - padding: 0, - fontSize: '18px', - }; - return ( - <h6 id={id} style={styles}> - {children} - </h6> - ); -} - -H6.defaultProps = defaultProps; -H6.propTypes = propTypes; diff --git a/addons/info/src/components/markdown/index.js b/addons/info/src/components/markdown/index.js deleted file mode 100644 index 2c9ff6ea3064..000000000000 --- a/addons/info/src/components/markdown/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { H1, H2, H3, H4, H5, H6 } from './htags'; -export { Code, Pre } from './code'; -export { P, A, LI, UL } from './text'; diff --git a/addons/info/src/components/markdown/pre/copy.js b/addons/info/src/components/markdown/pre/copy.js deleted file mode 100644 index 8142b089573e..000000000000 --- a/addons/info/src/components/markdown/pre/copy.js +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable no-undef */ -export default function copy(str) { - const tmp = document.createElement('TEXTAREA'); - const focus = document.activeElement; - - tmp.value = str; - - document.body.appendChild(tmp); - tmp.select(); - document.execCommand('copy'); - document.body.removeChild(tmp); - focus.focus(); -} diff --git a/addons/info/src/components/markdown/pre/copyButton.js b/addons/info/src/components/markdown/pre/copyButton.js deleted file mode 100644 index cf44fa8c3b53..000000000000 --- a/addons/info/src/components/markdown/pre/copyButton.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -function CopyButton({ onClick, toggled }) { - const toggleText = 'Copied!'; - const text = 'Copy'; - - return ( - <button - type="button" - onClick={onClick} - style={{ - backgroundColor: 'rgb(255, 255, 255)', - cursor: 'pointer', - fontSize: '13px', - alignSelf: 'flex-start', - flexShrink: '0', - overflow: 'hidden', - borderWidth: 1, - borderStyle: 'solid', - borderColor: 'rgb(238, 238, 238)', - borderImage: 'initial', - borderRadius: 3, - padding: '3px 10px', - }} - > - {toggled ? toggleText : text} - </button> - ); -} - -CopyButton.propTypes = { - onClick: PropTypes.func, - toggled: PropTypes.bool, -}; - -CopyButton.defaultProps = { - onClick: () => {}, - toggled: false, -}; - -export default CopyButton; diff --git a/addons/info/src/components/markdown/pre/pre.js b/addons/info/src/components/markdown/pre/pre.js deleted file mode 100644 index 160b2c09a964..000000000000 --- a/addons/info/src/components/markdown/pre/pre.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import CopyButton from './copyButton'; -import copy from './copy'; - -const TOGGLE_TIMEOUT = 1800; - -class Pre extends React.Component { - state = { - copied: false, - }; - - setRef = elem => { - this.pre = elem; - }; - - handleClick = () => { - const text = this.pre && this.pre.innerText; - - if (!text) { - return; - } - - copy(text); - this.setState({ copied: true }); - - clearTimeout(this.timeout); - - this.timeout = setTimeout(() => { - this.setState({ copied: false }); - }, TOGGLE_TIMEOUT); - }; - - render() { - const { theme, children } = this.props; - const { pre } = theme; - const { copied } = this.state; - - return ( - <pre - style={{ - ...{ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - fontSize: '.88em', - fontFamily: 'Menlo, Monaco, "Courier New", monospace', - backgroundColor: '#fafafa', - padding: '.5rem', - lineHeight: 1.5, - overflowX: 'scroll', - }, - ...pre, - }} - > - <div ref={this.setRef}>{children}</div> - <CopyButton onClick={this.handleClick} toggled={copied} /> - </pre> - ); - } -} - -Pre.propTypes = { - children: PropTypes.node, - theme: PropTypes.shape({ - pre: PropTypes.object, - }), -}; - -Pre.defaultProps = { - children: null, - theme: {}, -}; - -export default Pre; diff --git a/addons/info/src/components/markdown/text.js b/addons/info/src/components/markdown/text.js deleted file mode 100644 index 356a4575cd5e..000000000000 --- a/addons/info/src/components/markdown/text.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const defaultProps = { children: null }; -const propTypes = { children: PropTypes.node }; - -export function P({ children }) { - return <p>{children}</p>; -} - -P.defaultProps = defaultProps; -P.propTypes = propTypes; - -export function LI({ children }) { - return <li>{children}</li>; -} - -LI.defaultProps = defaultProps; -LI.propTypes = propTypes; - -export function UL({ children }) { - return <ul>{children}</ul>; -} - -UL.defaultProps = defaultProps; -UL.propTypes = propTypes; - -export function A({ href, children }) { - const style = { - color: '#3498db', - }; - return ( - <a href={href} target="_blank" rel="noopener noreferrer" style={style}> - {children} - </a> - ); -} - -A.defaultProps = defaultProps; -A.propTypes = { children: PropTypes.node, href: PropTypes.string.isRequired }; diff --git a/addons/info/src/components/types/ArrayOf.js b/addons/info/src/components/types/ArrayOf.js deleted file mode 100644 index 28f1dffe8156..000000000000 --- a/addons/info/src/components/types/ArrayOf.js +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable import/no-cycle */ -import React from 'react'; - -import PrettyPropType from './PrettyPropType'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const ArrayOf = ({ propType }) => ( - <span> - <span>[</span> - <span> - <PrettyPropType propType={getPropTypes(propType)} /> - </span> - <span>]</span> - </span> -); - -ArrayOf.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default ArrayOf; diff --git a/addons/info/src/components/types/Enum.js b/addons/info/src/components/types/Enum.js deleted file mode 100644 index 2de8d4830a87..000000000000 --- a/addons/info/src/components/types/Enum.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const Enum = ({ propType }) => ( - <span> - {getPropTypes(propType) - .map(({ value }) => value) - .join(' | ')} - </span> -); - -Enum.propTypes = { - propType: TypeInfo.isRequired, -}; diff --git a/addons/info/src/components/types/InstanceOf.js b/addons/info/src/components/types/InstanceOf.js deleted file mode 100644 index 786c77fdd399..000000000000 --- a/addons/info/src/components/types/InstanceOf.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const InstanceOf = ({ propType }) => <span>{getPropTypes(propType)}</span>; - -InstanceOf.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default InstanceOf; diff --git a/addons/info/src/components/types/Literal.js b/addons/info/src/components/types/Literal.js deleted file mode 100644 index 83b222e40788..000000000000 --- a/addons/info/src/components/types/Literal.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { TypeInfo } from './proptypes'; - -const Literal = ({ propType }) => <span>{propType.value}</span>; - -Literal.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default Literal; diff --git a/addons/info/src/components/types/ObjectOf.js b/addons/info/src/components/types/ObjectOf.js deleted file mode 100644 index 48a56d91026f..000000000000 --- a/addons/info/src/components/types/ObjectOf.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable import/no-cycle */ -import React from 'react'; - -import PrettyPropType from './PrettyPropType'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const ObjectOf = ({ propType }) => ( - <span> - {'{[<key>]: '} - <PrettyPropType propType={getPropTypes(propType)} /> - {'}'} - </span> -); - -ObjectOf.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default ObjectOf; diff --git a/addons/info/src/components/types/OneOf.js b/addons/info/src/components/types/OneOf.js deleted file mode 100644 index deacfa4633e8..000000000000 --- a/addons/info/src/components/types/OneOf.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const joinValues = propTypes => propTypes.map(({ value }) => value).join(' | '); - -const OneOf = ({ propType }) => { - const propTypes = getPropTypes(propType); - return <span>{`oneOf ${Array.isArray(propTypes) ? joinValues(propTypes) : propTypes}`}</span>; -}; - -OneOf.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default OneOf; diff --git a/addons/info/src/components/types/OneOfType.js b/addons/info/src/components/types/OneOfType.js deleted file mode 100644 index 2b269d0dad68..000000000000 --- a/addons/info/src/components/types/OneOfType.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable import/no-cycle */ -import React from 'react'; - -import PrettyPropType from './PrettyPropType'; -import { TypeInfo, getPropTypes } from './proptypes'; - -const OneOfType = ({ propType }) => { - const propTypes = getPropTypes(propType); - return ( - <span> - {propTypes - .map((value, i) => { - const key = `${value.name}${value.value ? `-${value.value}` : ''}`; - return [ - <PrettyPropType key={key} propType={value} />, - i < propTypes.length - 1 ? <span key={`${key}-separator`}> | </span> : null, - ]; - }) - .reduce((acc, tuple) => acc.concat(tuple), [])} - </span> - ); -}; -OneOfType.propTypes = { - propType: TypeInfo.isRequired, -}; -export default OneOfType; diff --git a/addons/info/src/components/types/PrettyPropType.js b/addons/info/src/components/types/PrettyPropType.js deleted file mode 100644 index ff7ea413614b..000000000000 --- a/addons/info/src/components/types/PrettyPropType.js +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable import/no-cycle */ -import PropTypes from 'prop-types'; -import React from 'react'; - -import Shape from './Shape'; -import OneOfType from './OneOfType'; -import ArrayOf from './ArrayOf'; -import ObjectOf from './ObjectOf'; -import OneOf from './OneOf'; -import InstanceOf from './InstanceOf'; -import Signature from './Signature'; -import Literal from './Literal'; - -import { TypeInfo } from './proptypes'; - -// propType -> Component map - these are a bit more complex prop types to display -const propTypeComponentMap = new Map([ - ['shape', Shape], - ['union', OneOfType], - ['arrayOf', ArrayOf], - ['objectOf', ObjectOf], - // Might be overkill to have below proptypes as separate components *shrug* - ['literal', Literal], - ['enum', OneOf], - ['instanceOf', InstanceOf], - ['signature', Signature], -]); - -const PrettyPropType = props => { - const { propType, depth } = props; - if (!propType) { - return <span>unknown</span>; - } - - if (propTypeComponentMap.has(propType.name)) { - const Component = propTypeComponentMap.get(propType.name); - return <Component propType={propType} depth={depth} />; - } - - // Otherwise, propType does not have a dedicated component, display proptype name by default - return <span>{propType.name || propType}</span>; -}; - -PrettyPropType.displayName = 'PrettyPropType'; - -PrettyPropType.defaultProps = { - propType: null, - depth: 1, -}; - -PrettyPropType.propTypes = { - propType: TypeInfo, - depth: PropTypes.number, -}; - -export default PrettyPropType; diff --git a/addons/info/src/components/types/PropertyLabel.js b/addons/info/src/components/types/PropertyLabel.js deleted file mode 100644 index d5ad3d896fab..000000000000 --- a/addons/info/src/components/types/PropertyLabel.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const styles = { - hasProperty: { - whiteSpace: 'nowrap', - }, -}; - -const PropertyLabel = ({ property, required }) => { - if (!property) return null; - - return ( - <span style={styles.hasProperty}> - {property} - {required ? '' : '?'}:  - </span> - ); -}; - -PropertyLabel.propTypes = { - property: PropTypes.string, - required: PropTypes.bool, -}; - -PropertyLabel.defaultProps = { - property: '', - required: false, -}; - -export default PropertyLabel; diff --git a/addons/info/src/components/types/Shape.js b/addons/info/src/components/types/Shape.js deleted file mode 100644 index a9f1b6c5affc..000000000000 --- a/addons/info/src/components/types/Shape.js +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable import/no-cycle */ -import PropTypes from 'prop-types'; -import React from 'react'; - -import PrettyPropType from './PrettyPropType'; -import PropertyLabel from './PropertyLabel'; - -import { TypeInfo, getPropTypes } from './proptypes'; - -const MARGIN_SIZE = 15; - -const HighlightButton = props => ( - <button - type="button" - {...props} - style={{ - display: 'inline-block', - background: 'none', - border: '0 none', - color: 'gray', - cursor: 'pointer', - }} - /> -); - -class Shape extends React.Component { - constructor(props) { - super(props); - this.state = { - minimized: false, - }; - } - - handleToggle = () => { - const { minimized } = this.state; - this.setState({ - minimized: !minimized, - }); - }; - - render() { - const { propType, depth } = this.props; - const { minimized } = this.state; - - const propTypes = getPropTypes(propType); - return ( - <span> - <HighlightButton onClick={this.handleToggle}>{'{'}</HighlightButton> - <HighlightButton onClick={this.handleToggle}>...</HighlightButton> - {!minimized && - Object.keys(propTypes).map(childProperty => ( - <div key={childProperty} style={{ marginLeft: depth * MARGIN_SIZE }}> - <PropertyLabel - property={childProperty} - required={propTypes[childProperty].required} - /> - <PrettyPropType depth={depth + 1} propType={propTypes[childProperty]} />, - </div> - ))} - - <HighlightButton onClick={this.handleToggle}>{'}'}</HighlightButton> - </span> - ); - } -} - -Shape.propTypes = { - propType: TypeInfo, - depth: PropTypes.number.isRequired, -}; - -Shape.defaultProps = { - propType: null, -}; - -export default Shape; diff --git a/addons/info/src/components/types/Signature.js b/addons/info/src/components/types/Signature.js deleted file mode 100644 index 3e0de0fb570d..000000000000 --- a/addons/info/src/components/types/Signature.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { TypeInfo } from './proptypes'; - -const Signature = ({ propType }) => <span>{propType.raw}</span>; - -Signature.propTypes = { - propType: TypeInfo.isRequired, -}; - -export default Signature; diff --git a/addons/info/src/components/types/proptypes.js b/addons/info/src/components/types/proptypes.js deleted file mode 100644 index ae5e28f942d1..000000000000 --- a/addons/info/src/components/types/proptypes.js +++ /dev/null @@ -1,12 +0,0 @@ -import PropTypes, { oneOfType } from 'prop-types'; - -export const TypeInfo = oneOfType([ - PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.any, - }), - PropTypes.string, -]); - -export const getPropTypes = propType => - typeof propType === 'string' ? propType : propType.value || propType.elements; diff --git a/addons/info/src/index.js b/addons/info/src/index.js deleted file mode 100644 index 32223bf6693d..000000000000 --- a/addons/info/src/index.js +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import nestedObjectAssign from 'nested-object-assign'; -import deprecate from 'util-deprecate'; -import { makeDecorator } from '@storybook/addons'; -import { logger } from '@storybook/client-logger'; -import Story from './components/Story'; -import PropTable from './components/PropTable/index'; -import makeTableComponent from './components/makeTableComponent'; -import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown'; - -const defaultOptions = { - inline: false, - header: true, - source: true, - propTables: [], - propTableCompare: (element, Component) => - // https://github.com/gaearon/react-hot-loader#checking-element-types - typeof reactHotLoaderGlobal === 'undefined' - ? element.type === Component - : // eslint-disable-next-line no-undef - reactHotLoaderGlobal.areComponentsEqual(element.type, Component), - TableComponent: PropTable, - maxPropsIntoLine: 3, - maxPropObjectKeys: 3, - maxPropArrayLength: 3, - maxPropStringLength: 50, -}; - -const defaultComponents = { - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - code: Code, - p: P, - a: A, - li: LI, - ul: UL, -}; - -let hasWarned = false; - -function addInfo(storyFn, context, infoOptions) { - const options = { - ...defaultOptions, - ...infoOptions, - }; - - // props.propTables can only be either an array of components or null - // propTables option is allowed to be set to 'false' (a boolean) - // if the option is false, replace it with null to avoid react warnings - if (!options.propTables) { - options.propTables = null; - } - - const components = { ...defaultComponents }; - if (options && options.components) { - Object.assign(components, options.components); - } - if (options && options.marksyConf) { - if (!hasWarned) { - logger.warn('@storybook/addon-info: "marksyConf" option has been renamed to "components"'); - hasWarned = true; - } - - Object.assign(components, options.marksyConf); - } - const props = { - info: options.text, - context, - showInline: Boolean(options.inline), - showHeader: Boolean(options.header), - showSource: Boolean(options.source), - styles: - typeof options.styles === 'function' - ? options.styles - : s => nestedObjectAssign({}, s, options.styles), - propTables: options.propTables, - propTablesExclude: options.propTablesExclude, - propTableCompare: options.propTableCompare, - PropTable: makeTableComponent(options.TableComponent), - components, - maxPropObjectKeys: options.maxPropObjectKeys, - maxPropArrayLength: options.maxPropArrayLength, - maxPropsIntoLine: options.maxPropsIntoLine, - maxPropStringLength: options.maxPropStringLength, - excludedPropTypes: options.excludedPropTypes, - }; - return <Story {...props}>{storyFn(context)}</Story>; -} - -export const withInfo = makeDecorator({ - name: 'withInfo', - parameterName: 'info', - allowDeprecatedUsage: true, - wrapper: (getStory, context, { options, parameters }) => { - const storyOptions = parameters || options; - const infoOptions = typeof storyOptions === 'string' ? { text: storyOptions } : storyOptions; - const mergedOptions = - typeof infoOptions === 'string' ? infoOptions : { ...options, ...infoOptions }; - return addInfo(getStory, context, mergedOptions); - }, -}); - -export { Story }; - -export function setDefaults(newDefaults) { - return deprecate( - () => Object.assign(defaultOptions, newDefaults), - 'setDefaults is deprecated. Instead, you can pass options into withInfo(options) directly, or use the info parameter.' - )(); -} diff --git a/addons/info/src/index.test.js b/addons/info/src/index.test.js deleted file mode 100644 index 7152f4a58b82..000000000000 --- a/addons/info/src/index.test.js +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -/* eslint-disable react/prop-types */ -import React from 'react'; -import { mount } from 'enzyme'; - -import { withInfo, setDefaults } from '.'; -import externalMdDocs from '../README.md'; - -const TestComponent = ({ func, obj, array, number, string, bool, empty }) => ( - <div> - <h1>{String(func)}</h1> - <h2>{String(obj)}</h2> - <h3>{String(array)}</h3> - <h4>{String(number)}</h4> - <h5>{String(string)}</h5> - <h6>{String(bool)}</h6> - <p>{String(empty)}</p> - <a href="#">test</a> - <code>storiesOf</code> - <ul> - <li>1</li> - <li>2</li> - </ul> - </div> -); - -const reactClassPath = 'some/path/TestComponent.jsx'; -const storybookReactClassMock = { - name: 'TestComponent', - path: reactClassPath, - docgenInfo: { - description: ` -# Awesome test component description -## with markdown support -**bold** *cursive* -\`\`\`js -a; -\`\`\``, - name: 'TestComponent', - }, -}; - -const testOptions = { propTables: false }; - -const testMarkdown = `# Test story -## with markdown info -containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)`; - -describe('addon Info', () => { - const createStoryFn = Component => ({ name }) => ( - <div> - It's a {name} story: - <Component - func={x => x + 1} - obj={{ a: 'a', b: 'b' }} - array={[1, 2, 3]} - number={7} - string="seven" - bool - /> - </div> - ); - const storyFn = createStoryFn(TestComponent); - - it('should render <Info /> and markdown', () => { - const Info = withInfo(testMarkdown)(storyFn); - - expect(mount(<Info />)).toMatchSnapshot(); - }); - it('should render <Info /> and external markdown', () => { - const Info = withInfo(externalMdDocs)(storyFn); - - expect(mount(<Info />)).toMatchSnapshot(); - }); - it('should render with text options', () => { - const Info = withInfo({ text: 'some text here' })(storyFn); - mount(<Info />); - }); - it('should render with missed info', () => { - setDefaults(testOptions); - const Info = withInfo()(storyFn); - mount(<Info />); - }); - it('should render <Info /> for memoized component', () => { - const MemoizedTestComponent = React.memo(TestComponent); - const Info = withInfo()(createStoryFn(MemoizedTestComponent)); - - expect(mount(<Info />)).toMatchSnapshot(); - }); - - it('should render component description if story kind matches component', () => { - const previousReactClassesValue = global.STORYBOOK_REACT_CLASSES[reactClassPath]; - Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: storybookReactClassMock }); - - const Info = () => - withInfo({ inline: true, propTables: false })(storyFn, { - kind: 'TestComponent', - name: 'Basic test', - }); - - expect(mount(<Info />)).toMatchSnapshot(); - - Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: previousReactClassesValue }); - }); - - it('should render component description if story name matches component', () => { - const previousReactClassesValue = global.STORYBOOK_REACT_CLASSES[reactClassPath]; - Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: storybookReactClassMock }); - - const Info = () => - withInfo({ inline: true, propTables: false })(storyFn, { - kind: 'Test Components', - name: 'TestComponent', - }); - - expect(mount(<Info />)).toMatchSnapshot(); - - Object.assign(global.STORYBOOK_REACT_CLASSES, { [reactClassPath]: previousReactClassesValue }); - }); -}); diff --git a/addons/info/src/react-utils.js b/addons/info/src/react-utils.js deleted file mode 100644 index c85ff7bb90eb..000000000000 --- a/addons/info/src/react-utils.js +++ /dev/null @@ -1,14 +0,0 @@ -import { isMemo } from 'react-is'; - -export function getType(typeOrMemo) { - return isMemo(typeOrMemo) ? typeOrMemo.type : typeOrMemo; -} - -export function getDisplayName(typeOrMemo) { - if (typeof typeOrMemo === 'string') { - return typeOrMemo; - } - - const type = getType(typeOrMemo); - return type.displayName || type.name || 'Unknown'; -} diff --git a/addons/jest/README.md b/addons/jest/README.md index fa3b1f2433b8..10b97de36c56 100644 --- a/addons/jest/README.md +++ b/addons/jest/README.md @@ -4,7 +4,7 @@ Brings Jest results in storybook. [Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) -[![Storybook Jest Addon Demo](https://raw.githubusercontent.com/storybookjs/storybook-addon-jest/master/storybook-addon-jest.gif)](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel) +[![Storybook Jest Addon Demo](https://raw.githubusercontent.com/storybookjs/storybook/next/addons/jest/docs/storybook-addon-jest.gif)](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel) > Checkout the above [Live Storybook](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel). @@ -73,8 +73,8 @@ within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-jest/register'] -} + addons: ['@storybook/addon-jest'], +}; ``` ## Usage @@ -92,13 +92,9 @@ export default { decorators: [withTests({ results })], }; -export const defaultView = () => ( - <div>Jest results in storybook</div> -); -defaultView.story = { - parameters: { - jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], - }, +export const defaultView = () => <div>Jest results in storybook</div>; +defaultView.parameters = { + jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], }; ``` @@ -126,13 +122,9 @@ export default { title: 'MyComponent', }; -export const defaultView = () => ( - <div>Jest results in storybook</div> -); -defaultView.story = { - parameters: { - jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], - }, +export const defaultView = () => <div>Jest results in storybook</div>; +defaultView.parameters = { + jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], }; ``` @@ -147,13 +139,9 @@ export default { title: 'MyComponent', }; -export const defaultView = () => ( - <div>Jest results in storybook</div> -); -defaultView.story = { - parameters: { - jest: { disable: true }, - }, +export const defaultView = () => <div>Jest results in storybook</div>; +defaultView.parameters = { + jest: { disable: true }, }; ``` diff --git a/addons/jest/docs/storybook-addon-jest.gif b/addons/jest/docs/storybook-addon-jest.gif index 4b6c71b6f9f1..4e94db960f21 100644 Binary files a/addons/jest/docs/storybook-addon-jest.gif and b/addons/jest/docs/storybook-addon-jest.gif differ diff --git a/addons/jest/package.json b/addons/jest/package.json index 7fba6ed7a0fa..3405ac8de5e1 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -22,36 +22,47 @@ }, "license": "MIT", "author": "Renaud Tertrais <renaud.tertrais@gmail.com> (https://github.com/renaudtertrais)", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", "react-sizeme": "^2.5.2", - "upath": "^1.1.0", - "util-deprecate": "^1.0.2" + "regenerator-runtime": "^0.13.3", + "upath": "^1.1.0" + }, + "devDependencies": { + "@types/webpack-env": "^1.15.2" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/jest/src/components/Message.tsx b/addons/jest/src/components/Message.tsx index b87dccac668a..d58451260d02 100644 --- a/addons/jest/src/components/Message.tsx +++ b/addons/jest/src/components/Message.tsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React, { Fragment, FunctionComponent } from 'react'; import { styled } from '@storybook/theming'; const positiveConsoleRegex = /\[32m(.*?)\[39m/; @@ -51,10 +51,6 @@ const StatusColor = styled.strong<{ status: string }>(({ status, theme }) => ({ fontWeight: 500, })); -const Main = styled(({ msg, className }) => <section className={className}>{msg}</section>)({ - padding: 5, -}); - const colorizeText: (msg: string, type: string) => MsgElement[] = (msg: string, type: string) => { if (type) { return msg @@ -77,12 +73,9 @@ const getConvertedText: (msg: string) => MsgElement[] = (msg: string) => { if (!msg) return elementArray; - const splitText = msg - .split(/\[2m/) - .join('') - .split(/\[22m/); + const splitText = msg.split(/\[2m/).join('').split(/\[22m/); - splitText.forEach(element => { + splitText.forEach((element) => { if (element && element.trim()) { if ( element.indexOf(failStartToken) > -1 && @@ -114,12 +107,7 @@ const getTestDetail: (msg: string) => TestDetail = (msg: string) => { const current = lines[index]; const next = lines[index + 1]; - if ( - current - .trim() - .toLowerCase() - .indexOf(stackTraceStartToken) === 0 - ) { + if (current.trim().toLowerCase().indexOf(stackTraceStartToken) === 0) { testDetail.stackTrace += `${current.trim()}\n`; } else if (current.trim().indexOf(titleEndToken) > -1) { let title; @@ -148,7 +136,7 @@ interface MessageProps { msg: string; } -export const Message = (props: any) => { +export const Message: FunctionComponent<MessageProps> = (props) => { const { msg } = props; const detail: TestDetail = getTestDetail(msg); diff --git a/addons/jest/src/components/Result.tsx b/addons/jest/src/components/Result.tsx index 14cbe8d50c16..43545d03939a 100644 --- a/addons/jest/src/components/Result.tsx +++ b/addons/jest/src/components/Result.tsx @@ -1,4 +1,4 @@ -import React, { Component, Fragment, useState } from 'react'; +import React, { Fragment, useState } from 'react'; import { styled, themes, convert } from '@storybook/theming'; import { Icons } from '@storybook/components'; import Message from './Message'; @@ -41,10 +41,7 @@ const Icon = styled<any, any>(Icons)(({ theme }) => ({ })); const capitalizeFirstLetter = (text: string) => { - return text - .charAt(0) - .toUpperCase() - .concat(text.slice(1)); + return text.charAt(0).toUpperCase().concat(text.slice(1)); }; interface ResultProps { diff --git a/addons/jest/src/index.ts b/addons/jest/src/index.ts index 8deb37063493..2e90287ab39c 100644 --- a/addons/jest/src/index.ts +++ b/addons/jest/src/index.ts @@ -1,5 +1,4 @@ -import addons, { Parameters, StoryFn } from '@storybook/addons'; -import deprecate from 'util-deprecate'; +import addons, { Parameters } from '@storybook/addons'; import { normalize, sep } from 'upath'; import { ADD_TESTS } from './shared'; @@ -12,7 +11,7 @@ const findTestResults = ( jestTestResults: { testResults: { name: string }[] }, jestTestFilesExt: string ) => - Object.values(testFiles).map(name => { + Object.values(testFiles).map((name) => { const fileName = `${sep}${name}${jestTestFilesExt}`; if (jestTestResults && jestTestResults.testResults) { @@ -21,7 +20,7 @@ const findTestResults = ( return { fileName, name, - result: jestTestResults.testResults.find(test => + result: jestTestResults.testResults.find((test) => Boolean(normalize(test.name).match(fileNamePattern)) ), }; @@ -55,14 +54,6 @@ export const withTests = (userOptions: { results: any; filesExt?: string }) => { const options = { ...defaultOptions, ...userOptions }; return (...args: any[]) => { - if (typeof args[0] === 'string') { - return deprecate((storyFn: StoryFn<any>, { kind }: Parameters) => { - emitAddTests({ kind, story: storyFn, testFiles: (args as any) as string[], options }); - - return storyFn(); - }, 'Passing component filenames to the `@storybook/addon-jest` via `withTests` is deprecated. Instead, use the `jest` story parameter'); - } - const [storyFn, { kind, parameters = {} }] = args; let { jest: testFiles } = parameters; diff --git a/addons/jest/src/register.tsx b/addons/jest/src/register.tsx index 6537ad61373a..4db72017ce74 100644 --- a/addons/jest/src/register.tsx +++ b/addons/jest/src/register.tsx @@ -4,9 +4,9 @@ import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared'; import Panel from './components/Panel'; -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, (api) => { addons.addPanel(PANEL_ID, { - title: 'tests', + title: 'Tests', render: ({ active, key }) => <Panel key={key} api={api} active={active} />, paramKey: PARAM_KEY, }); diff --git a/addons/knobs/README.md b/addons/knobs/README.md index 4f8caa1a6ce3..a7bcd2683c47 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -23,35 +23,34 @@ within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-knobs/register'] -} + addons: ['@storybook/addon-knobs'], +}; ``` Now, write your stories with Knobs. ### With React + ```js -import React from "react"; -import { withKnobs, text, boolean, number } from "@storybook/addon-knobs"; +import React from 'react'; +import { withKnobs, text, boolean, number } from '@storybook/addon-knobs'; export default { - title: "Storybook Knobs", - decorators: [withKnobs] + title: 'Storybook Knobs', + decorators: [withKnobs], }; // Add the `withKnobs` decorator to add knobs support to your stories. // You can also configure `withKnobs` as a global decorator. // Knobs for React props export const withAButton = () => ( - <button disabled={boolean("Disabled", false)}> - {text("Label", "Hello Storybook")} - </button> + <button disabled={boolean('Disabled', false)}>{text('Label', 'Hello Storybook')}</button> ); // Knobs as dynamic variables. export const asDynamicVariables = () => { - const name = text("Name", "James"); - const age = number("Age", 35); + const name = text('Name', 'James'); + const age = number('Age', 35); const content = `I am ${name} and I'm ${age} years old.`; return <div>{content}</div>; @@ -59,7 +58,9 @@ export const asDynamicVariables = () => { ``` ### With Vue.js + MyButton.story.js: + ```js import { storiesOf } from '@storybook/vue'; import { withKnobs, text, boolean } from '@storybook/addon-knobs'; @@ -67,8 +68,8 @@ import { withKnobs, text, boolean } from '@storybook/addon-knobs'; import MyButton from './MyButton.vue'; export default { - title: "Storybook Knobs", - decorators: [withKnobs] + title: 'Storybook Knobs', + decorators: [withKnobs], }; // Assign `props` to the story's component, calling @@ -76,21 +77,22 @@ export default { // then pass the story's prop data to the component’s prop in // the template with `v-bind:` or by placing the prop within // the component’s slot. -export const withKnobs = () => ({ +export const exampleWithKnobs = () => ({ components: { MyButton }, props: { isDisabled: { - default: boolean('Disabled', false) + default: boolean('Disabled', false), }, text: { - default: text('Text', 'Hello Storybook') - } + default: text('Text', 'Hello Storybook'), + }, }, - template: `<MyButton :isDisabled="isDisabled">{{ text }}</MyButton>` + template: `<MyButton :isDisabled="isDisabled">{{ text }}</MyButton>`, }); ``` MyButton.vue: + ```vue <template> <button :disabled="isDisabled"> @@ -103,14 +105,15 @@ export default { props: { isDisabled: { type: Boolean, - default: false - } - } -} + default: false, + }, + }, +}; </script> ``` ### With Angular + ```js import { storiesOf } from '@storybook/angular'; import { boolean, number, text, withKnobs } from '@storybook/addon-knobs'; @@ -118,8 +121,8 @@ import { boolean, number, text, withKnobs } from '@storybook/addon-knobs'; import { Button } from '@storybook/angular/demo'; export default { - title: "Storybook Knobs", - decorators: [withKnobs] + title: 'Storybook Knobs', + decorators: [withKnobs], }; export const withKnobs = () => ({ @@ -131,9 +134,10 @@ export const withKnobs = () => ({ ``` ### With Ember + ```js import { withKnobs, text, boolean } from '@storybook/addon-knobs'; -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; export default { title: 'StoryBook with Knobs', @@ -160,9 +164,9 @@ export const inGroups = () => { const personalGroupId = 'personal info'; const generalGroupId = 'general info'; - const name = text("Name", "James", personalGroupId); - const age = number("Age", 35, personalGroupId); - const message = text("Hello!", 35, generalGroupId); + const name = text('Name', 'James', personalGroupId); + const age = number('Age', 35, { min: 0, max: 99 }, personalGroupId); + const message = text('Hello!', 35, generalGroupId); const content = ` I am ${name} and I'm ${age} years old. ${message} @@ -245,10 +249,10 @@ import { number } from '@storybook/addon-knobs'; const label = 'Temperature'; const defaultValue = 73; const options = { - range: true, - min: 60, - max: 90, - step: 1, + range: true, + min: 60, + max: 90, + step: 1, }; const groupId = 'GROUP-ID1'; @@ -345,7 +349,7 @@ Options can also be an array: ```js import { select } from '@storybook/addon-knobs'; const label = 'Cats'; -const options = ['linus', 'eleanor', 'lover'] +const options = ['linus', 'eleanor', 'lover']; const defaultValue = 'eleanor'; const groupId = 'GROUP-ID2'; const value = select(label, options, defaultValue, groupId); @@ -369,7 +373,7 @@ const arrayOfObjects = [ ]; const defaultValue = arrayOfObjects[0]; const groupId = 'GROUP-ID3'; -const value = select(label, options, defaultValue, groupId); +const value = select(label, arrayOfObjects, defaultValue, groupId); ``` ### radio buttons @@ -393,7 +397,7 @@ const value = radios(label, options, defaultValue, groupId); ### options -Configurable UI for selecting a value from a set of options. +Configurable UI for selecting a value from a set of options. ```js import { optionsKnob as options } from '@storybook/addon-knobs'; @@ -406,13 +410,15 @@ const valuesObj = { }; const defaultValue = 'kiwi'; const optionsObj = { - display: 'inline-radio' + display: 'inline-radio', }; const groupId = 'GROUP-ID1'; const value = options(label, valuesObj, defaultValue, optionsObj, groupId); ``` + > The display property for `optionsObj` accepts: +> > - `radio` > - `inline-radio` > - `check` @@ -459,8 +465,8 @@ If your component needs the date in a different form you can wrap the `date` fun ```js function myDateKnob(name, defaultValue) { - const stringTimestamp = date(name, defaultValue) - return new Date(stringTimestamp) + const stringTimestamp = date(name, defaultValue); + return new Date(stringTimestamp); } ``` @@ -494,20 +500,16 @@ export default { decorators: [withKnobs], }; -export const defaultView = () => ( - <div /> -); -defaultView.story = { - parameters: { - knobs: { - // Doesn't emit events while user is typing. - timestamps: true, - - // Escapes strings to be safe for inserting as innerHTML. This option is true by default. It's safe to set it to `false` with frameworks like React which do escaping on their side. - // You can still set it to false, but it's strongly discouraged to set to true in cases when you host your storybook on some route of your main site or web app. - escapeHTML: true, - } - } +export const defaultView = () => <div />; +defaultView.parameters = { + knobs: { + // Doesn't emit events while user is typing. + timestamps: true, + + // Escapes strings to be safe for inserting as innerHTML. This option is true by default. It's safe to set it to `false` with frameworks like React which do escaping on their side. + // You can still set it to false, but it's strongly discouraged to set to true in cases when you host your storybook on some route of your main site or web app. + escapeHTML: true, + }, }; ``` @@ -518,9 +520,8 @@ If you are using Typescript, make sure you have the type definitions installed f - node - react -You can install them using: (*assuming you are using Typescript >2.0.*) +You can install them using: (_assuming you are using Typescript >2.0._) ```sh yarn add @types/node @types/react --dev ``` - diff --git a/addons/knobs/angular.js b/addons/knobs/angular.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/angular.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/docs/demo.gif b/addons/knobs/docs/demo.gif index 847a0f77ec0b..12206cb0653d 100644 Binary files a/addons/knobs/docs/demo.gif and b/addons/knobs/docs/demo.gif differ diff --git a/addons/knobs/html.js b/addons/knobs/html.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/html.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/marko.js b/addons/knobs/marko.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/marko.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/mithril.js b/addons/knobs/mithril.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/mithril.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/package.json b/addons/knobs/package.json index 07d665af93d8..1b1cc9d97cae 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-knobs", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook Addon Prop Editor Component", "keywords": [ "addon", @@ -16,48 +16,61 @@ "directory": "addons/knobs" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/channels": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "@types/react-color": "^3.0.1", "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", "escape-html": "^1.0.3", - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "global": "^4.3.2", "lodash": "^4.17.15", "prop-types": "^15.7.2", "qs": "^6.6.0", "react-color": "^2.17.0", "react-lifecycles-compat": "^3.0.4", - "react-select": "^3.0.8" + "react-select": "^3.0.8", + "regenerator-runtime": "^0.13.3" }, "devDependencies": { + "@types/enzyme": "^3.10.5", "@types/escape-html": "0.0.20", "@types/react-lifecycles-compat": "^3.0.1", - "@types/react-select": "^3.0.4" + "@types/react-select": "^3.0.12", + "@types/webpack-env": "^1.15.2", + "enzyme": "^3.11.0" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/knobs/polymer.js b/addons/knobs/polymer.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/polymer.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/react.js b/addons/knobs/react.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/react.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/src/KnobManager.ts b/addons/knobs/src/KnobManager.ts index f59430f73e37..db2f872572b0 100644 --- a/addons/knobs/src/KnobManager.ts +++ b/addons/knobs/src/KnobManager.ts @@ -3,7 +3,6 @@ import { navigator } from 'global'; import escape from 'escape-html'; import { getQueryParams } from '@storybook/client-api'; -// eslint-disable-next-line import/no-extraneous-dependencies import { Channel } from '@storybook/channels'; import KnobStore, { KnobStoreKnob } from './KnobStore'; @@ -49,6 +48,7 @@ function escapeStrings(obj: any): any { interface KnobManagerOptions { escapeHTML?: boolean; disableDebounce?: boolean; + disableForceUpdate?: boolean; } export default class KnobManager { diff --git a/addons/knobs/src/KnobStore.ts b/addons/knobs/src/KnobStore.ts index b8ddf0e0b063..cc3699e3f1f2 100644 --- a/addons/knobs/src/KnobStore.ts +++ b/addons/knobs/src/KnobStore.ts @@ -63,7 +63,7 @@ export default class KnobStore { } markAllUnused() { - Object.keys(this.store).forEach(knobName => { + Object.keys(this.store).forEach((knobName) => { this.store[knobName].used = false; }); } diff --git a/addons/knobs/src/__types__/knob-test-cases.ts b/addons/knobs/src/__types__/knob-test-cases.ts index 50c6f988ad72..19a147f5000b 100644 --- a/addons/knobs/src/__types__/knob-test-cases.ts +++ b/addons/knobs/src/__types__/knob-test-cases.ts @@ -15,7 +15,6 @@ import { } from '../index'; // Note: this is a helper to batch test return types and avoid "declared but never read" errors -// eslint-disable-next-line @typescript-eslint/no-empty-function function expectKnobOfType<T>(..._: T[]) {} const groupId = 'GROUP-ID1'; diff --git a/addons/knobs/src/components/Panel.tsx b/addons/knobs/src/components/Panel.tsx index e9cb4fa9ffc0..bcb9bd3baf42 100644 --- a/addons/knobs/src/components/Panel.tsx +++ b/addons/knobs/src/components/Panel.tsx @@ -126,7 +126,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { const { api } = this.props; if (!this.options.timestamps || !timestamp || this.lastEdit <= timestamp) { - Object.keys(knobs).forEach(name => { + Object.keys(knobs).forEach((name) => { const knob = knobs[name]; // For the first time, get values from the URL and set them. if (!this.loadedFromUrl) { @@ -192,7 +192,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { const queryParams: { [key: string]: any } = {}; - Object.keys(newKnobs).forEach(n => { + Object.keys(newKnobs).forEach((n) => { const knob = newKnobs[n]; queryParams[`knob-${n}`] = getKnobControl(knob.type).serialize(knob.value); }); @@ -219,9 +219,9 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { const groups: Record<string, PanelKnobGroups> = {}; const groupIds: string[] = []; - const knobKeysArray = Object.keys(knobs).filter(key => knobs[key].used); + const knobKeysArray = Object.keys(knobs).filter((key) => knobs[key].used); - knobKeysArray.forEach(key => { + knobKeysArray.forEach((key) => { const knobKeyGroupId = knobs[key].groupId || DEFAULT_GROUP_ID; groupIds.push(knobKeyGroupId); groups[knobKeyGroupId] = { @@ -229,7 +229,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { <TabWrapper key={knobKeyGroupId} active={active}> <PropForm knobs={knobsArray.filter( - knob => (knob.groupId || DEFAULT_GROUP_ID) === knobKeyGroupId + (knob) => (knob.groupId || DEFAULT_GROUP_ID) === knobKeyGroupId )} onFieldChange={this.handleChange} onFieldClick={this.handleClick} @@ -240,7 +240,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { }; }); - const knobsArray = knobKeysArray.map(key => knobs[key]); + const knobsArray = knobKeysArray.map((key) => knobs[key]); if (knobsArray.length === 0) { return ( @@ -252,6 +252,7 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { href="https://github.com/storybookjs/storybook/tree/master/addons/knobs" target="_blank" withArrow + cancel={false} > dynamically interact with components </Link> @@ -264,9 +265,9 @@ export default class KnobPanel extends PureComponent<KnobPanelProps> { const sortEntries = (g: Record<string, PanelKnobGroups>): [string, PanelKnobGroups][] => { const unsortedKeys = Object.keys(g); if (unsortedKeys.includes(DEFAULT_GROUP_ID)) { - const sortedKeys = unsortedKeys.filter(key => key !== DEFAULT_GROUP_ID); + const sortedKeys = unsortedKeys.filter((key) => key !== DEFAULT_GROUP_ID); sortedKeys.push(DEFAULT_GROUP_ID); - return sortedKeys.map<[string, PanelKnobGroups]>(key => [key, g[key]]); + return sortedKeys.map<[string, PanelKnobGroups]>((key) => [key, g[key]]); } return Object.entries(g); }; diff --git a/addons/knobs/src/components/PropForm.tsx b/addons/knobs/src/components/PropForm.tsx index 2acd26559c11..de3fce776531 100644 --- a/addons/knobs/src/components/PropForm.tsx +++ b/addons/knobs/src/components/PropForm.tsx @@ -47,7 +47,7 @@ export default class PropForm extends Component<PropFormProps> { return ( <Form> - {knobs.map(knob => { + {knobs.map((knob) => { const changeHandler = this.makeChangeHandler(knob.name, knob.type); const InputType: ComponentType<any> = getKnobControl(knob.type) || InvalidType; diff --git a/addons/knobs/src/components/__tests__/Panel.js b/addons/knobs/src/components/__tests__/Panel.js index a01dbb2c6595..65f1cb163b69 100644 --- a/addons/knobs/src/components/__tests__/Panel.js +++ b/addons/knobs/src/components/__tests__/Panel.js @@ -18,7 +18,7 @@ const createTestApi = () => ({ // is a workaround jest.mock('react', () => { const r = jest.requireActual('react'); - return { ...r, memo: x => x }; + return { ...r, memo: (x) => x }; }); describe('Panel', () => { @@ -50,7 +50,7 @@ describe('Panel', () => { handlers[e] = handler; }, emit: jest.fn(), - getQueryParam: key => testQueryParams[key], + getQueryParam: (key) => testQueryParams[key], setQueryParams: jest.fn(), }; @@ -146,7 +146,7 @@ describe('Panel', () => { const wrapper = root.update().find(Panel); const formWrapper = wrapper.find(PropForm); - const knobs = formWrapper.map(formInstanceWrapper => formInstanceWrapper.prop('knobs')); + const knobs = formWrapper.map((formInstanceWrapper) => formInstanceWrapper.prop('knobs')); expect(knobs).toMatchSnapshot(); @@ -182,7 +182,7 @@ describe('Panel', () => { const titles = wrapper .find(TabsState) .find('button') - .map(child => child.prop('children')); + .map((child) => child.prop('children')); expect(titles).toEqual(['foo', 'bar']); const knobs = wrapper.find(PropForm); @@ -222,10 +222,10 @@ describe('Panel', () => { const titles = wrapper .find(TabsState) .find('button') - .map(child => child.prop('children')); + .map((child) => child.prop('children')); expect(titles).toEqual(['foo', DEFAULT_GROUP_ID]); - const knobs = wrapper.find(PropForm).map(propForm => propForm.prop('knobs')); + const knobs = wrapper.find(PropForm).map((propForm) => propForm.prop('knobs')); // there are props with no groupId so Other should also have its own PropForm expect(knobs.length).toEqual(titles.length); expect(knobs).toMatchSnapshot(); diff --git a/addons/knobs/src/components/types/Boolean.tsx b/addons/knobs/src/components/types/Boolean.tsx index bd04676a62e6..393363679b32 100644 --- a/addons/knobs/src/components/types/Boolean.tsx +++ b/addons/knobs/src/components/types/Boolean.tsx @@ -34,14 +34,14 @@ const BooleanType: FunctionComponent<BooleanTypeProps> & { id={knob.name} name={knob.name} type="checkbox" - onChange={e => onChange(e.target.checked)} + onChange={(e) => onChange(e.target.checked)} checked={knob.value} /> ); BooleanType.defaultProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; BooleanType.propTypes = { diff --git a/addons/knobs/src/components/types/Checkboxes.tsx b/addons/knobs/src/components/types/Checkboxes.tsx index 3eec4306ba7b..4c343d450646 100644 --- a/addons/knobs/src/components/types/Checkboxes.tsx +++ b/addons/knobs/src/components/types/Checkboxes.tsx @@ -51,7 +51,7 @@ const CheckboxLabel = styled.label({ export default class CheckboxesType extends Component<CheckboxesTypeProps, CheckboxesTypeState> { static defaultProps: CheckboxesTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, isInline: false, }; @@ -95,7 +95,7 @@ export default class CheckboxesType extends Component<CheckboxesTypeProps, Check }; renderCheckboxList = ({ options }: CheckboxesTypeKnob) => - Object.keys(options).map(key => this.renderCheckbox(key, options[key])); + Object.keys(options).map((key) => this.renderCheckbox(key, options[key])); renderCheckbox = (label: string, value: string) => { const { knob } = this.props; diff --git a/addons/knobs/src/components/types/Color.tsx b/addons/knobs/src/components/types/Color.tsx index abe313978bf4..c738e2763ada 100644 --- a/addons/knobs/src/components/types/Color.tsx +++ b/addons/knobs/src/components/types/Color.tsx @@ -56,7 +56,7 @@ export default class ColorType extends Component<ColorTypeProps, ColorTypeState> static defaultProps: ColorTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; static serialize = (value: ColorTypeKnobValue) => value; @@ -129,7 +129,7 @@ export default class ColorType extends Component<ColorTypeProps, ColorTypeState> <Swatch style={colorStyle} /> {displayColorPicker ? ( <Popover - ref={e => { + ref={(e) => { if (e) this.popover = e; }} > diff --git a/addons/knobs/src/components/types/Date.tsx b/addons/knobs/src/components/types/Date.tsx index 8c921532492d..93753275cb6d 100644 --- a/addons/knobs/src/components/types/Date.tsx +++ b/addons/knobs/src/components/types/Date.tsx @@ -42,7 +42,7 @@ const formatTime = (date: Date) => { export default class DateType extends Component<DateTypeProps, DateTypeState> { static defaultProps: DateTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; static propTypes = { diff --git a/addons/knobs/src/components/types/Files.tsx b/addons/knobs/src/components/types/Files.tsx index a038ca3ff7df..a19738e267ac 100644 --- a/addons/knobs/src/components/types/Files.tsx +++ b/addons/knobs/src/components/types/Files.tsx @@ -21,7 +21,7 @@ const FileInput = styled(Form.Input)({ }); function fileReaderPromise(file: File) { - return new Promise<string>(resolve => { + return new Promise<string>((resolve) => { const fileReader = new FileReader(); fileReader.onload = (e: Event) => resolve((e.currentTarget as FileReader).result as string); fileReader.readAsDataURL(file); @@ -51,7 +51,7 @@ const FilesType: FunctionComponent<FilesTypeProps> & { FilesType.defaultProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; FilesType.propTypes = { diff --git a/addons/knobs/src/components/types/Number.tsx b/addons/knobs/src/components/types/Number.tsx index 7778976f0e24..bc242b009ef3 100644 --- a/addons/knobs/src/components/types/Number.tsx +++ b/addons/knobs/src/components/types/Number.tsx @@ -14,11 +14,8 @@ export interface NumberTypeKnobOptions { step?: number; } -export interface NumberTypeKnob - extends KnobControlConfig<NumberTypeKnobValue>, - NumberTypeKnobOptions { - value: NumberTypeKnobValue; -} +export type NumberTypeKnob = KnobControlConfig<NumberTypeKnobValue> & + NumberTypeKnobOptions & { value?: NumberTypeKnobValue }; interface NumberTypeProps extends KnobControlProps<NumberTypeKnobValue | null> { knob: NumberTypeKnob; @@ -69,7 +66,7 @@ export default class NumberType extends Component<NumberTypeProps> { static defaultProps: NumberTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; static serialize = (value: NumberTypeKnobValue | null | undefined) => diff --git a/addons/knobs/src/components/types/Object.tsx b/addons/knobs/src/components/types/Object.tsx index ade9fff10a74..8ff178d25dc2 100644 --- a/addons/knobs/src/components/types/Object.tsx +++ b/addons/knobs/src/components/types/Object.tsx @@ -25,12 +25,12 @@ class ObjectType<T> extends Component<ObjectTypeProps<T>> { static defaultProps: ObjectTypeProps<any> = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; - static serialize: { <T>(object: T): string } = object => JSON.stringify(object); + static serialize: { <T>(object: T): string } = (object) => JSON.stringify(object); - static deserialize: { <T>(value: string): T } = value => (value ? JSON.parse(value) : {}); + static deserialize: { <T>(value: string): T } = (value) => (value ? JSON.parse(value) : {}); static getDerivedStateFromProps<T>( props: ObjectTypeProps<T>, diff --git a/addons/knobs/src/components/types/Options.tsx b/addons/knobs/src/components/types/Options.tsx index 54a49191fe70..a13be29e0c2d 100644 --- a/addons/knobs/src/components/types/Options.tsx +++ b/addons/knobs/src/components/types/Options.tsx @@ -63,13 +63,13 @@ interface OptionsSelectValueItem { label: string; } -const serialize: { <T>(value: T): T } = value => value; -const deserialize: { <T>(value: T): T } = value => value; +const serialize: { <T>(value: T): T } = (value) => value; +const deserialize: { <T>(value: T): T } = (value) => value; const OptionsType: FunctionComponent<OptionsTypeProps<any>> & { serialize: typeof serialize; deserialize: typeof deserialize; -} = props => { +} = (props) => { const { knob, onChange } = props; const { display } = knob.optionsObj; @@ -84,19 +84,20 @@ const OptionsType: FunctionComponent<OptionsTypeProps<any>> & { } if (display === 'select' || display === 'multi-select') { - const options: OptionsSelectValueItem[] = Object.keys(knob.options).map(key => ({ + const options: OptionsSelectValueItem[] = Object.keys(knob.options).map((key) => ({ value: knob.options[key], label: key, })); const isMulti = display === 'multi-select'; - const optionsIndex = options.findIndex(i => i.value === knob.value); + const optionsIndex = options.findIndex((i) => i.value === knob.value); let defaultValue: typeof options | typeof options[0] = options[optionsIndex]; let handleChange: ReactSelectOnChangeFn = (e: OptionsSelectValueItem) => onChange(e.value); if (isMulti) { - defaultValue = options.filter(i => knob.value.includes(i.value)); - handleChange = (values: OptionsSelectValueItem[]) => onChange(values.map(item => item.value)); + defaultValue = options.filter((i) => knob.value.includes(i.value)); + handleChange = (values: OptionsSelectValueItem[]) => + onChange(values.map((item) => item.value)); } return ( @@ -115,7 +116,7 @@ const OptionsType: FunctionComponent<OptionsTypeProps<any>> & { OptionsType.defaultProps = { knob: {} as any, display: 'select', - onChange: value => value, + onChange: (value) => value, }; OptionsType.propTypes = { diff --git a/addons/knobs/src/components/types/Radio.tsx b/addons/knobs/src/components/types/Radio.tsx index 4c102661f374..7cee7cfeea43 100644 --- a/addons/knobs/src/components/types/Radio.tsx +++ b/addons/knobs/src/components/types/Radio.tsx @@ -41,7 +41,7 @@ const RadioLabel = styled.label({ class RadiosType extends Component<RadiosTypeProps> { static defaultProps: RadiosTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, isInline: false, }; @@ -61,9 +61,9 @@ class RadiosType extends Component<RadiosTypeProps> { renderRadioButtonList({ options }: RadiosTypeKnob) { if (Array.isArray(options)) { - return options.map(val => this.renderRadioButton(val, val)); + return options.map((val) => this.renderRadioButton(val, val)); } - return Object.keys(options).map(key => this.renderRadioButton(key, options[key])); + return Object.keys(options).map((key) => this.renderRadioButton(key, options[key])); } renderRadioButton(label: string, value: RadiosTypeKnobValue) { @@ -79,7 +79,7 @@ class RadiosType extends Component<RadiosTypeProps> { id={id} name={name} value={opts.value || undefined} - onChange={e => onChange(e.target.value)} + onChange={(e) => onChange(e.target.value)} checked={value === knob.value} /> <RadioLabel htmlFor={id}>{label}</RadioLabel> diff --git a/addons/knobs/src/components/types/Select.tsx b/addons/knobs/src/components/types/Select.tsx index 50a5d52b5648..49c87db80cfe 100644 --- a/addons/knobs/src/components/types/Select.tsx +++ b/addons/knobs/src/components/types/Select.tsx @@ -32,14 +32,14 @@ const SelectType: FunctionComponent<SelectTypeProps> & { const { options } = knob; const callbackReduceArrayOptions = (acc: any, option: any, i: number) => { - if (typeof option !== 'object') return { ...acc, [option]: option }; + if (typeof option !== 'object' || option === null) return { ...acc, [option]: option }; const label = option.label || option.key || i; return { ...acc, [label]: option }; }; const entries = Array.isArray(options) ? options.reduce(callbackReduceArrayOptions, {}) : options; - const selectedKey = Object.keys(entries).find(key => { + const selectedKey = Object.keys(entries).find((key) => { const { value: knobVal } = knob; const entryVal = entries[key]; @@ -69,7 +69,7 @@ const SelectType: FunctionComponent<SelectTypeProps> & { SelectType.defaultProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; SelectType.propTypes = { diff --git a/addons/knobs/src/components/types/Text.tsx b/addons/knobs/src/components/types/Text.tsx index f09c8b526ca9..57db9c6941cc 100644 --- a/addons/knobs/src/components/types/Text.tsx +++ b/addons/knobs/src/components/types/Text.tsx @@ -5,13 +5,13 @@ import { Form } from '@storybook/components'; import { KnobControlConfig, KnobControlProps } from './types'; type TextTypeKnobValue = string; -export type TextTypeKnob = KnobControlConfig<TextTypeKnobValue>; +export type TextTypeKnob = KnobControlConfig<TextTypeKnobValue> & { value?: TextTypeKnobValue }; type TextTypeProps = KnobControlProps<TextTypeKnobValue>; export default class TextType extends Component<TextTypeProps> { static defaultProps: TextTypeProps = { knob: {} as any, - onChange: value => value, + onChange: (value) => value, }; static propTypes = { diff --git a/addons/knobs/src/deprecated.js b/addons/knobs/src/deprecated.js deleted file mode 100644 index 5bc204f7316f..000000000000 --- a/addons/knobs/src/deprecated.js +++ /dev/null @@ -1,29 +0,0 @@ -import { - withKnobs, - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - files, - button, -} from '.'; - -export { - withKnobs, - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - files, - button, -}; diff --git a/addons/knobs/src/index.ts b/addons/knobs/src/index.ts index 6f8e757aa231..1e940fcb8117 100644 --- a/addons/knobs/src/index.ts +++ b/addons/knobs/src/index.ts @@ -128,7 +128,6 @@ export const withKnobs = makeDecorator({ name: 'withKnobs', parameterName: 'knobs', skipIfNoParametersOrOptions: false, - allowDeprecatedUsage: true, wrapper: (getStory, context, { options, parameters }) => { const storyOptions = parameters || options; const allOptions = { ...defaultOptions, ...storyOptions }; diff --git a/addons/knobs/src/preset/addDecorator.ts b/addons/knobs/src/preset/addDecorator.ts index 08d985c0e190..364246df2610 100644 --- a/addons/knobs/src/preset/addDecorator.ts +++ b/addons/knobs/src/preset/addDecorator.ts @@ -1,4 +1,3 @@ -import { addDecorator } from '@storybook/client-api'; import { withKnobs } from '../index'; -addDecorator(withKnobs); +export const decorators = [withKnobs]; diff --git a/addons/knobs/src/preset/index.ts b/addons/knobs/src/preset/index.ts index 0bce78742e0d..8660a5368c50 100644 --- a/addons/knobs/src/preset/index.ts +++ b/addons/knobs/src/preset/index.ts @@ -1,8 +1,8 @@ -type KnobsOptions = { +interface KnobsOptions { addDecorator?: boolean; -}; +} -export function addons(entry: any[] = [], options: any) { +export function managerEntries(entry: any[] = [], options: any) { return [...entry, require.resolve('../register')]; } diff --git a/addons/knobs/src/register.tsx b/addons/knobs/src/register.tsx index 8acbc9a5ea80..98083e05e5a0 100644 --- a/addons/knobs/src/register.tsx +++ b/addons/knobs/src/register.tsx @@ -3,7 +3,7 @@ import addons from '@storybook/addons'; import Panel from './components/Panel'; import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared'; -addons.register(ADDON_ID, api => { +addons.register(ADDON_ID, (api) => { addons.addPanel(PANEL_ID, { title: 'Knobs', render: ({ active, key }) => <Panel api={api} key={key} active={active} />, diff --git a/addons/knobs/src/registerKnobs.ts b/addons/knobs/src/registerKnobs.ts index 0c2be7e5b105..40980934e736 100644 --- a/addons/knobs/src/registerKnobs.ts +++ b/addons/knobs/src/registerKnobs.ts @@ -37,10 +37,12 @@ function knobChanged(change: KnobStoreKnob) { const knobOptions = knobStore.get(name); knobOptions.value = value; - if (!manager.options.disableDebounce) { - debouncedResetAndForceUpdate(); - } else { - resetAndForceUpdate(); + if (!manager.options.disableForceUpdate && !knobOptions.disableForceUpdate) { + if (!manager.options.disableDebounce && !knobOptions.disableDebounce) { + debouncedResetAndForceUpdate(); + } else { + resetAndForceUpdate(); + } } } diff --git a/addons/knobs/src/type-defs.ts b/addons/knobs/src/type-defs.ts index ddc8c2de34e3..469dbdc6f83b 100644 --- a/addons/knobs/src/type-defs.ts +++ b/addons/knobs/src/type-defs.ts @@ -18,7 +18,12 @@ export type Mutable<T> = { -readonly [P in keyof T]: T[P] extends readonly (infer U)[] ? U[] : T[P]; }; -type KnobPlus<T extends KnobType, K> = K & { type: T; groupId?: string }; +type KnobPlus<T extends KnobType, K> = K & { + type: T; + groupId?: string; + disableDebounce?: boolean; + disableForceUpdate?: boolean; +}; export type Knob<T extends KnobType = any> = T extends 'text' ? KnobPlus<T, Pick<TextTypeKnob, 'value'>> diff --git a/addons/knobs/vue.js b/addons/knobs/vue.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/knobs/vue.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/addons/links/README.md b/addons/links/README.md index 22a7c0cf2a65..6ca46168c971 100644 --- a/addons/links/README.md +++ b/addons/links/README.md @@ -16,7 +16,7 @@ within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-links/register'] + addons: ['@storybook/addon-links'] } ``` @@ -150,4 +150,4 @@ It accepts all the props the `a` element does, plus `story` and `kind`. It the ` >Go to Second</LinkTo> ``` -To implement such a component for another framework, you need to add special handling for `click` event on native `a` element. See [`RoutedLink` sources](https://github.com/storybookjs/storybook/blob/master/lib/components/src/navigation/routed_link.js#L4-L9) for reference. +To implement such a component for another framework, you need to add special handling for `click` event on native `a` element. See [`RoutedLink` sources](https://github.com/storybookjs/storybook/blob/master/addons/links/src/react/components/RoutedLink.js#L20-L24) for reference. diff --git a/addons/links/package.json b/addons/links/package.json index 335b74c26e13..b3bb898363ce 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Story Links addon for storybook", "keywords": [ "addon", @@ -16,35 +16,49 @@ "directory": "addons/links" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", "@storybook/csf": "0.0.1", - "@storybook/router": "5.3.0-rc.0", + "@storybook/router": "6.0.0-beta.21", + "@types/qs": "^6.9.0", "core-js": "^3.0.1", "global": "^4.3.2", "prop-types": "^15.7.2", "qs": "^6.6.0", - "ts-dedent": "^1.1.0" + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1" + }, + "devDependencies": { + "@types/webpack-env": "^1.15.2", + "enzyme": "^3.11.0" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/links/preset.js b/addons/links/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/links/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/links/register.js b/addons/links/register.js index 9232e6c069d5..cc38cb06f1f2 100644 --- a/addons/links/register.js +++ b/addons/links/register.js @@ -1 +1 @@ -require('./dist/manager').register(); +require('./dist/register'); diff --git a/addons/links/src/constants.ts b/addons/links/src/constants.ts index 8cd994edd00a..7fb1ad05b5d3 100644 --- a/addons/links/src/constants.ts +++ b/addons/links/src/constants.ts @@ -1,4 +1,5 @@ export const ADDON_ID = 'storybook/links'; +export const PARAM_KEY = `links`; export default { NAVIGATE: `${ADDON_ID}/navigate`, diff --git a/addons/links/src/manager.ts b/addons/links/src/manager.ts deleted file mode 100644 index d5aa5b788989..000000000000 --- a/addons/links/src/manager.ts +++ /dev/null @@ -1,14 +0,0 @@ -import addons from '@storybook/addons'; - -import EVENTS, { ADDON_ID } from './constants'; - -export const register = () => { - addons.register(ADDON_ID, api => { - const channel = addons.getChannel(); - - channel.on(EVENTS.REQUEST, ({ kind, name }) => { - const id = api.storyId(kind, name); - api.emit(EVENTS.RECEIVE, id); - }); - }); -}; diff --git a/addons/links/src/preset/addDecorator.ts b/addons/links/src/preset/addDecorator.ts new file mode 100644 index 000000000000..ff68743b1a37 --- /dev/null +++ b/addons/links/src/preset/addDecorator.ts @@ -0,0 +1,3 @@ +import { withLinks } from '../index'; + +export const decorators = [withLinks]; diff --git a/addons/links/src/preset/index.ts b/addons/links/src/preset/index.ts new file mode 100644 index 000000000000..1234ed30130f --- /dev/null +++ b/addons/links/src/preset/index.ts @@ -0,0 +1,15 @@ +interface LinkOptions { + addDecorator?: boolean; +} + +export function managerEntries(entry: any[] = []) { + return [...entry, require.resolve('../register')]; +} + +export function config(entry: any[] = [], { addDecorator = true }: LinkOptions = {}) { + const linkConfig = []; + if (addDecorator) { + linkConfig.push(require.resolve('./addDecorator')); + } + return [...entry, ...linkConfig]; +} diff --git a/addons/links/src/preview.test.js b/addons/links/src/preview.test.js index 46647a36a37e..88008541f0d2 100644 --- a/addons/links/src/preview.test.js +++ b/addons/links/src/preview.test.js @@ -17,6 +17,8 @@ jest.mock('global', () => ({ kind: 'kind', })), }, + window: global, + __STORYBOOK_LOGGER: console, __STORYBOOK_CLIENT_API__: { raw: jest.fn(() => [ { @@ -49,7 +51,7 @@ describe('preview', () => { it('should select the kind (only) provided', () => { const channel = { emit: jest.fn() }; addons.getChannel.mockReturnValue(channel); - __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => null); + __STORYBOOK_STORY_STORE__.fromId.mockImplementation((input) => null); const handler = linkTo('kind'); handler(); @@ -64,7 +66,7 @@ describe('preview', () => { const channel = { emit: jest.fn() }; addons.getChannel.mockReturnValue(channel); // simulate a currently selected, but not found as ID - __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => + __STORYBOOK_STORY_STORE__.fromId.mockImplementation((input) => !input ? { kind: 'kind', @@ -85,7 +87,7 @@ describe('preview', () => { it('should select the id provided', () => { const channel = { emit: jest.fn() }; addons.getChannel.mockReturnValue(channel); - __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => + __STORYBOOK_STORY_STORE__.fromId.mockImplementation((input) => input === 'kind--story' ? { story: 'name', @@ -106,7 +108,7 @@ describe('preview', () => { it('should handle functions returning strings', () => { const channel = { emit: jest.fn() }; addons.getChannel.mockReturnValue(channel); - __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => null); + __STORYBOOK_STORY_STORE__.fromId.mockImplementation((input) => null); const handler = linkTo( (a, b) => a + b, diff --git a/addons/links/src/preview.ts b/addons/links/src/preview.ts index 0aeb0ebd9ed8..07e319cf7d93 100644 --- a/addons/links/src/preview.ts +++ b/addons/links/src/preview.ts @@ -5,10 +5,11 @@ import { __STORYBOOK_CLIENT_API__ as clientApi, } from 'global'; import qs from 'qs'; -import addons from '@storybook/addons'; +import addons, { makeDecorator } from '@storybook/addons'; import { STORY_CHANGED, SELECT_STORY } from '@storybook/core-events'; import { toId } from '@storybook/csf'; import { logger } from '@storybook/client-logger'; +import { PARAM_KEY } from './constants'; interface ParamsId { storyId: string; @@ -74,7 +75,7 @@ export const linkTo = ( }; export const hrefTo = (kind: string, name: string): Promise<string> => { - return new Promise(resolve => { + return new Promise((resolve) => { const { storyId } = storyStore.getSelection(); const current = storyStore.fromId(storyId); resolve(generateUrl(toId(kind || current.kind, name))); @@ -109,8 +110,12 @@ const off = () => { } }; -export const withLinks = (storyFn: () => void) => { - on(); - addons.getChannel().once(STORY_CHANGED, off); - return storyFn(); -}; +export const withLinks = makeDecorator({ + name: 'withLinks', + parameterName: PARAM_KEY, + wrapper: (getStory, context, { parameters }) => { + on(); + addons.getChannel().once(STORY_CHANGED, off); + return getStory(context); + }, +}); diff --git a/addons/links/src/react/components/RoutedLink.js b/addons/links/src/react/components/RoutedLink.js index a78b69f48a95..7f83ddec6a51 100644 --- a/addons/links/src/react/components/RoutedLink.js +++ b/addons/links/src/react/components/RoutedLink.js @@ -10,11 +10,11 @@ import React from 'react'; const LEFT_BUTTON = 0; // Cmd/Ctrl/Shift/Alt + Click should trigger default browser behaviour. Same applies to non-left clicks -const isPlainLeftClick = e => +const isPlainLeftClick = (e) => e.button === LEFT_BUTTON && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey; export default class RoutedLink extends React.Component { - onClick = e => { + onClick = (e) => { const { onClick } = this.props; if (isPlainLeftClick(e)) { diff --git a/addons/links/src/react/components/link.test.js b/addons/links/src/react/components/link.test.js index b38b6bf2bc9d..fd174ebe9d11 100644 --- a/addons/links/src/react/components/link.test.js +++ b/addons/links/src/react/components/link.test.js @@ -14,6 +14,7 @@ jest.mock('global', () => ({ search: 'search', }, }, + window: global, __STORYBOOK_STORY_STORE__: { getSelection: jest.fn(() => ({ id: 1 })), fromId: jest.fn(() => ({})), diff --git a/addons/links/src/react/components/link.tsx b/addons/links/src/react/components/link.tsx index 5627923cb24e..fccca70d04a5 100644 --- a/addons/links/src/react/components/link.tsx +++ b/addons/links/src/react/components/link.tsx @@ -66,7 +66,7 @@ export default class LinkTo extends PureComponent<Props, State> { const { href } = this.state; return ( - <a href={href} onClick={e => cancelled(e, this.handleClick)} {...rest}> + <a href={href} onClick={(e) => cancelled(e, this.handleClick)} {...rest}> {children} </a> ); diff --git a/addons/links/src/register.ts b/addons/links/src/register.ts new file mode 100644 index 000000000000..5c082e8a04fb --- /dev/null +++ b/addons/links/src/register.ts @@ -0,0 +1,12 @@ +import addons from '@storybook/addons'; + +import EVENTS, { ADDON_ID } from './constants'; + +addons.register(ADDON_ID, (api) => { + const channel = addons.getChannel(); + + channel.on(EVENTS.REQUEST, ({ kind, name }) => { + const id = api.storyId(kind, name); + api.emit(EVENTS.RECEIVE, id); + }); +}); diff --git a/addons/notes/README.md b/addons/notes/README.md deleted file mode 100644 index 15f1ed96edd5..000000000000 --- a/addons/notes/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Storybook Addon Notes - -Storybook Addon Notes allows you to write notes (text or HTML) for your stories in [Storybook](https://storybook.js.org). - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -![Storybook Addon Notes Demo](docs/demo.png) - -## Getting Started - -**NOTE: Documentation on master branch is for alpha version, stable release is on [master](https://github.com/storybookjs/storybook/tree/master/addons/)** - -```sh -yarn add -D @storybook/addon-notes -``` - -within `.storybook/main.js`: - -```js -module.exports = { - addons: ['@storybook/addon-notes/register'] -} -``` - -Alternatively register the notes addon into a panel. Choose only one, not both. - -```js -module.exports = { - addons: ['@storybook/addon-notes/register-panel'] -} -``` - -Now, you can use the `notes` parameter to add a note to each story. - - -```js -import Component from './Component'; - -export default { - title: 'Component', - parameters: { - notes: 'some documentation here', - }, -}; -``` - -## Using Markdown - -Using Markdown in your notes is supported, Storybook will load Markdown as raw by default. - -```js -import Component from './Component'; -import markdown from './someMarkdownText.md'; - -export default { - title: 'Component', -}; - -export const withMarkdown = () => <Component />; -withmarkdown.story = { - parameters: { - notes: { markdown }, - } -}; -``` - -## Giphy - -When using Markdown, you can also embed gifs from Giphy into your Markdown. Currently, the value `cheese` of the query prop is used to search and return the first result returned by Giphy. - -```md -# Title - -<Giphy query='cheese' /> -``` - -## Multiple Notes Sections - -If you need to display different notes for different consumers of your storybook (e.g design, developers), you can configure multiple notes pages. The following will render a tab with unique notes for both `Introduction` and `Design`. - -```js -import { storiesOf } from '@storybook/react'; -import Component from './Component'; - -import intro from './intro.md'; -import design from './design.md'; - -export default { - title: 'Component', - parameters: { - notes: { Introduction: intro, 'Design Notes': design }, - }, -}; -``` diff --git a/addons/notes/docs/demo.png b/addons/notes/docs/demo.png deleted file mode 100644 index 1584dcef9ad5..000000000000 Binary files a/addons/notes/docs/demo.png and /dev/null differ diff --git a/addons/notes/package.json b/addons/notes/package.json deleted file mode 100644 index 6744aa425139..000000000000 --- a/addons/notes/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@storybook/addon-notes", - "version": "5.3.0-rc.0", - "description": "Write notes for your Storybook stories.", - "keywords": [ - "addon", - "notes", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/notes", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/notes" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/public_api.js", - "types": "dist/public_api.d.ts", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/router": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", - "core-js": "^3.0.1", - "global": "^4.3.2", - "markdown-to-jsx": "^6.10.3", - "memoizerific": "^1.11.3", - "prop-types": "^15.7.2", - "util-deprecate": "^1.0.2" - }, - "devDependencies": { - "@types/prop-types": "^15.5.9", - "@types/util-deprecate": "^1.0.0", - "@types/webpack-env": "^1.14.0" - }, - "peerDependencies": { - "react": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/notes/register-panel.js b/addons/notes/register-panel.js deleted file mode 100644 index b345979c2c39..000000000000 --- a/addons/notes/register-panel.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/register.js').default('panel'); diff --git a/addons/notes/register.js b/addons/notes/register.js deleted file mode 100644 index 752a851655c2..000000000000 --- a/addons/notes/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/register.js').default('tab'); diff --git a/addons/notes/src/Panel.test.js b/addons/notes/src/Panel.test.js deleted file mode 100644 index 13ef69d336c9..000000000000 --- a/addons/notes/src/Panel.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { shallow, mount } from 'enzyme'; -import { Link } from '@reach/router'; -import { SyntaxHighlighter as SyntaxHighlighterBase } from '@storybook/components'; -import { SyntaxHighlighter, NotesLink } from './Panel'; - -describe('NotesPanel', () => { - describe('SyntaxHighlighter component', () => { - it('should return code if className is undefined', () => { - const wrapper = shallow(<SyntaxHighlighter>some text</SyntaxHighlighter>); - const code = wrapper.find('code'); - expect(code.exists()).toBeTruthy(); - expect(code.text()).toBe('some text'); - }); - it('should return SyntaxHighlighterBase if there is a className prop', () => { - const wrapper = shallow( - <SyntaxHighlighter className="lang-jsx">some text</SyntaxHighlighter> - ); - const syntaxHighlighterBase = wrapper.find(SyntaxHighlighterBase); - expect(syntaxHighlighterBase.exists()).toBeTruthy(); - expect(syntaxHighlighterBase.prop('language')).toBe('jsx'); - }); - }); - - describe('NotesLink component', () => { - it('should render storybook links with @storybook/router Link', () => { - const component = mount( - <NotesLink href="/story/addon-notes" title="title"> - Storybook Link - </NotesLink> - ); - expect(component.find(Link).prop('to')).toBe('/?path=/story/addon-notes'); - expect(component.find(Link).prop('title')).toBe('title'); - }); - it('should render absolute links as <a>', () => { - const component = mount( - <NotesLink href="https://example.com" title="title"> - Storybook Link - </NotesLink> - ); - expect(component.find('a').prop('href')).toBe('https://example.com'); - expect(component.find('a').prop('title')).toBe('title'); - }); - }); -}); diff --git a/addons/notes/src/Panel.tsx b/addons/notes/src/Panel.tsx deleted file mode 100644 index d13879abd9f7..000000000000 --- a/addons/notes/src/Panel.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { ReactElement, Fragment, ReactNode } from 'react'; -import { types } from '@storybook/addons'; -import { API, Consumer, Combo } from '@storybook/api'; -import { Link as RouterLink } from '@storybook/router'; -import { styled } from '@storybook/theming'; - -import { - SyntaxHighlighter as SyntaxHighlighterBase, - Placeholder, - DocumentWrapper, - Link, - TabWrapper, - TabsState, -} from '@storybook/components'; -import Markdown from 'markdown-to-jsx'; -import Giphy from './giphy'; - -import { formatter } from './formatter'; - -import { PARAM_KEY, Parameters } from './shared'; - -const Panel = styled.div<{}>(({ theme }) => ({ - padding: '3rem 40px', - boxSizing: 'border-box', - width: '100%', - maxWidth: 980, - margin: '0 auto', - ...(theme.addonNotesTheme || {}), -})); - -interface Props { - active: boolean; - api: API; -} - -function read(param: Parameters | undefined): Record<string, string> | string | undefined { - if (!param) { - return undefined; - } - if (typeof param === 'string') { - return param; - } - if ('disable' in param) { - return undefined; - } - if ('text' in param) { - return param.text; - } - if ('markdown' in param) { - return param.markdown; - } - if (typeof param === 'object') { - return param; - } - return undefined; -} - -interface SyntaxHighlighterProps { - className?: string; - children: ReactElement; - [key: string]: any; -} -export const SyntaxHighlighter = ({ className, children, ...props }: SyntaxHighlighterProps) => { - // markdown-to-jsx does not add className to inline code - if (typeof className !== 'string') { - return <code>{children}</code>; - } - // className: "lang-jsx" - const language = className.split('-'); - return ( - <SyntaxHighlighterBase - language={language[1] || 'plaintext'} - bordered - format={false} - copyable - {...props} - > - {children} - </SyntaxHighlighterBase> - ); -}; - -interface NotesLinkProps { - href: string; - children: ReactElement; -} -export const NotesLink = ({ href, children, ...props }: NotesLinkProps) => { - /* https://github.com/sindresorhus/is-absolute-url/blob/master/index.js */ - const isAbsoluteUrl = /^[a-z][a-z0-9+.-]*:/.test(href); - const isAnchorUrl = /^#.*/.test(href); - - if (isAbsoluteUrl || isAnchorUrl) { - return ( - <a href={href} {...props}> - {children} - </a> - ); - } - - return ( - <RouterLink to={href} {...props}> - {children} - </RouterLink> - ); -}; - -// use our SyntaxHighlighter component in place of a <code> element when -// converting markdown to react elements -const defaultOptions = { - overrides: { - code: SyntaxHighlighter, - a: NotesLink, - Giphy: { - component: Giphy, - }, - }, -}; - -interface Overrides { - overrides: { - [type: string]: ReactNode; - }; -} -type Options = typeof defaultOptions & Overrides; - -const mapper = ({ - state, - api, -}: Combo): { value?: string | Record<string, string>; options: Options } => { - const extraElements = Object.entries(api.getElements(types.NOTES_ELEMENT)).reduce( - (acc, [k, v]) => ({ ...acc, [k]: v.render }), - {} - ); - const options = { - ...defaultOptions, - overrides: { ...defaultOptions.overrides, ...extraElements }, - }; - - const story = state.storiesHash[state.storyId]; - const value = read(story ? api.getParameters(story.id, PARAM_KEY) : undefined); - - return { options, value }; -}; - -const NotesPanel = ({ active }: Props) => { - if (!active) { - return null; - } - - return ( - <Consumer filter={mapper}> - {({ options, value }: { options: Options; value?: string | Record<string, string> }) => { - if (!value) { - return ( - <Placeholder> - <Fragment>No notes yet</Fragment> - <Fragment> - Learn how to  - <Link - href="https://github.com/storybookjs/storybook/tree/master/addons/notes" - target="_blank" - withArrow - secondary - cancel={false} - > - document components in Markdown - </Link> - </Fragment> - </Placeholder> - ); - } - - if (typeof value === 'string' || Object.keys(value).length === 1) { - const md = typeof value === 'object' ? Object.values(value)[0] : value; - - return ( - <Panel className="addon-notes-container"> - <DocumentWrapper> - <Markdown options={options}>{formatter(md)}</Markdown> - </DocumentWrapper> - </Panel> - ); - } - - const groups: { title: string; render: (props: { active: boolean }) => void }[] = []; - - Object.entries(value).forEach(([title, docs]) => { - groups.push({ - title, - render: ({ active: isActive }) => ( - <TabWrapper key={title} active={isActive}> - <Panel> - <DocumentWrapper> - <Markdown options={options}>{formatter(docs)}</Markdown> - </DocumentWrapper> - </Panel> - </TabWrapper> - ), - }); - }); - - return ( - <div className="addon-notes-container"> - <TabsState> - {groups.map(group => ( - <div id={group.title} key={group.title} title={group.title}> - {group.render} - </div> - ))} - </TabsState> - </div> - ); - }} - </Consumer> - ); -}; - -export default NotesPanel; diff --git a/addons/notes/src/formatter.ts b/addons/notes/src/formatter.ts deleted file mode 100644 index cb2f406fb710..000000000000 --- a/addons/notes/src/formatter.ts +++ /dev/null @@ -1,26 +0,0 @@ -import memoize from 'memoizerific'; - -export const formatter = memoize(2)((code: string) => { - // code provided to the component is often coming from template literals, which preserve whitespace. - // sometimes the first line doesn't have padding, but the second does. - // we split the code-string into lines, then if we find padding on line 0 or 1, - // we assume that padding is bad, and remove that much padding on all following lines - return code - .split(/\n/) - .reduce( - (acc, i, index) => { - const match = i.match(/^((:?\s|\t)+)/); - const padding = match ? match[1] : ''; - - if (acc.firstIndent === '' && padding && index < 3) { - return { result: `${acc.result}\n${i.replace(padding, '')}`, firstIndent: padding }; - } - return { - result: `${acc.result}\n${i.replace(acc.firstIndent, '').replace(/\s*$/, '')}`, - firstIndent: acc.firstIndent, - }; - }, - { firstIndent: '', result: '' } - ) - .result.trim(); -}); diff --git a/addons/notes/src/giphy.tsx b/addons/notes/src/giphy.tsx deleted file mode 100644 index 1111ce453564..000000000000 --- a/addons/notes/src/giphy.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { fetch } from 'global'; -import React, { Component } from 'react'; - -import { logger } from '@storybook/client-logger'; - -interface Props { - query: string; -} -interface State { - src: string | null; -} -export default class Giphy extends Component<Props, State> { - state: State = { - src: null, - }; - - componentDidMount() { - const { query } = this.props; - // TODO: replace this api_key, and make it configurable - // note: I have requested a production api_key: - // it's pending: bluXZc8ZAre19mvTtVi900CdsJhbVTEK - fetch(`http://api.giphy.com/v1/gifs/search?limit=1&api_key=dc6zaTOxFJmzC&q=${query}`) - .then((response: { ok: any; json: () => void }) => response.ok && response.json()) - .then((data: { data: { images: { original: { url: string } } }[] }) => { - this.setState({ - src: data.data[0].images.original.url, - }); - }) - .catch((e: any) => logger.error(e)); - } - - render() { - const { src } = this.state; - // TODO: we should have a nice looking <Img /> component - return src ? <img src={src} alt="" /> : null; - } -} diff --git a/addons/notes/src/index.ts b/addons/notes/src/index.ts deleted file mode 100644 index f1ed9aea6602..000000000000 --- a/addons/notes/src/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { makeDecorator, StoryContext, StoryGetter, WrapperSettings } from '@storybook/addons'; -import deprecate from 'util-deprecate'; - -// todo resolve any after @storybook/addons and @storybook/channels are migrated to TypeScript -export const withNotes = makeDecorator({ - name: 'withNotes', - parameterName: 'notes', - skipIfNoParametersOrOptions: true, - allowDeprecatedUsage: true, - - wrapper: deprecate( - (getStory: StoryGetter, context: StoryContext, { options, parameters }: WrapperSettings) => { - const storyOptions = parameters || options; - - const { text, markdown } = - typeof storyOptions === 'string' - ? { - text: storyOptions, - markdown: undefined, - } - : storyOptions; - - if (!text && !markdown) { - throw new Error( - `Parameter 'notes' must must be a string or an object with 'text' or 'markdown' properties` - ); - } - - return getStory(context); - }, - 'withNotes is deprecated' - ), -}); - -export const withMarkdownNotes = deprecate((text: string, options: any) => {}, -'withMarkdownNotes is deprecated'); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/notes/src/public_api.ts b/addons/notes/src/public_api.ts deleted file mode 100644 index dc0dc8965a31..000000000000 --- a/addons/notes/src/public_api.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '.'; diff --git a/addons/notes/src/register.tsx b/addons/notes/src/register.tsx deleted file mode 100644 index a5c6c0bf896a..000000000000 --- a/addons/notes/src/register.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react'; -import addons, { types } from '@storybook/addons'; - -import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared'; - -// TODO: fix eslint in tslint (igor said he fixed it, should ask him) -import Panel from './Panel'; - -export default function register(type: types) { - addons.register(ADDON_ID, api => { - addons.add(PANEL_ID, { - type, - title: 'Notes', - route: ({ storyId }) => `/info/${storyId}`, // todo add type - match: ({ viewMode }) => viewMode === 'info', // todo add type - render: ({ active, key }) => <Panel api={api} active={active} key={key} />, - paramKey: PARAM_KEY, - }); - }); -} diff --git a/addons/notes/src/shared.ts b/addons/notes/src/shared.ts deleted file mode 100644 index 708819358328..000000000000 --- a/addons/notes/src/shared.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const ADDON_ID = 'storybookjs/notes'; -export const PANEL_ID = `${ADDON_ID}/panel`; -export const PARAM_KEY = `notes`; - -interface TextParameter { - text: string; -} -interface MarkdownParameter { - markdown: string; -} -interface DisabledParameter { - disable: boolean; -} -type TabsParameter = Record<string, string>; - -export type Parameters = - | string - | TextParameter - | MarkdownParameter - | DisabledParameter - | TabsParameter; diff --git a/addons/notes/src/typings.d.ts b/addons/notes/src/typings.d.ts deleted file mode 100644 index 7d209326fbef..000000000000 --- a/addons/notes/src/typings.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// There are no types for markdown-to-jsx -declare module 'markdown-to-jsx' { - const Markdown: any; - export default Markdown; -} - -declare module 'global'; diff --git a/addons/notes/tsconfig.json b/addons/notes/tsconfig.json deleted file mode 100644 index 8876bb6737a1..000000000000 --- a/addons/notes/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] -} diff --git a/addons/ondevice-actions/README.md b/addons/ondevice-actions/README.md deleted file mode 100644 index 5853c8695b0f..000000000000 --- a/addons/ondevice-actions/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Storybook Actions Addon for react-native - -Storybook Actions Addon allows you to log events/actions inside stories in [Storybook](https://storybook.js.org). - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -**This addon is a wrapper for addon [@storybook/addon-actions](https://github.com/storybookjs/storybook/blob/master/addons/actions). -Refer to its documentation to understand how to use actions** - -## Installation - -```sh -yarn add -D @storybook/addon-ondevice-actions @storybook/addon-actions -``` - -## Configuration - -Create a file called `rn-addons.js` in your storybook config. - -Add following content to it: - -```js -import '@storybook/addon-ondevice-actions/register'; -``` - -Then import `rn-addons.js` next to your `getStorybookUI` call. - -```js -import './rn-addons'; -``` - -See [@storybook/addon-actions](https://github.com/storybookjs/storybook/blob/master/addons/actions) to learn how to write stories with actions and the [crna-kitchen-sink app](../../examples-native/crna-kitchen-sink) for more examples. diff --git a/addons/ondevice-actions/package.json b/addons/ondevice-actions/package.json deleted file mode 100644 index 3028b4a5bc9f..000000000000 --- a/addons/ondevice-actions/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-actions", - "version": "5.3.0-rc.0", - "description": "Action Logger addon for react-native storybook", - "keywords": [ - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/actions", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "core-js": "^3.0.1", - "fast-deep-equal": "^2.0.1" - }, - "devDependencies": { - "@storybook/addon-actions": "5.3.0-rc.0" - }, - "peerDependencies": { - "@storybook/addon-actions": "*", - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/ondevice-actions/register.js b/addons/ondevice-actions/register.js deleted file mode 100644 index e69edbea3ed1..000000000000 --- a/addons/ondevice-actions/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist').register(); diff --git a/addons/ondevice-actions/src/components/ActionLogger/Inspect.tsx b/addons/ondevice-actions/src/components/ActionLogger/Inspect.tsx deleted file mode 100644 index 158fbc2a0aee..000000000000 --- a/addons/ondevice-actions/src/components/ActionLogger/Inspect.tsx +++ /dev/null @@ -1,177 +0,0 @@ -/* eslint-disable react/no-array-index-key */ -/* eslint-disable no-nested-ternary */ -import React, { Component } from 'react'; -import { Button, View, Text } from 'react-native'; - -const theme = { - OBJECT_PREVIEW_ARRAY_MAX_PROPERTIES: 10, - OBJECT_PREVIEW_OBJECT_MAX_PROPERTIES: 5, - OBJECT_NAME_COLOR: 'rgb(136, 19, 145)', - OBJECT_VALUE_NULL_COLOR: 'rgb(128, 128, 128)', - OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(128, 128, 128)', - 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_NUMBER_COLOR: 'rgb(28, 0, 207)', - OBJECT_VALUE_BOOLEAN_COLOR: 'rgb(28, 0, 207)', - OBJECT_VALUE_FUNCTION_PREFIX_COLOR: 'rgb(13, 34, 170)', - - ARROW_COLOR: '#6e6e6e', - ARROW_MARGIN_RIGHT: 3, - ARROW_FONT_SIZE: 12, - ARROW_ANIMATION_DURATION: '0', -}; - -class Inspect extends Component<{ name?: string; value: any }, { expanded: boolean }> { - state = { expanded: false }; - - render() { - const { name, value } = this.props; - const { expanded } = this.state; - const toggle = ( - <View style={{ width: 40, height: 40 }}> - {name && - ((Array.isArray(value) && value.length) || - (value && - typeof value === 'object' && - !Array.isArray(value) && - Object.keys(value).length)) ? ( - <Button - onPress={() => this.setState(s => ({ expanded: !s.expanded }))} - title={!expanded ? '▶' : '▼'} - /> - ) : null} - </View> - ); - - const nameComponent = name ? ( - <Text style={{ color: theme.OBJECT_NAME_COLOR }}>{name}</Text> - ) : null; - - if (Array.isArray(value)) { - if (name) { - return ( - <> - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - {toggle} - {nameComponent} - <Text>{`: ${value.length === 0 ? '[]' : expanded ? '[' : '[...]'}`}</Text> - </View> - {expanded ? ( - <View style={{ marginLeft: 40 }}> - {value.map((v, i) => ( - <View key={i} style={{ marginLeft: 40 }}> - <Inspect value={v} /> - </View> - ))} - <View style={{ marginLeft: 20 }}> - <Text>]</Text> - </View> - </View> - ) : null} - </> - ); - } - return ( - <View> - <Text>[</Text> - {value.map((v, i) => ( - <View key={i} style={{ marginLeft: 20 }}> - <Inspect value={v} /> - </View> - ))} - <Text>]</Text> - </View> - ); - } - if (value && typeof value === 'object' && !(value instanceof RegExp)) { - if (name) { - return ( - <> - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - {toggle} - {nameComponent} - <Text> - {`: ${Object.keys(value).length === 0 ? '{}' : expanded ? '{' : '{...}'}`} - </Text> - </View> - {expanded ? ( - <View style={{ marginLeft: 40 }}> - {Object.entries(value).map(([key, v]) => ( - <View key={key}> - <Inspect name={key} value={v} /> - </View> - ))} - <View style={{ marginLeft: 20 }}> - <Text>{'}'}</Text> - </View> - </View> - ) : null} - </> - ); - } - return ( - <View> - <Text>{'{'}</Text> - {Object.entries(value).map(([key, v]) => ( - <View key={key}> - <Inspect name={key} value={v} /> - </View> - ))} - <Text>{'}'}</Text> - </View> - ); - } - if (name) { - return ( - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - {toggle} - {nameComponent} - <Text>: </Text> - <Value value={value} /> - </View> - ); - } - return ( - <View> - <Value value={value} /> - </View> - ); - } -} - -function Value({ value }: { value: any }) { - if (value === null) { - return <Text style={{ color: theme.OBJECT_VALUE_NULL_COLOR }}>null</Text>; - } - if (value === undefined) { - return <Text style={{ color: theme.OBJECT_VALUE_UNDEFINED_COLOR }}>undefined</Text>; - } - if (value instanceof RegExp) { - return ( - <Text style={{ color: theme.OBJECT_VALUE_REGEXP_COLOR }}> - {`/${value.source}/${value.flags}`} - </Text> - ); - } - switch (typeof value) { - case 'string': - return ( - <Text style={{ color: theme.OBJECT_VALUE_STRING_COLOR }}>{JSON.stringify(value)}</Text> - ); - case 'number': - return ( - <Text style={{ color: theme.OBJECT_VALUE_NUMBER_COLOR }}>{JSON.stringify(value)}</Text> - ); - case 'boolean': - return ( - <Text style={{ color: theme.OBJECT_VALUE_BOOLEAN_COLOR }}>{JSON.stringify(value)}</Text> - ); - case 'function': - return <Text style={{ color: theme.OBJECT_VALUE_FUNCTION_PREFIX_COLOR }}>[Function]</Text>; - default: - return <Text>{JSON.stringify(value)}</Text>; - } -} - -export default Inspect; diff --git a/addons/ondevice-actions/src/components/ActionLogger/index.tsx b/addons/ondevice-actions/src/components/ActionLogger/index.tsx deleted file mode 100644 index 1081214c3bf3..000000000000 --- a/addons/ondevice-actions/src/components/ActionLogger/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { Button, View, Text, ScrollView } from 'react-native'; -import { ActionDisplay } from '@storybook/addon-actions'; -import Inspect from './Inspect'; - -interface ActionLoggerProps { - actions: ActionDisplay[]; - onClear: () => void; -} - -export const ActionLogger: FunctionComponent<ActionLoggerProps> = ({ actions, onClear }) => ( - <ScrollView> - <ScrollView horizontal> - <View> - {actions.map((action: ActionDisplay) => ( - <View key={action.id} style={{ flexDirection: 'row' }}> - <View>{action.count > 1 ? <Text>{action.count}</Text> : null}</View> - <View style={{ flexGrow: 1 }}> - <Inspect name={action.data.name} value={action.data.args || action.data} /> - </View> - </View> - ))} - </View> - </ScrollView> - <View> - <Button onPress={onClear} title="CLEAR" /> - </View> - </ScrollView> -); - -export default ActionLogger; diff --git a/addons/ondevice-actions/src/containers/ActionLogger/index.tsx b/addons/ondevice-actions/src/containers/ActionLogger/index.tsx deleted file mode 100644 index 9ea3ffa3c0c7..000000000000 --- a/addons/ondevice-actions/src/containers/ActionLogger/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { Component } from 'react'; -import deepEqual from 'fast-deep-equal'; - -import { addons } from '@storybook/addons'; -import { SELECT_STORY } from '@storybook/core-events'; -import { ActionDisplay, EVENT_ID } from '@storybook/addon-actions'; - -import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger'; - -interface ActionLoggerProps { - active: boolean; -} - -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 channel = addons.getChannel(); - - constructor(props: ActionLoggerProps) { - super(props); - - this.state = { actions: [] }; - } - - componentDidMount() { - this.channel.addListener(EVENT_ID, this.addAction); - this.channel.addListener(SELECT_STORY, this.handleStoryChange); - } - - componentWillUnmount() { - this.channel.removeListener(SELECT_STORY, this.handleStoryChange); - this.channel.removeListener(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; - } -} diff --git a/addons/ondevice-actions/src/index.tsx b/addons/ondevice-actions/src/index.tsx deleted file mode 100644 index 657f6d2dc38f..000000000000 --- a/addons/ondevice-actions/src/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import addons from '@storybook/addons'; -import { ADDON_ID, PANEL_ID, PARAM_KEY } from '@storybook/addon-actions'; -import ActionLogger from './containers/ActionLogger'; - -export function register() { - addons.register(ADDON_ID, () => { - addons.addPanel(PANEL_ID, { - title: 'Actions', - render: ({ active, key }) => <ActionLogger key={key} active={active} />, - paramKey: PARAM_KEY, - }); - }); -} diff --git a/addons/ondevice-actions/tsconfig.json b/addons/ondevice-actions/tsconfig.json deleted file mode 100644 index 9b69fbfdaed4..000000000000 --- a/addons/ondevice-actions/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["src/__tests__/**/*"] -} diff --git a/addons/ondevice-backgrounds/README.md b/addons/ondevice-backgrounds/README.md deleted file mode 100644 index 24527443d5a2..000000000000 --- a/addons/ondevice-backgrounds/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Storybook Backgrounds Addon for react-native - -Storybook Backgrounds Addon for react-native can be used to change background colors of your stories right from the device. - -<img src="docs/demo.gif" alt="Storybook Backgrounds Addon Demo" width="400" /> - -## Installation - -```sh -yarn add -D @storybook/addon-ondevice-backgrounds -``` - -## Configuration - -Create a file called `rn-addons.js` in your storybook config. - -Add following content to it: - -```js -import '@storybook/addon-ondevice-backgrounds/register'; -``` - -Then import `rn-addons.js` next to your `getStorybookUI` call. - -```js -import './rn-addons'; -``` - -## Usage - -react-native users will have to import `storiesOf` from `@storybook/react-native` and are required to add the `withBackgrounds` decorator. - -Then write your stories like this: - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react-native'; -import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds'; - -addDecorator(withBackgrounds); - -storiesOf('Button', module) - .addParameters({ - backgrounds: [ - { name: 'dark', value: '#222222' }, - { name: 'light', value: '#eeeeee', default: true }, - ], - }) - .add('with text', () => <Text>Click me</Text>); -``` - -See [web backgrounds addon](../backgrounds#usage) for detailed usage and the [crna-kitchen-sink app](../../examples-native/crna-kitchen-sink) for more examples. diff --git a/addons/ondevice-backgrounds/docs/demo.gif b/addons/ondevice-backgrounds/docs/demo.gif deleted file mode 100644 index b36edd000016..000000000000 Binary files a/addons/ondevice-backgrounds/docs/demo.gif and /dev/null differ diff --git a/addons/ondevice-backgrounds/package.json b/addons/ondevice-backgrounds/package.json deleted file mode 100644 index 6281630a3c41..000000000000 --- a/addons/ondevice-backgrounds/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-backgrounds", - "version": "5.3.0-rc.0", - "description": "A react-native storybook addon to show different backgrounds for your preview", - "keywords": [ - "addon", - "background", - "react", - "storybook" - ], - "homepage": "https://storybook.js.org", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/ondevice-backgrounds" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "core-js": "^3.0.1", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/ondevice-backgrounds/register.js b/addons/ondevice-backgrounds/register.js deleted file mode 100644 index cc38cb06f1f2..000000000000 --- a/addons/ondevice-backgrounds/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/register'); diff --git a/addons/ondevice-backgrounds/src/BackgroundPanel.tsx b/addons/ondevice-backgrounds/src/BackgroundPanel.tsx deleted file mode 100644 index 8d36e6491f94..000000000000 --- a/addons/ondevice-backgrounds/src/BackgroundPanel.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-disable react/destructuring-assignment, import/no-extraneous-dependencies */ -import React, { Component } from 'react'; -import { View, Text } from 'react-native'; -import Events from '@storybook/core-events'; -import { AddonStore } from '@storybook/addons'; -import { API } from '@storybook/api'; -import { StoryStore } from '@storybook/client-api'; - -import Swatch from './Swatch'; -import BackgroundEvents, { PARAM_KEY } from './constants'; -import { Background } from './index'; - -const codeSample = ` -import { storiesOf } from '@storybook/react-native'; -import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds'; - -addDecorator(withBackgrounds); - -storiesOf('First Component', module) - .addParameters({ - backgrounds: [ - { name: 'warm', value: 'hotpink', default: true }, - { name: 'cool', value: 'deepskyblue' }, - ], - }) - .add("First Button", () => <Button>Click me</Button>); -`.trim(); - -const Instructions = () => ( - <View> - <Text style={{ fontSize: 16 }}>Setup Instructions</Text> - <Text> - Please add the background decorator definition to your story. The background decorate accepts - an array of items, which should include a name for your color (preferably the css class name) - and the corresponding color / image value. - </Text> - <Text> - Below is an example of how to add the background decorator to your story definition. - </Text> - <Text>{codeSample}</Text> - </View> -); - -export type Channel = ReturnType<AddonStore['getChannel']>; -type Selection = ReturnType<StoryStore['fromId']>; -interface BackgroundPanelProps { - channel: Channel; - api: API; - active: boolean; -} - -interface BackgroundPanelState { - selection: Selection; -} - -export default class BackgroundPanel extends Component<BackgroundPanelProps, BackgroundPanelState> { - componentDidMount() { - this.props.channel.on(Events.SELECT_STORY, this.onStorySelected); - } - - componentWillUnmount() { - this.props.channel.removeListener(Events.SELECT_STORY, this.onStorySelected); - } - - setBackgroundFromSwatch = (background: string) => { - this.props.channel.emit(BackgroundEvents.UPDATE_BACKGROUND, background); - }; - - onStorySelected = (selection: Selection) => { - this.setState({ selection }); - }; - - render() { - const { active, api } = this.props; - - if (!active || !this.state) { - return null; - } - - const story = api - .store() - .getStoryAndParameters(this.state.selection.kind, this.state.selection.story); - const backgrounds: Background[] = story.parameters[PARAM_KEY]; - - return ( - <View> - {backgrounds ? ( - backgrounds.map(({ value, name }) => ( - <View key={`${name} ${value}`}> - <Swatch value={value} name={name} setBackground={this.setBackgroundFromSwatch} /> - </View> - )) - ) : ( - <Instructions /> - )} - </View> - ); - } -} diff --git a/addons/ondevice-backgrounds/src/Swatch.tsx b/addons/ondevice-backgrounds/src/Swatch.tsx deleted file mode 100644 index cd55fe629b18..000000000000 --- a/addons/ondevice-backgrounds/src/Swatch.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import PropTypes from 'prop-types'; -import { TouchableOpacity, View, Text } from 'react-native'; - -interface SwatchProps { - name: string; - value: string; - setBackground: (background: string) => void; -} - -const Swatch: FunctionComponent<SwatchProps> = ({ name, value, setBackground }) => ( - <TouchableOpacity - style={{ - borderRadius: 4, - borderWidth: 1, - borderColor: 'rgba(0,0,0,0.2)', - marginTop: 10, - marginBottom: 20, - marginHorizontal: 10, - }} - onPress={() => setBackground(value)} - > - <View style={{ flex: 1, backgroundColor: value, height: 40 }} /> - <View style={{ padding: 4, flexDirection: 'row', justifyContent: 'space-between' }}> - <Text>{name}:</Text> - <Text>{value}</Text> - </View> - </TouchableOpacity> -); - -Swatch.propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - setBackground: PropTypes.func.isRequired, -}; - -export default Swatch; diff --git a/addons/ondevice-backgrounds/src/constants.ts b/addons/ondevice-backgrounds/src/constants.ts deleted file mode 100644 index d28430f5e05c..000000000000 --- a/addons/ondevice-backgrounds/src/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const PARAM_KEY = 'backgrounds'; -export const ADDON_ID = 'storybook-addon-background'; -export const PANEL_ID = `${ADDON_ID}/background-panel`; - -export default { - SET: `${ADDON_ID}:set`, - UNSET: `${ADDON_ID}:unset`, - UPDATE_BACKGROUND: `${ADDON_ID}:update`, -}; diff --git a/addons/ondevice-backgrounds/src/container.tsx b/addons/ondevice-backgrounds/src/container.tsx deleted file mode 100644 index ba57b6d4fd5c..000000000000 --- a/addons/ondevice-backgrounds/src/container.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component } from 'react'; -import { View } from 'react-native'; -import Constants from './constants'; -import { Channel } from './BackgroundPanel'; - -interface ContainerProps { - initialBackground: string; - channel: Channel; -} - -interface ContainerState { - background: string; -} - -export default class Container extends Component<ContainerProps, ContainerState> { - constructor(props: ContainerProps) { - super(props); - this.state = { background: props.initialBackground || '' }; - } - - componentDidMount() { - const { channel } = this.props; - channel.on(Constants.UPDATE_BACKGROUND, this.onBackgroundChange); - } - - componentWillUnmount() { - const { channel } = this.props; - channel.removeListener(Constants.UPDATE_BACKGROUND, this.onBackgroundChange); - } - - onBackgroundChange = (background: string) => { - this.setState({ background }); - }; - - render() { - const { background } = this.state; - const { children } = this.props; - - return ( - <View style={{ flex: 1, backgroundColor: background || 'transparent' }}>{children}</View> - ); - } -} diff --git a/addons/ondevice-backgrounds/src/index.tsx b/addons/ondevice-backgrounds/src/index.tsx deleted file mode 100644 index f9d1c8a0b848..000000000000 --- a/addons/ondevice-backgrounds/src/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; - -import addons, { makeDecorator } from '@storybook/addons'; - -import Events from './constants'; -import Container from './container'; - -export interface Background { - name: string; - value: string; - default?: boolean; -} - -export const withBackgrounds = makeDecorator({ - name: 'withBackgrounds', - parameterName: 'backgrounds', - skipIfNoParametersOrOptions: true, - allowDeprecatedUsage: true, - wrapper: (getStory, context, { options, parameters }) => { - const data = parameters || options || []; - const backgrounds: Background[] = Array.isArray(data) ? data : Object.values(data); - - let background = 'transparent'; - if (backgrounds.length !== 0) { - addons.getChannel().emit(Events.SET, backgrounds); - - const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0]; - - if (defaultOrFirst) { - background = defaultOrFirst.value; - } - } - - return ( - <Container initialBackground={background} channel={addons.getChannel()}> - {getStory(context)} - </Container> - ); - }, -}); diff --git a/addons/ondevice-backgrounds/src/register.tsx b/addons/ondevice-backgrounds/src/register.tsx deleted file mode 100644 index 39e1046517b8..000000000000 --- a/addons/ondevice-backgrounds/src/register.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import addons from '@storybook/addons'; - -import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; -import BackgroundPanel from './BackgroundPanel'; - -addons.register(ADDON_ID, api => { - const channel = addons.getChannel(); - addons.addPanel(PANEL_ID, { - title: 'Backgrounds', - render: ({ active }) => <BackgroundPanel channel={channel} api={api} active={active} />, - paramKey: PARAM_KEY, - }); -}); diff --git a/addons/ondevice-backgrounds/tsconfig.json b/addons/ondevice-backgrounds/tsconfig.json deleted file mode 100644 index 8876bb6737a1..000000000000 --- a/addons/ondevice-backgrounds/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] -} diff --git a/addons/ondevice-knobs/README.md b/addons/ondevice-knobs/README.md deleted file mode 100644 index 3c17c1275377..000000000000 --- a/addons/ondevice-knobs/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Storybook Knobs Addon for react-native - -Storybook Knobs Addon allows you to edit react props using the Storybook UI using variables inside stories in [Storybook](https://storybook.js.org). - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -**This addon is a wrapper for addon [@storybook/addon-knobs](https://github.com/storybookjs/storybook/blob/master/addons/knobs). -Refer to its documentation to understand how to use knobs** - -## Installation - -```sh -yarn add -D @storybook/addon-ondevice-knobs @storybook/addon-knobs -``` - -## Configuration - -Create a file called `rn-addons.js` in your storybook config. - -Add following content to it: - -```js -import '@storybook/addon-ondevice-knobs/register'; -``` - -Then import `rn-addons.js` next to your `getStorybookUI` call. - -```js -import './rn-addons'; -``` - -See [@storybook/addon-knobs](https://github.com/storybookjs/storybook/blob/master/addons/knobs) to learn how to write stories with knobs and the [crna-kitchen-sink app](../../examples-native/crna-kitchen-sink) for more examples. diff --git a/addons/ondevice-knobs/package.json b/addons/ondevice-knobs/package.json deleted file mode 100644 index c5f2cc2b8dd6..000000000000 --- a/addons/ondevice-knobs/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-knobs", - "version": "5.3.0-rc.0", - "description": "Display storybook story knobs on your deviced.", - "keywords": [ - "addon", - "knobs", - "ondevice", - "react-native", - "storybook" - ], - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/ondevice-knobs" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@emotion/native": "^10.0.14", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "core-js": "^3.0.1", - "deep-equal": "^1.0.1", - "prop-types": "^15.7.2", - "react-native-color-picker": "^0.4.0", - "react-native-modal-datetime-picker": "^7.4.2", - "react-native-modal-selector": "^1.0.2", - "react-native-switch": "^1.5.0" - }, - "peerDependencies": { - "@storybook/addon-knobs": "5.3.0-rc.0", - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/ondevice-knobs/register.js b/addons/ondevice-knobs/register.js deleted file mode 100644 index 66453c4cafe9..000000000000 --- a/addons/ondevice-knobs/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/index').register(); diff --git a/addons/ondevice-knobs/src/GroupTabs.js b/addons/ondevice-knobs/src/GroupTabs.js deleted file mode 100644 index e423304468e5..000000000000 --- a/addons/ondevice-knobs/src/GroupTabs.js +++ /dev/null @@ -1,72 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { ScrollView, Text, TouchableOpacity } from 'react-native'; -import styled from '@emotion/native'; - -const Label = styled.Text(({ theme, active }) => ({ - color: active ? theme.buttonActiveTextColor : theme.buttonTextColor, - fontSize: 17, -})); - -class GroupTabs extends Component { - renderTab(name, group) { - let { title } = group; - if (typeof title === 'function') { - title = title(); - } - - const { onGroupSelect, selectedGroup } = this.props; - - return ( - <TouchableOpacity - style={{ - marginTop: 5, - marginRight: 15, - paddingBottom: 10, - }} - key={name} - onPress={() => onGroupSelect(name)} - > - <Label active={selectedGroup === name}>{title}</Label> - </TouchableOpacity> - ); - } - - render() { - const { groups } = this.props; - - const entries = groups ? Object.entries(groups) : null; - - return entries && entries.length ? ( - <ScrollView - horizontal - style={{ - marginHorizontal: 10, - flexDirection: 'row', - marginBottom: 10, - borderBottomWidth: 1, - borderBottomColor: '#ccc', - }} - > - {entries.map(([key, value]) => this.renderTab(key, value))} - </ScrollView> - ) : ( - <Text>no groups available</Text> - ); - } -} - -GroupTabs.defaultProps = { - groups: {}, - onGroupSelect: () => {}, - selectedGroup: null, -}; - -GroupTabs.propTypes = { - // eslint-disable-next-line react/forbid-prop-types - groups: PropTypes.object, - onGroupSelect: PropTypes.func, - selectedGroup: PropTypes.string, -}; - -export default GroupTabs; diff --git a/addons/ondevice-knobs/src/PropField.js b/addons/ondevice-knobs/src/PropField.js deleted file mode 100644 index dfde3288ceb6..000000000000 --- a/addons/ondevice-knobs/src/PropField.js +++ /dev/null @@ -1,49 +0,0 @@ -import PropTypes from 'prop-types'; -import { View, Text } from 'react-native'; -import React from 'react'; -import styled from '@emotion/native'; -import TypeMap from './types'; - -const InvalidType = () => <Text style={{ margin: 10 }}>Invalid Type</Text>; - -const Label = styled.Text(({ theme }) => ({ - marginLeft: 10, - fontSize: 14, - color: theme.labelColor, - fontWeight: 'bold', -})); - -const PropField = ({ onChange, onPress, knob }) => { - const InputType = TypeMap[knob.type] || InvalidType; - - return ( - <View> - {!knob.hideLabel ? <Label>{`${knob.label || knob.name}`}</Label> : null} - <InputType knob={knob} onChange={onChange} onPress={onPress} /> - </View> - ); -}; - -PropField.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - label: PropTypes.string, - value: PropTypes.any, - hideLabel: PropTypes.bool, - type: PropTypes.oneOf([ - 'text', - 'number', - 'color', - 'boolean', - 'object', - 'select', - 'array', - 'date', - 'button', - ]), - }).isRequired, - onChange: PropTypes.func.isRequired, - onPress: PropTypes.func.isRequired, -}; - -export default PropField; diff --git a/addons/ondevice-knobs/src/PropForm.js b/addons/ondevice-knobs/src/PropForm.js deleted file mode 100644 index e0c35334cf1b..000000000000 --- a/addons/ondevice-knobs/src/PropForm.js +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; -import PropField from './PropField'; - -export default class PropForm extends React.Component { - makeChangeHandler(name, type) { - return value => { - const { onFieldChange } = this.props; - const change = { name, type, value }; - onFieldChange(change); - }; - } - - render() { - const { knobs, onFieldClick } = this.props; - - return ( - <View> - {knobs.map(knob => { - const changeHandler = this.makeChangeHandler(knob.name, knob.type); - return ( - <PropField - key={knob.name} - name={knob.name} - type={knob.type} - value={knob.value} - knob={knob} - onChange={changeHandler} - onPress={onFieldClick} - /> - ); - })} - </View> - ); - } -} - -PropForm.displayName = 'PropForm'; - -PropForm.defaultProps = { - knobs: [], -}; - -PropForm.propTypes = { - knobs: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.any, - }) - ), - onFieldChange: PropTypes.func.isRequired, - onFieldClick: PropTypes.func.isRequired, -}; diff --git a/addons/ondevice-knobs/src/index.js b/addons/ondevice-knobs/src/index.js deleted file mode 100644 index 34e4793351bc..000000000000 --- a/addons/ondevice-knobs/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import addons from '@storybook/addons'; -import Panel from './panel'; - -export { withKnobs } from '@storybook/addon-knobs'; - -export function register() { - addons.register('RNKNOBS', () => { - const channel = addons.getChannel(); - addons.addPanel('RNKNOBS', { - title: 'Knobs', - // eslint-disable-next-line react/prop-types - render: ({ active, key }) => <Panel key={key} channel={channel} active={active} />, - paramKey: 'knobs', - }); - }); -} diff --git a/addons/ondevice-knobs/src/panel.js b/addons/ondevice-knobs/src/panel.js deleted file mode 100644 index 489ac5adaa94..000000000000 --- a/addons/ondevice-knobs/src/panel.js +++ /dev/null @@ -1,197 +0,0 @@ -import React from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; -import { SELECT_STORY, FORCE_RE_RENDER } from '@storybook/core-events'; -import { SET, SET_OPTIONS, RESET, CHANGE, CLICK } from '@storybook/addon-knobs'; -import styled from '@emotion/native'; -import GroupTabs from './GroupTabs'; -import PropForm from './PropForm'; - -const getTimestamp = () => +new Date(); - -const DEFAULT_GROUP_ID = 'Other'; - -const Touchable = styled.TouchableOpacity(({ theme }) => ({ - borderRadius: 2, - borderWidth: 1, - borderColor: theme.borderColor, - padding: 4, - margin: 10, - justifyContent: 'center', - alignItems: 'center', -})); - -const ResetButton = styled.Text(({ theme }) => ({ - color: theme.buttonTextColor, -})); - -export default class Panel extends React.Component { - constructor(props) { - super(props); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - this.setKnobs = this.setKnobs.bind(this); - this.reset = this.reset.bind(this); - this.setOptions = this.setOptions.bind(this); - this.onGroupSelect = this.onGroupSelect.bind(this); - - this.state = { knobs: {}, groupId: DEFAULT_GROUP_ID }; - this.options = {}; - - this.lastEdit = getTimestamp(); - this.loadedFromUrl = false; - } - - componentDidMount() { - const { channel } = this.props; - - channel.on(SET, this.setKnobs); - channel.on(SET_OPTIONS, this.setOptions); - channel.on(SELECT_STORY, this.reset); - channel.emit(FORCE_RE_RENDER); - } - - componentWillUnmount() { - const { channel } = this.props; - channel.removeListener(SET, this.setKnobs); - channel.removeListener(SELECT_STORY, this.reset); - } - - onGroupSelect(name) { - this.setState({ groupId: name }); - } - - setOptions(options = { timestamps: false }) { - this.options = options; - } - - setKnobs({ knobs, timestamp }) { - if (!this.options.timestamps || !timestamp || this.lastEdit <= timestamp) { - this.setState({ knobs }); - } - } - - reset = () => { - const { channel } = this.props; - this.setState({ knobs: {} }); - channel.emit(RESET); - }; - - emitChange(changedKnob) { - const { channel } = this.props; - channel.emit(CHANGE, changedKnob); - } - - handleChange(changedKnob) { - this.lastEdit = getTimestamp(); - const { knobs } = this.state; - const { name } = changedKnob; - const newKnobs = { ...knobs }; - newKnobs[name] = { - ...newKnobs[name], - ...changedKnob, - }; - - this.setState({ knobs: newKnobs }); - - this.setState( - { knobs: newKnobs }, - this.emitChange( - changedKnob.type === 'number' - ? { ...changedKnob, value: parseFloat(changedKnob.value) } - : changedKnob - ) - ); - } - - handleClick(knob) { - const { channel } = this.props; - - channel.emit(CLICK, knob); - } - - render() { - const { active } = this.props; - - if (!active) { - return null; - } - - const { knobs, groupId: stateGroupId } = this.state; - - const groups = {}; - const groupIds = []; - - let knobsArray = Object.keys(knobs); - - const knobsWithGroups = knobsArray.filter(key => knobs[key].groupId); - - knobsWithGroups.forEach(key => { - const knobKeyGroupId = knobs[key].groupId; - groupIds.push(knobKeyGroupId); - groups[knobKeyGroupId] = { - render: () => <Text id={knobKeyGroupId}>{knobKeyGroupId}</Text>, - title: knobKeyGroupId, - }; - }); - - const allHaveGroups = groupIds.length > 0 && knobsArray.length === knobsWithGroups.length; - - // If all of the knobs are assigned to a group, we don't need the default group. - const groupId = - stateGroupId === DEFAULT_GROUP_ID && allHaveGroups - ? knobs[knobsWithGroups[0]].groupId - : stateGroupId; - - if (groupIds.length > 0) { - if (!allHaveGroups) { - groups[DEFAULT_GROUP_ID] = { - render: () => <Text id={DEFAULT_GROUP_ID}>{DEFAULT_GROUP_ID}</Text>, - title: DEFAULT_GROUP_ID, - }; - } - - if (groupId === DEFAULT_GROUP_ID) { - knobsArray = knobsArray.filter(key => !knobs[key].groupId); - } - - if (groupId !== DEFAULT_GROUP_ID) { - knobsArray = knobsArray.filter(key => knobs[key].groupId === groupId); - } - } - - knobsArray = knobsArray.map(key => knobs[key]); - - if (knobsArray.length === 0) { - return <Text>NO KNOBS</Text>; - } - - return ( - <View style={{ flex: 1, paddingTop: 10 }}> - {groupIds.length > 0 && ( - <GroupTabs groups={groups} onGroupSelect={this.onGroupSelect} selectedGroup={groupId} /> - )} - <View> - <PropForm - knobs={knobsArray} - onFieldChange={this.handleChange} - onFieldClick={this.handleClick} - /> - </View> - <Touchable onPress={this.reset}> - <ResetButton>RESET</ResetButton> - </Touchable> - </View> - ); - } -} - -Panel.propTypes = { - active: PropTypes.bool.isRequired, - channel: PropTypes.shape({ - emit: PropTypes.func, - on: PropTypes.func, - removeListener: PropTypes.func, - }).isRequired, - onReset: PropTypes.object, // eslint-disable-line -}; diff --git a/addons/ondevice-knobs/src/types/Array.js b/addons/ondevice-knobs/src/types/Array.js deleted file mode 100644 index 88b781ea14e4..000000000000 --- a/addons/ondevice-knobs/src/types/Array.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import styled from '@emotion/native'; - -const Input = styled.TextInput(({ theme }) => ({ - borderWidth: 1, - borderColor: theme.borderColor, - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - color: theme.labelColor, -})); - -function formatArray(value, separator) { - if (value === '') { - return []; - } - return value.split(separator); -} - -const ArrayType = ({ knob, onChange }) => ( - <Input - id={knob.name} - underlineColorAndroid="transparent" - autoCapitalize="none" - value={knob.value.join(knob.separator)} - onChangeText={e => onChange(formatArray(e, knob.separator))} - /> -); - -ArrayType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -ArrayType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.array, - separator: PropTypes.string, - }), - onChange: PropTypes.func, -}; - -ArrayType.serialize = value => value; -ArrayType.deserialize = value => { - if (Array.isArray(value)) return value; - - return Object.keys(value) - .sort() - .reduce((array, key) => [...array, value[key]], []); -}; - -export default ArrayType; diff --git a/addons/ondevice-knobs/src/types/Boolean.js b/addons/ondevice-knobs/src/types/Boolean.js deleted file mode 100644 index 5b38c266d1a6..000000000000 --- a/addons/ondevice-knobs/src/types/Boolean.js +++ /dev/null @@ -1,39 +0,0 @@ -import PropTypes from 'prop-types'; -import { View } from 'react-native'; -import { Switch } from 'react-native-switch'; -import React from 'react'; - -class BooleanType extends React.Component { - onValueChange = () => { - const { onChange, knob } = this.props; - onChange(!knob.value); - }; - - render() { - const { knob } = this.props; - - return ( - <View style={{ margin: 10 }}> - <Switch id={knob.name} onValueChange={this.onValueChange} value={knob.value} /> - </View> - ); - } -} - -BooleanType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -BooleanType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.bool, - }), - onChange: PropTypes.func, -}; - -BooleanType.serialize = value => (value ? String(value) : null); -BooleanType.deserialize = value => value === 'true'; - -export default BooleanType; diff --git a/addons/ondevice-knobs/src/types/Button.js b/addons/ondevice-knobs/src/types/Button.js deleted file mode 100644 index 5b5a4238a672..000000000000 --- a/addons/ondevice-knobs/src/types/Button.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { TouchableOpacity } from 'react-native'; -import styled from '@emotion/native'; - -const Label = styled.Text(({ theme }) => ({ - fontSize: 17, - color: theme.labelColor, -})); - -const ButtonType = ({ knob, onPress }) => ( - <TouchableOpacity style={{ margin: 10 }} onPress={() => onPress(knob)}> - <Label>{knob.name}</Label> - </TouchableOpacity> -); - -ButtonType.defaultProps = { - knob: {}, -}; - -ButtonType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - }), - onPress: PropTypes.func.isRequired, -}; - -ButtonType.serialize = value => value; -ButtonType.deserialize = value => value; - -export default ButtonType; diff --git a/addons/ondevice-knobs/src/types/Color.js b/addons/ondevice-knobs/src/types/Color.js deleted file mode 100644 index 102cae7ad1cc..000000000000 --- a/addons/ondevice-knobs/src/types/Color.js +++ /dev/null @@ -1,104 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { Text, Modal, View, TouchableOpacity, TouchableWithoutFeedback } from 'react-native'; -import { ColorPicker, fromHsv } from 'react-native-color-picker'; -import styled from '@emotion/native'; - -const Touchable = styled.TouchableOpacity(({ theme, color }) => ({ - borderColor: theme.borderColor, - width: 30, - height: 20, - borderRadius: 2, - borderWidth: 1, - margin: 10, - backgroundColor: color, -})); - -class ColorType extends React.Component { - constructor(props) { - super(props); - this.state = { - displayColorPicker: false, - }; - } - - openColorPicker = () => { - this.setState({ - displayColorPicker: true, - }); - }; - - closeColorPicker = () => { - this.setState({ - displayColorPicker: false, - }); - }; - - onChangeColor = color => { - const { onChange } = this.props; - - onChange(fromHsv(color)); - }; - - render() { - const { knob } = this.props; - const { displayColorPicker } = this.state; - return ( - <View> - <Touchable color={knob.value} onPress={this.openColorPicker} /> - <Modal - supportedOrientations={['portrait', 'landscape']} - transparent - visible={displayColorPicker} - onRequestClose={this.closeColorPicker} - > - <TouchableWithoutFeedback onPress={this.closeColorPicker}> - <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> - <TouchableWithoutFeedback> - <View - style={{ - backgroundColor: 'white', - borderWidth: 1, - borderColor: 'rgb(247, 244, 244)', - width: 250, - height: 250, - padding: 10, - }} - > - <TouchableOpacity - onPress={this.closeColorPicker} - style={{ alignSelf: 'flex-end', padding: 5 }} - > - <Text style={{ fontSize: 18, fontWeight: 'bold' }}>X</Text> - </TouchableOpacity> - <ColorPicker - onColorSelected={this.onChangeColor} - defaultColor={knob.value} - style={{ flex: 1 }} - /> - </View> - </TouchableWithoutFeedback> - </View> - </TouchableWithoutFeedback> - </Modal> - </View> - ); - } -} - -ColorType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.string, - }), - onChange: PropTypes.func, -}; -ColorType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -ColorType.serialize = value => value; -ColorType.deserialize = value => value; - -export default ColorType; diff --git a/addons/ondevice-knobs/src/types/Date.js b/addons/ondevice-knobs/src/types/Date.js deleted file mode 100644 index 7776ba2959f2..000000000000 --- a/addons/ondevice-knobs/src/types/Date.js +++ /dev/null @@ -1,104 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { PureComponent } from 'react'; -import { View } from 'react-native'; -import DateTimePicker from 'react-native-modal-datetime-picker'; -import styled from '@emotion/native'; - -const Touchable = styled.TouchableOpacity(({ theme }) => ({ - borderColor: theme.borderColor, - borderWidth: 1, - borderRadius: 2, - padding: 5, -})); - -const Label = styled.Text(({ theme }) => ({ - fontSize: 13, - color: theme.labelColor, -})); - -// TODO seconds support -class DateType extends PureComponent { - constructor() { - super(); - this.state = { - isDateVisible: false, - isTimeVisible: false, - }; - } - - showDatePicker = () => { - this.setState({ isDateVisible: true }); - }; - - showTimePicker = () => { - this.setState({ isTimeVisible: true }); - }; - - hidePicker = () => { - this.setState({ isDateVisible: false, isTimeVisible: false }); - }; - - onDatePicked = date => { - const value = date.valueOf(); - const { onChange } = this.props; - onChange(value); - this.hidePicker(); - }; - - render() { - const { knob } = this.props; - - const { isTimeVisible, isDateVisible } = this.state; - const d = new Date(knob.value); - - // https://stackoverflow.com/a/30272803 - const dateString = [ - `0${d.getDate()}`.slice(-2), - `0${d.getMonth() + 1}`.slice(-2), - d.getFullYear(), - ].join('-'); - const timeString = `${`0${d.getHours()}`.slice(-2)}:${`0${d.getMinutes()}`.slice(-2)}`; - - return ( - <View style={{ margin: 10 }}> - <View style={{ flexDirection: 'row' }}> - <Touchable onPress={this.showDatePicker}> - <Label>{dateString}</Label> - </Touchable> - <Touchable - style={{ - marginLeft: 5, - }} - onPress={this.showTimePicker} - > - <Label>{timeString}</Label> - </Touchable> - </View> - <DateTimePicker - date={d} - isVisible={isTimeVisible || isDateVisible} - mode={isTimeVisible ? 'time' : 'date'} - onConfirm={this.onDatePicked} - onCancel={this.hidePicker} - /> - </View> - ); - } -} -DateType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -DateType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.number, - }), - onChange: PropTypes.func, -}; - -DateType.serialize = value => String(value); -DateType.deserialize = value => parseFloat(value); - -export default DateType; diff --git a/addons/ondevice-knobs/src/types/Number.js b/addons/ondevice-knobs/src/types/Number.js deleted file mode 100644 index dbd4946ce4f4..000000000000 --- a/addons/ondevice-knobs/src/types/Number.js +++ /dev/null @@ -1,96 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { View, Slider } from 'react-native'; -import styled from '@emotion/native'; - -const Input = styled.TextInput(({ theme }) => ({ - borderWidth: 1, - borderColor: theme.borderColor, - borderRadius: 2, - fontSize: 13, - padding: 5, - color: theme.labelColor, -})); - -class NumberType extends React.Component { - constructor(props) { - super(props); - this.renderNormal = this.renderNormal.bind(this); - this.renderRange = this.renderRange.bind(this); - } - - numberTransformer = x => { - if (Number.isNaN(Number(x))) { - return x.substr(0, x.length - 1); - } - - return x; - }; - - onChangeNormal = value => { - const { onChange } = this.props; - - if (!Number.isNaN(value)) { - onChange(value); - } - }; - - renderNormal() { - const { knob } = this.props; - - return ( - <Input - autoCapitalize="none" - underlineColorAndroid="transparent" - value={(knob.value || '').toString()} - transformer={this.numberTransformer} - keyboardType="numeric" - onChangeText={this.onChangeNormal} - /> - ); - } - - renderRange() { - const { knob, onChange } = this.props; - - return ( - <Slider - value={knob.value} - minimumValue={knob.min} - maximumValue={knob.max} - step={knob.step} - onSlidingComplete={val => onChange(parseFloat(val))} - /> - ); - } - - render() { - const { knob } = this.props; - - return ( - <View style={{ margin: 10 }}>{knob.range ? this.renderRange() : this.renderNormal()}</View> - ); - } -} - -NumberType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -NumberType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - step: PropTypes.number, - min: PropTypes.number, - max: PropTypes.number, - range: PropTypes.bool, - }), - onChange: PropTypes.func, -}; - -NumberType.serialize = value => String(value); -NumberType.deserialize = value => parseFloat(value); - -export default NumberType; diff --git a/addons/ondevice-knobs/src/types/Object.js b/addons/ondevice-knobs/src/types/Object.js deleted file mode 100644 index 87d8725baaf4..000000000000 --- a/addons/ondevice-knobs/src/types/Object.js +++ /dev/null @@ -1,102 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import deepEqual from 'deep-equal'; -import styled from '@emotion/native'; - -const Input = styled.TextInput(({ theme }) => ({ - borderWidth: 1, - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - borderColor: theme.borderColor, - color: theme.labelColor, -})); - -class ObjectType extends React.Component { - constructor(...args) { - super(...args); - this.state = {}; - } - - getJSONString() { - const { json, jsonString } = this.state; - const { knob } = this.props; - - // If there is an error in the JSON, we need to give that errored JSON. - if (this.failed) return jsonString; - - // If the editor value and the knob value is the same, we need to return the - // editor value as it allow user to add new fields to the JSON. - if (deepEqual(json, knob.value)) return jsonString; - - // If the knob's value is different from the editor, it seems like - // there's a outside change and we need to get that. - return JSON.stringify(knob.value, null, 2); - } - - handleChange = value => { - const { onChange } = this.props; - - const withReplacedQuotes = value - .replace(/[\u2018\u2019]/g, "'") - .replace(/[\u201C\u201D]/g, '"'); - - const newState = { - jsonString: withReplacedQuotes, - }; - - try { - newState.json = JSON.parse(withReplacedQuotes.trim()); - - onChange(newState.json); - this.failed = false; - } catch (err) { - this.failed = true; - } - - this.setState(newState); - }; - - render() { - const { knob } = this.props; - const jsonString = this.getJSONString(); - const extraStyle = {}; - - if (this.failed) { - extraStyle.borderWidth = 1; - extraStyle.borderColor = '#fadddd'; - extraStyle.backgroundColor = '#fff5f5'; - } - - return ( - <Input - id={knob.name} - style={extraStyle} - value={jsonString} - onChangeText={this.handleChange} - multiline - autoCapitalize="none" - underlineColorAndroid="transparent" - /> - ); - } -} - -ObjectType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -ObjectType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), - }), - onChange: PropTypes.func, -}; - -ObjectType.serialize = object => JSON.stringify(object); -ObjectType.deserialize = value => (value ? JSON.parse(value) : {}); - -export default ObjectType; diff --git a/addons/ondevice-knobs/src/types/Select.js b/addons/ondevice-knobs/src/types/Select.js deleted file mode 100644 index 530131bc4d5b..000000000000 --- a/addons/ondevice-knobs/src/types/Select.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ - -import PropTypes from 'prop-types'; -import { View } from 'react-native'; -import React from 'react'; -import ModalPicker from 'react-native-modal-selector'; -import styled from '@emotion/native'; - -const Input = styled.TextInput(({ theme }) => ({ - borderWidth: 1, - borderRadius: 2, - padding: 5, - margin: 10, - borderColor: theme.borderColor, - color: theme.labelColor, -})); - -class SelectType extends React.Component { - getOptions = ({ options }) => { - if (Array.isArray(options)) { - return options.map(val => ({ key: val, label: val })); - } - - return Object.keys(options).map(key => ({ label: key, key: options[key] })); - }; - - render() { - const { knob, onChange } = this.props; - - const options = this.getOptions(knob); - - const active = options.filter(({ key }) => knob.value === key)[0]; - const selected = active && active.label; - - return ( - <View> - <ModalPicker - data={options} - initValue={knob.value} - onChange={option => onChange(option.key)} - animationType="none" - > - <Input - editable={false} - value={selected} - autoCapitalize="none" - underlineColorAndroid="transparent" - /> - </ModalPicker> - </View> - ); - } -} - -SelectType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -SelectType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.string, - options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - selectV2: PropTypes.bool, - }), - onChange: PropTypes.func, -}; - -SelectType.serialize = value => value; -SelectType.deserialize = value => value; - -export default SelectType; diff --git a/addons/ondevice-knobs/src/types/Text.js b/addons/ondevice-knobs/src/types/Text.js deleted file mode 100644 index e806e73f810e..000000000000 --- a/addons/ondevice-knobs/src/types/Text.js +++ /dev/null @@ -1,41 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import styled from '@emotion/native'; - -const Input = styled.TextInput(({ theme }) => ({ - borderWidth: 1, - borderColor: theme.borderColor, - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - color: theme.labelColor, -})); - -const TextType = ({ knob, onChange }) => ( - <Input - id={knob.name} - value={knob.value} - onChangeText={onChange} - autoCapitalize="none" - underlineColorAndroid="transparent" - /> -); - -TextType.defaultProps = { - knob: {}, - onChange: value => value, -}; - -TextType.propTypes = { - knob: PropTypes.shape({ - name: PropTypes.string, - value: PropTypes.string, - }), - onChange: PropTypes.func, -}; - -TextType.serialize = value => value; -TextType.deserialize = value => value; - -export default TextType; diff --git a/addons/ondevice-knobs/src/types/index.js b/addons/ondevice-knobs/src/types/index.js deleted file mode 100644 index ebd51deb858e..000000000000 --- a/addons/ondevice-knobs/src/types/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import TextType from './Text'; -import NumberType from './Number'; -import ColorType from './Color'; -import BooleanType from './Boolean'; -import ObjectType from './Object'; -import SelectType from './Select'; -import ArrayType from './Array'; -import DateType from './Date'; -import ButtonType from './Button'; - -export default { - text: TextType, - number: NumberType, - color: ColorType, - boolean: BooleanType, - object: ObjectType, - select: SelectType, - array: ArrayType, - date: DateType, - button: ButtonType, -}; diff --git a/addons/ondevice-notes/README.md b/addons/ondevice-notes/README.md deleted file mode 100644 index 9d5329d210a9..000000000000 --- a/addons/ondevice-notes/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Storybook Notes Addon for react-native - -The Notes Addon allows you to write notes (text or markdown) for your stories in [Storybook](https://storybook.js.org). - -![Storybook Addon Notes Demo](docs/demo.png) - -## Installation - -```sh -yarn add -D @storybook/addon-ondevice-notes -``` - -## Configuration - -Create a file called `rn-addons.js` in your storybook config. - -Add following content to it: - -```js -import '@storybook/addon-ondevice-notes/register'; -``` - -Then import `rn-addons.js` next to your `getStorybookUI` call. - -```js -import './rn-addons'; -``` - -## Usage - -Use the `notes` parameter to add a note to stories: - -```js -import { storiesOf } from '@storybook/react-native'; - -import Component from './Component'; - -storiesOf('Component', module).add('with some emoji', () => <Component />, { - notes: 'A small component', -}); -``` - -See the [crna-kitchen-sink app](../../examples-native/crna-kitchen-sink) for more examples. diff --git a/addons/ondevice-notes/docs/demo.png b/addons/ondevice-notes/docs/demo.png deleted file mode 100644 index 3aa3082cb3a8..000000000000 Binary files a/addons/ondevice-notes/docs/demo.png and /dev/null differ diff --git a/addons/ondevice-notes/package.json b/addons/ondevice-notes/package.json deleted file mode 100644 index 5b4095649818..000000000000 --- a/addons/ondevice-notes/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-notes", - "version": "5.3.0-rc.0", - "description": "Write notes for your react-native Storybook stories.", - "keywords": [ - "addon", - "notes", - "react-native", - "storybook" - ], - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/ondevice-notes" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@emotion/core": "^10.0.20", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "core-js": "^3.0.1", - "prop-types": "^15.7.2", - "react-native-simple-markdown": "^1.1.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/ondevice-notes/register.js b/addons/ondevice-notes/register.js deleted file mode 100644 index 18cdafda57c4..000000000000 --- a/addons/ondevice-notes/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/register.js'); diff --git a/addons/ondevice-notes/src/components/Notes.tsx b/addons/ondevice-notes/src/components/Notes.tsx deleted file mode 100644 index b27360ad81d6..000000000000 --- a/addons/ondevice-notes/src/components/Notes.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { View } from 'react-native'; -import Markdown from 'react-native-simple-markdown'; -import { AddonStore } from '@storybook/addons'; -import { API } from '@storybook/api'; -import { ThemeContext } from '@emotion/core'; - -export const PARAM_KEY = `notes`; - -interface NotesProps { - channel: ReturnType<AddonStore['getChannel']>; - api: API; - active: boolean; -} - -export const Notes = ({ active, api }: NotesProps) => { - if (!active) { - return null; - } - - const selection = api.store().getSelection(); - - if (!selection) { - return null; - } - - const story = api.store().fromId(selection.storyId); - const text = story.parameters[PARAM_KEY]; - - const textAfterFormatted: string = text ? text.trim() : ''; - - return ( - <View style={{ padding: 10, flex: 1 }}> - <ThemeContext.Consumer> - {theme => ( - <Markdown styles={{ text: { color: (theme as any).labelColor } }}> - {textAfterFormatted} - </Markdown> - )} - </ThemeContext.Consumer> - </View> - ); -}; diff --git a/addons/ondevice-notes/src/index.ts b/addons/ondevice-notes/src/index.ts deleted file mode 100644 index 6c03530f0864..000000000000 --- a/addons/ondevice-notes/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { logger } from '@storybook/client-logger'; - -// eslint-disable-next-line no-undef -if (__DEV__) { - logger.log("import '@storybook/addon-ondevice-notes/register' to register the notes addon"); -} diff --git a/addons/ondevice-notes/src/register.tsx b/addons/ondevice-notes/src/register.tsx deleted file mode 100644 index fa663581ac21..000000000000 --- a/addons/ondevice-notes/src/register.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import addons from '@storybook/addons'; -import { Notes } from './components/Notes'; - -export const PARAM_KEY = `notes`; - -addons.register('storybook/notes', api => { - const channel = addons.getChannel(); - addons.addPanel('storybook/notes/panel', { - title: 'Notes', - render: ({ active, key }) => <Notes key={key} channel={channel} api={api} active={active} />, - paramKey: PARAM_KEY, - }); -}); diff --git a/addons/ondevice-notes/src/typings.d.ts b/addons/ondevice-notes/src/typings.d.ts deleted file mode 100644 index eead99469329..000000000000 --- a/addons/ondevice-notes/src/typings.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'react-native-simple-markdown' { - const Markdown: any; - export default Markdown; -} diff --git a/addons/ondevice-notes/tsconfig.json b/addons/ondevice-notes/tsconfig.json deleted file mode 100644 index 24ef9bc68916..000000000000 --- a/addons/ondevice-notes/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] -} diff --git a/addons/options/README.md b/addons/options/README.md deleted file mode 100644 index 854cc8b45c54..000000000000 --- a/addons/options/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Options Addon is deprecated as of Storybook 5.0 - -Please read https://storybook.js.org/docs/configurations/options-parameter/ to learn about storybook's options and setting them. diff --git a/addons/options/docs/screenshot.png b/addons/options/docs/screenshot.png deleted file mode 100644 index 7226f009f35c..000000000000 Binary files a/addons/options/docs/screenshot.png and /dev/null differ diff --git a/addons/options/package.json b/addons/options/package.json deleted file mode 100644 index bc3c266eb093..000000000000 --- a/addons/options/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@storybook/addon-options", - "version": "5.3.0-rc.0", - "description": "Options addon for storybook", - "keywords": [ - "addon", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/options", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "addons/options" - }, - "license": "MIT", - "files": [ - "dist/**/*", - "docs/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "types": "dist/public_api.d.ts", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "core-js": "^3.0.1", - "util-deprecate": "^1.0.2" - }, - "peerDependencies": { - "react": "*" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/addons/options/register.js b/addons/options/register.js deleted file mode 100644 index cc38cb06f1f2..000000000000 --- a/addons/options/register.js +++ /dev/null @@ -1 +0,0 @@ -require('./dist/register'); diff --git a/addons/options/src/constants.ts b/addons/options/src/constants.ts deleted file mode 100644 index ba5ec2b90d5c..000000000000 --- a/addons/options/src/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -// addons, panels and events get unique names using a prefix -export const ADDON_ID = 'storybookjs/options'; - -export default { - SET: `${ADDON_ID}/options-event`, -}; diff --git a/addons/options/src/index.ts b/addons/options/src/index.ts deleted file mode 100644 index e7e403924a85..000000000000 --- a/addons/options/src/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import deprecate from 'util-deprecate'; -import addons, { makeDecorator } from '@storybook/addons'; - -import EVENTS from './constants'; - -function emitOptions(options: any) { - const channel = addons.getChannel(); - if (!channel) { - throw new Error( - 'Failed to find addon channel. This may be due to https://github.com/storybookjs/storybook/issues/1192.' - ); - } - - // since 'undefined' and 'null' are the valid values we don't want to - // override the hierarchySeparator or hierarchyRootSeparator if the prop is missing - channel.emit(EVENTS.SET, { - options, - }); -} - -// setOptions function will send Storybook UI options when the channel is -// ready. If called before, options will be cached until it can be sent. -let globalOptions = {}; -export const setOptions = deprecate((options: any) => { - globalOptions = options; - emitOptions(options); -}, '`setOptions(options)` is deprecated. Please use the `withOptions(options)` decorator globally.'); - -export const withOptions = makeDecorator({ - name: 'withOptions', - parameterName: 'options', - skipIfNoParametersOrOptions: false, - wrapper: deprecate((getStory, context, { options: inputOptions, parameters }) => { - // do not send hierarchy related options over the channel - const { hierarchySeparator, hierarchyRootSeparator, ...change }: any = { - ...globalOptions, - ...inputOptions, - ...parameters, - }; - - if (Object.keys(change).length) { - emitOptions({ - ...globalOptions, - ...inputOptions, - ...parameters, - }); - } - - // MUTATION ! - // eslint-disable-next-line no-param-reassign - (context as any).options = { - ...globalOptions, - ...inputOptions, - ...parameters, - }; - - return getStory({ - ...context, - options: { - ...globalOptions, - ...inputOptions, - ...parameters, - }, - } as any); - }, 'withOptions is deprecated, use addParameters({ options: {} }) instead'), -}); - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/options/src/public_api.ts b/addons/options/src/public_api.ts deleted file mode 100644 index c327fdba2b68..000000000000 --- a/addons/options/src/public_api.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ADDON_ID } from './constants'; -export { setOptions, withOptions } from './index'; diff --git a/addons/options/src/register.ts b/addons/options/src/register.ts deleted file mode 100644 index 8cec35efa859..000000000000 --- a/addons/options/src/register.ts +++ /dev/null @@ -1,14 +0,0 @@ -import addons from '@storybook/addons'; -import deprecate from 'util-deprecate'; -import EVENTS, { ADDON_ID } from './constants'; - -addons.register( - ADDON_ID, - deprecate(api => { - const channel = addons.getChannel(); - - channel.on(EVENTS.SET, data => { - api.setOptions(data.options); - }); - }, 'storybook-addon-options is deprecated and will stop working soon') -); diff --git a/addons/options/src/typings.d.ts b/addons/options/src/typings.d.ts deleted file mode 100644 index 2b6c3a6d4280..000000000000 --- a/addons/options/src/typings.d.ts +++ /dev/null @@ -1 +0,0 @@ -let module: any; diff --git a/addons/options/tsconfig.json b/addons/options/tsconfig.json deleted file mode 100644 index 1d10f46e8b7f..000000000000 --- a/addons/options/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] - }, - "include": [ - "src/**/*" - ] -} diff --git a/addons/queryparams/README.md b/addons/queryparams/README.md index 8f3402004371..0befd0e5e69a 100644 --- a/addons/queryparams/README.md +++ b/addons/queryparams/README.md @@ -1,6 +1,6 @@ # storybook-addon-queryparams -This storybook addon can be helpful if your components need special query parameters to work the way you want them. +This storybook addon can be helpful if your components need special query parameters to work the way you want them to. It allows you to mock query params per story so that you can easily reproduce different states of your component. ## Getting started @@ -10,23 +10,57 @@ First, install the addon. $ yarn add @storybook/addon-queryparams --dev ``` -import the `withQuery` decorator so the url will be changed before rendering stories. +Register it by adding it in the addons attribute in your `main.js` file (create this file inside your storybook config directory if needed). + +```js +module.exports = { + addons: ['@storybook/addon-queryparams'], +}; +``` + +In your story, add the `withQuery` decorator and define the query parameters you want to mock: ```js import React from 'react'; -import { storiesOf, addDecorator } from '@storybook/react'; +import { Button } from '@storybook/react/demo'; import { withQuery } from '@storybook/addon-queryparams'; +export default { + title: 'Button', + component: Button, + decorators: [withQuery], + parameters: { + query: { + mock: 'Hello world!', + }, + }, +}; + +export const WithMockedSearch = () => { + const urlParams = new URLSearchParams(document.location.search); + const mockedParam = urlParams.get('mock'); + return <div>Mocked value: {mockedParam}</div>; +}; +``` + +<details> + <summary>Example with storiesOf API</summary> + +```js +import React from 'react'; +import { storiesOf } from '@storybook/react'; + storiesOf('button', module) - .addDecorator(withQuery) .addParameters({ query: { - mock: true, - } + mock: 'Hello World!', + }, }) - .add('Prints the document.search', () => ( - <div> - This is the current document.search: {document.search}, it includes `mock`! - </div> - )); + .add('Prints the mocked parameter', () => { + const urlParams = new URLSearchParams(document.location.search); + const mockedParam = urlParams.get('mock'); + return <div>Mocked value: {mockedParam}</div>; + }); ``` + +</details> diff --git a/addons/queryparams/package.json b/addons/queryparams/package.json index 3a54936d194f..291a72e4054c 100644 --- a/addons/queryparams/package.json +++ b/addons/queryparams/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-queryparams", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -17,33 +17,47 @@ "directory": "addons/addon-queryparams" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "qs": "^6.6.0", "react": "^16.8.3", - "ts-dedent": "^1.1.0" + "regenerator-runtime": "^0.13.3", + "ts-dedent": "^1.1.1" + }, + "devDependencies": { + "@types/webpack-env": "^1.15.2" + }, + "peerDependencies": { + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/queryparams/preset.js b/addons/queryparams/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/queryparams/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/queryparams/src/index.ts b/addons/queryparams/src/index.ts index a84100160ad0..62138a138ba8 100644 --- a/addons/queryparams/src/index.ts +++ b/addons/queryparams/src/index.ts @@ -8,6 +8,7 @@ import { PARAM_KEY } from './constants'; export const withQuery = makeDecorator({ name: 'withQuery', parameterName: PARAM_KEY, + skipIfNoParametersOrOptions: true, wrapper: (getStory: StoryGetter, context: StoryContext, { parameters }) => { const { location } = document; const currentQuery = qs.parse(location.search, { ignoreQueryPrefix: true }); diff --git a/addons/queryparams/src/preset/addDecorator.ts b/addons/queryparams/src/preset/addDecorator.ts new file mode 100644 index 000000000000..f04053309f79 --- /dev/null +++ b/addons/queryparams/src/preset/addDecorator.ts @@ -0,0 +1,3 @@ +import { withQuery } from '../index'; + +export const decorators = [withQuery]; diff --git a/addons/queryparams/src/preset/index.ts b/addons/queryparams/src/preset/index.ts new file mode 100644 index 000000000000..af12e51942ac --- /dev/null +++ b/addons/queryparams/src/preset/index.ts @@ -0,0 +1,11 @@ +interface QueryParamsOptions { + addDecorator?: boolean; +} + +export function config(entry: any[] = [], { addDecorator = true }: QueryParamsOptions = {}) { + const queryParamsConfig = []; + if (addDecorator) { + queryParamsConfig.push(require.resolve('./addDecorator')); + } + return [...entry, ...queryParamsConfig]; +} diff --git a/addons/storyshots/storyshots-core/.eslintrc.js b/addons/storyshots/storyshots-core/.eslintrc.js index d6e90d6d46a8..d52d94baae34 100644 --- a/addons/storyshots/storyshots-core/.eslintrc.js +++ b/addons/storyshots/storyshots-core/.eslintrc.js @@ -4,7 +4,6 @@ module.exports = { '@storybook/angular', '@storybook/html', '@storybook/react', - '@storybook/react-native', '@storybook/preact', '@storybook/vue', '@storybook/svelte', diff --git a/addons/storyshots/storyshots-core/.storybook/configTest.js b/addons/storyshots/storyshots-core/.storybook/configTest.js index 8ae32982a9b9..9c163a43e4d4 100644 --- a/addons/storyshots/storyshots-core/.storybook/configTest.js +++ b/addons/storyshots/storyshots-core/.storybook/configTest.js @@ -3,7 +3,7 @@ import { configure } from '@storybook/react'; const req = require.context('../stories/required_with_context', false, /\.stories\.js$/); const loadStories = () => { - const result = req.keys().map(filename => req(filename)); + const result = req.keys().map((filename) => req(filename)); // eslint-disable-next-line global-require require('../stories/directly_required'); return result; diff --git a/addons/storyshots/storyshots-core/README.md b/addons/storyshots/storyshots-core/README.md index e2696305ef77..61a9fe18b430 100644 --- a/addons/storyshots/storyshots-core/README.md +++ b/addons/storyshots/storyshots-core/README.md @@ -16,26 +16,45 @@ Add the following module into your app. yarn add @storybook/addon-storyshots --dev ``` +## Configure Storyshots for HTML snapshots + +Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer, as long as it matches Jest's config [`testMatch`](http://facebook.github.io/jest/docs/en/configuration.html#testmatch-array-string)). +Then add following content to it: + +```js +import initStoryshots from '@storybook/addon-storyshots'; + +initStoryshots(); +``` + +That's all. + +Now run your Jest test command. (Usually, `npm test`.) Then you can see all of your stories are converted as Jest snapshot tests. + +![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/storyshots/storyshots-core/docs/storyshots.png) + ## Configure your app for Jest + In many cases, for example Create React App, it's already configured for Jest. You need to create a filename with the extension `.test.js`. If you still need to configure jest you can use the resources mentioned below: -- [Getting Started - Jest Official Documentation](https://facebook.github.io/jest/docs/en/getting-started.html) -- [Javascript Testing with Jest - Egghead](https://egghead.io/lessons/javascript-test-javascript-with-jest). ***paid content*** +- [Getting Started - Jest Official Documentation](https://facebook.github.io/jest/docs/en/getting-started.html) +- [Javascript Testing with Jest - Egghead](https://egghead.io/lessons/javascript-test-javascript-with-jest). **_paid content_** > Note: If you use React 16, you'll need to follow [these additional instructions](https://github.com/facebook/react/issues/9102#issuecomment-283873039). > -> Note: Make sure you have added the ```json``` extension to ```moduleFileExtensions``` in ```jest.config.json```. If this is missing it leads to the [following error](https://github.com/storybookjs/storybook/issues/3728): ```Cannot find module 'spdx-license-ids' from 'scan.js'```. +> Note: Make sure you have added the `json` extension to `moduleFileExtensions` in `jest.config.json`. If this is missing it leads to the [following error](https://github.com/storybookjs/storybook/issues/3728): `Cannot find module 'spdx-license-ids' from 'scan.js'`. > -> Note: Please make sure you are using ```jsdom``` as the testEnvironment on your jest config file. - +> Note: Please make sure you are using `jsdom` as the testEnvironment on your jest config file. ### Configure Jest to work with Webpack's [require.context()](https://webpack.js.org/guides/dependency-management/#require-context) -Sometimes it's useful to configure Storybook with Webpack's require.context feature. You could be loading stories [one of two ways](https://storybook.js.org/docs/basics/writing-stories/#loading-stories). +**NOTE**: if you are using Storybook 5.3's `main.js` to list story files, this is no longer needed. + +Sometimes it's useful to configure Storybook with Webpack's require.context feature. You could be loading stories [one of two ways](https://storybook.js.org/docs/basics/writing-stories/#loading-stories). -1) If you're using the `storiesOf` API, you can integrate it this way: +1. If you're using the `storiesOf` API, you can integrate it this way: ```js import { configure } from '@storybook/react'; @@ -43,13 +62,13 @@ import { configure } from '@storybook/react'; const req = require.context('../stories', true, /\.stories\.js$/); // <- import all the stories at once function loadStories() { - req.keys().forEach(filename => req(filename)); + req.keys().forEach((filename) => req(filename)); } configure(loadStories, module); ``` -2) If you're using Component Story Format (CSF), you'll integrate it like so: +2. If you're using Component Story Format (CSF), you'll integrate it like so: ```js import { configure } from '@storybook/react'; @@ -57,7 +76,7 @@ import { configure } from '@storybook/react'; const req = require.context('../stories', true, /\.stories\.js$/); // <- import all the stories at once configure(req, module); -``` +``` The problem here is that it will work only during the build with webpack, other tools may lack this feature. Since Storyshot is running under Jest, @@ -80,11 +99,13 @@ Next, it needs to be registered and loaded before each test. To register it, cre import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; registerRequireContextHook(); ``` + That file needs to be added as a setup file for Jest. To do that, add (or create) a property in Jest's config called [`setupFiles`](https://jestjs.io/docs/en/configuration.html#setupfiles-array). Add the file name and path to this array. ```json setupFiles: ['<rootDir>/.jest/register-context.js'] ``` + Finally, add the plugin to `.babelrc`: ```json @@ -98,6 +119,7 @@ Finally, add the plugin to `.babelrc`: } } ``` + The plugin is only added to the test environment otherwise it could replace webpack's version of it. #### Option 2: Macro @@ -118,6 +140,7 @@ const req = requireContext('../stories', true, /\.stories\.js$/); ``` ### Configure Jest for React + StoryShots addon for React is dependent on [react-test-renderer](https://github.com/facebook/react/tree/master/packages/react-test-renderer), but [doesn't](#deps-issue) install it, so you need to install it separately. @@ -126,6 +149,7 @@ yarn add react-test-renderer --dev ``` ### Configure Jest for Angular + StoryShots addon for Angular is dependent on [jest-preset-angular](https://github.com/thymikee/jest-preset-angular), but [doesn't](#deps-issue) install it, so you need to install it separately. @@ -135,6 +159,7 @@ yarn add jest-preset-angular If you already use Jest for testing your angular app - probably you already have the needed jest configuration. Anyway you can add these lines to your jest config: + ```js module.exports = { globals: { @@ -147,7 +172,9 @@ module.exports = { moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', '.html'], }; ``` + ### Configure Jest for Vue + StoryShots addon for Vue is dependent on [jest-vue-preprocessor](https://github.com/vire/jest-vue-preprocessor), but [doesn't](#deps-issue) install it, so you need to install it separately. @@ -157,20 +184,20 @@ yarn add jest-vue-preprocessor If you already use Jest for testing your vue app - probably you already have the needed jest configuration. Anyway you can add these lines to your jest config: + ```js module.exports = { transform: { '^.+\\.jsx?$': 'babel-jest', '.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor', }, - transformIgnorePatterns: [ - '/node_modules/(?!(@storybook/.*\\.vue$))', - ], + transformIgnorePatterns: ['/node_modules/(?!(@storybook/.*\\.vue$))'], moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'node'], }; ``` ### Configure Jest for Preact + StoryShots addon for Preact is dependent on [preact-render-to-json](https://github.com/nathancahill/preact-render-to-json), but [doesn't](#deps-issue) install it, so you need to install it separately. @@ -180,7 +207,7 @@ yarn add preact-render-to-json --dev ### Configure Jest for MDX Docs Add-On Stories -If using the [Docs add-on](../../docs/README.md) with +If using the [Docs add-on](../../docs/README.md) with [MDX stories](../../docs/docs/mdx.md) you will need to configure Jest to transform MDX stories into something Storyshots can understand: @@ -196,6 +223,7 @@ Add the following to your Jest configuration: ``` ### <a name="deps-issue"></a>Why don't we install dependencies of each framework ? + Storyshots addon is currently supporting React, Angular and Vue. Each framework needs its own packages to be integrated with Jest. We don't want people that use only React will need to bring other dependencies that do not make sense for them. `dependencies` - will installed an exact version of the particular dep - Storyshots can work with different versions of the same framework (let's say React v16 and React v15), that have to be compatible with a version of its plugin (react-test-renderer). @@ -208,24 +236,6 @@ Storyshots addon is currently supporting React, Angular and Vue. Each framework For more information read npm [docs](https://docs.npmjs.com/files/package.json#dependencies) -## Configure Storyshots for HTML snapshots - -Create a new test file with the name `Storyshots.test.js`. (Or whatever the name you prefer, as long as it matches Jest's config [`testMatch`](http://facebook.github.io/jest/docs/en/configuration.html#testmatch-array-string)). -Then add following content to it: - -```js -import initStoryshots from '@storybook/addon-storyshots'; - -initStoryshots(); -``` - -That's all. - -Now run your Jest test command. (Usually, `npm test`.) Then you can see all of your stories are converted as Jest snapshot tests. - -![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/storyshots/storyshots-core/docs/storyshots.png) - - ### Using `createNodeMock` to mock refs `react-test-renderer` doesn't provide refs for rendered components. By @@ -236,34 +246,33 @@ out elements that rely on refs, you will have to use the Here is an example of how to specify the `createNodeMock` option in Storyshots: ```js -import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots' -import TextareaThatUsesRefs from '../component/TextareaThatUsesRefs' +import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots'; +import TextareaThatUsesRefs from '../component/TextareaThatUsesRefs'; initStoryshots({ test: snapshotWithOptions({ createNodeMock: (element) => { if (element.type === TextareaThatUsesRefs) { - return document.createElement('textarea') + return document.createElement('textarea'); } }, }), -}) +}); ``` Provide a function to have story-specific options: - ```js initStoryshots({ - test: snapshotWithOptions(story =>({ + test: snapshotWithOptions((story) => ({ createNodeMock: (element) => { - if(story.name == 'foobar') { - return null + if (story.name == 'foobar') { + return null; } - return element + return element; }, })), -}) +}); ``` ### StoryShots for async rendered components @@ -278,28 +287,28 @@ Add _stories of UserForm_ in the file: UserForm.story.jsx ```jsx /* global module */ -import React from "react"; -import { QueryRenderer } from "react-relay"; -import { storiesOf } from "@storybook/react"; +import React from 'react'; +import { QueryRenderer } from 'react-relay'; +import { storiesOf } from '@storybook/react'; // Use the same queries used in YOUR app routes -import { newUserFormQuery, editUserFormQuery } from "app/routes"; -import UserFormContainer from "app/users/UserForm"; +import { newUserFormQuery, editUserFormQuery } from 'app/routes'; +import UserFormContainer from 'app/users/UserForm'; // YOUR function to generate a Relay Environment mock. // See https://github.com/1stdibs/relay-mock-network-layer for more info -import getEnvironment from "test/support/relay-environment-mock"; +import getEnvironment from 'test/support/relay-environment-mock'; // User test data YOU generated for your tests -import { user } from "test/support/data/index"; +import { user } from 'test/support/data/index'; // Use this function to return a new Environment for each story const Environment = () => getEnvironment({ mocks: { - Node: () => ({ __typename: "User" }), - User: () => user - } + Node: () => ({ __typename: 'User' }), + User: () => user, + }, }); /** @@ -326,23 +335,23 @@ const renderStory = (query, environment, variables = {}) => ( /> ); -storiesOf("users/UserForm", module) - .add("New User", () => { +storiesOf('users/UserForm', module) + .add('New User', () => { const environment = new Environment(); return renderStory(newUserFormQuery, environment); }) - .add("Editing User", () => { + .add('Editing User', () => { const environment = new Environment(); return renderStory(editUserFormQuery, environment, { id: user.id }); - }) + }); ``` Then, init Storyshots for async component in the file: StoryShots.test.js ```jsx -import initStoryshots, { Stories2SnapsConverter } from "@storybook/addon-storyshots"; -import { mount } from "enzyme"; -import toJson from "enzyme-to-json"; +import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots'; +import { mount } from 'enzyme'; +import toJson from 'enzyme-to-json'; // Runner initStoryshots({ @@ -350,7 +359,7 @@ initStoryshots({ test: ({ story, context, - done // --> callback passed to test method when asyncJest option is true + done, // --> callback passed to test method when asyncJest option is true }) => { const converter = new Stories2SnapsConverter(); const snapshotFilename = converter.getSnapshotFileName(context); @@ -369,17 +378,17 @@ initStoryshots({ } done(); - }, waitTime) + }, waitTime); }, // other options here }); - ``` + NOTICE that When using the `asyncJest: true` option, you also must specify a `test` method that calls the `done()` callback. This is a really powerful technique to write stories of Relay components because it integrates data fetching with component rendering. So instead of passing data props manually, we can let Relay do the job for us as it does in our application. -Whenever you change you're data requirements by adding (and rendering) or (accidentally) deleting fields in your graphql query fragments, you'll get a different snapshot and thus an error in the StoryShot test. +Whenever you change your data requirements by adding (and rendering) or (accidentally) deleting fields in your graphql query fragments, you'll get a different snapshot and thus an error in the StoryShot test. ## Options @@ -403,8 +412,8 @@ initStoryshots({ By default, Storyshots assumes the config directory path for your project as below: -- Storybook for React: `.storybook` -- Storybook for React Native: `storybook` +- Storybook for React: `.storybook` +- Storybook for React Native: `storybook` If you are using a different config directory path, you could change it like this: @@ -412,7 +421,7 @@ If you are using a different config directory path, you could change it like thi import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ - configPath: '.my-storybook-config-dir' + configPath: '.my-storybook-config-dir', }); ``` @@ -431,15 +440,14 @@ original one. It also may be useful for separating tests to different test confi ```js initStoryshots({ - configPath: '.my-storybook-config-dir/testConfig1.js' + configPath: '.my-storybook-config-dir/testConfig1.js', }); initStoryshots({ - configPath: '.my-storybook-config-dir/testConfig2.js' + configPath: '.my-storybook-config-dir/testConfig2.js', }); ``` - ### `suite` By default, Storyshots groups stories inside a Jest test suite called "Storyshots". You could change it like this: @@ -448,7 +456,7 @@ By default, Storyshots groups stories inside a Jest test suite called "Storyshot import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ - suite: 'MyStoryshots' + suite: 'MyStoryshots', }); ``` @@ -460,7 +468,7 @@ If you'd like to only run a subset of the stories for your snapshot tests based import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ - storyKindRegex: /^MyComponent$/ + storyKindRegex: /^MyComponent$/, }); ``` @@ -472,7 +480,7 @@ If you want to run all stories except stories of a specific kind, you can write import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ - storyKindRegex:/^((?!.*?DontTest).)*$/ + storyKindRegex: /^((?!.*?DontTest).)*$/, }); ``` @@ -487,7 +495,7 @@ If you'd like to only run a subset of the stories for your snapshot tests based import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ - storyNameRegex: /buttons/ + storyNameRegex: /buttons/, }); ``` @@ -531,7 +539,6 @@ initStoryshots({ If you are using enzyme, you need to make sure jest knows how to serialize rendered components. For that, you can pass an enzyme-compatible snapshotSerializer (like [enzyme-to-json](https://github.com/adriantoine/enzyme-to-json), [jest-serializer-enzyme](https://github.com/rogeliog/jest-serializer-enzyme) etc.) with the `snapshotSerializer` option (see below). - ### `snapshotSerializers` Pass an array of snapshotSerializers to the jest runtime that serializes your story (such as enzyme-to-json). @@ -547,8 +554,9 @@ initStoryshots({ ``` This option needs to be set if either: -* the multiSnapshot function is used to create multiple snapshot files (i.e. one per story), since it ignores any serializers specified in your jest config. -* serializers not specified in your jest config should be used when snapshotting stories. + +- the multiSnapshot function is used to create multiple snapshot files (i.e. one per story), since it ignores any serializers specified in your jest config. +- serializers not specified in your jest config should be used when snapshotting stories. ### `serializer` (deprecated) @@ -567,6 +575,7 @@ initStoryshots({ This option only needs to be set if the default `snapshotSerializers` is not set in your jest config. ### `stories2snapsConverter` + This parameter should be an instance of the [`Stories2SnapsConverter`](src/Stories2SnapsConverter.js) (or a derived from it) Class that is used to convert story-file name to snapshot-file name and vice versa. By default, the instance of this class is created with these default options: @@ -590,7 +599,6 @@ initStoryshots({ storiesExtensions: ['.foo'], }), }); - ``` ## Exports @@ -618,6 +626,7 @@ Like the default, but allows you to specify a set of options for the renderer, j Like `snapshotWithOptions`, but generate a separate snapshot file for each stories file rather than a single monolithic file (as is the convention in Jest). This makes it dramatically easier to review changes. If you'd like the benefit of separate snapshot files, but don't have custom options to pass, you can pass an empty object. If you use [Component Story Format](https://storybook.js.org/docs/formats/component-story-format/), you may also need to add an additional Jest transform to automate detecting story file names: + ```js // jest.config.js module.exports = { @@ -668,10 +677,26 @@ initStoryshots({ if (snapshotFileName) { expect(toJson(shallowTree)).toMatchSpecificSnapshot(snapshotFileName); } - } + }, }); ``` ### `asyncJest` Enables Jest `done()` callback in the StoryShots tests for async testing. See [StoryShots for async rendered components](#storyshots-for-async-rendered-components) for more info. + +## Story Parameters + +### `disable` + +Some stories are difficult or impossible to snapshot, such as those covering components that use external DOM-modifying libraries, and those that deliberately throw errors. It is possible to skip stories like these by giving them a parameter of `storyshots: {disable: true}`. There is also a shorthand for this, `storyshots: false`. + +```js +export const Exception = () => { + throw new Error('storyFn threw an error! WHOOPS'); +}; +Exception.storyName = 'story throws exception'; +Exception.parameters = { + storyshots: { disable: true }, +}; +``` diff --git a/addons/storyshots/storyshots-core/injectFileName.js b/addons/storyshots/storyshots-core/injectFileName.js index c8f946bab571..991bce4fc5f3 100644 --- a/addons/storyshots/storyshots-core/injectFileName.js +++ b/addons/storyshots/storyshots-core/injectFileName.js @@ -4,7 +4,7 @@ const getNextTransformer = (fileName, config) => { const self = config.transform.find(([pattern]) => new RegExp(pattern).test(fileName)); return new ScriptTransformer({ ...config, - transform: config.transform.filter(entry => entry !== self), + transform: config.transform.filter((entry) => entry !== self), }); }; diff --git a/addons/storyshots/storyshots-core/package.json b/addons/storyshots/storyshots-core/package.json index a0c34385a0e2..214f4f177e37 100644 --- a/addons/storyshots/storyshots-core/package.json +++ b/addons/storyshots/storyshots-core/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storyshots", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.", "keywords": [ "addon", @@ -16,15 +16,15 @@ "directory": "addons/storyshots/storyshots-core" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "build-storybook": "build-storybook", "example": "jest storyshot.test", @@ -32,32 +32,44 @@ "storybook": "start-storybook -p 6006" }, "dependencies": { - "@jest/transform": "^24.9.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", + "@jest/transform": "^26.0.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", "@types/glob": "^7.1.1", - "@types/jest": "^24.0.16", + "@types/jest": "^25.1.1", "@types/jest-specific-snapshot": "^0.5.3", "babel-plugin-require-context-hook": "^1.0.0", "core-js": "^3.0.1", "glob": "^7.1.3", "global": "^4.3.2", - "jest-specific-snapshot": "^2.0.0", + "jest-specific-snapshot": "^3.0.0", + "pretty-format": "^25.5.0", "read-pkg-up": "^7.0.0", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { - "@storybook/addon-docs": "5.3.0-rc.0", - "@storybook/react": "5.3.0-rc.0", + "@storybook/addon-docs": "6.0.0-beta.21", + "@storybook/react": "6.0.0-beta.21", "babel-loader": "^8.0.6", + "enzyme": "^3.11.0", "enzyme-to-json": "^3.4.1", "jest-emotion": "^10.0.17", "react": "^16.8.3" }, + "peerDependencies": { + "react-dom": "*" + }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.test.ts b/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.test.ts index d26baf6ab696..0176f2e48b1e 100644 --- a/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.test.ts +++ b/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.test.ts @@ -36,7 +36,7 @@ describe('getPossibleStoriesFiles', () => { const storyshots = 'test/__snapshots__/foo.web.stories.storyshot'; const result = target.getPossibleStoriesFiles(storyshots); - const platformAgnosticResult = result.map(path => path.replace(/\\|\//g, '/')); + const platformAgnosticResult = result.map((path) => path.replace(/\\|\//g, '/')); expect(platformAgnosticResult).toEqual([ 'test/foo.web.stories.js', diff --git a/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts b/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts index a2514260446b..85425bae4d72 100644 --- a/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts +++ b/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts @@ -43,7 +43,7 @@ export class Stories2SnapsConverter { To fix it, add following to your jest.config.js: transform: { // should be above any other js transform like babel-jest - '^.+\\\\.stories\\\\.js$': '@storybook/addon-storyshots/injectFileName', + '^.+\\.stories\\.js$': '@storybook/addon-storyshots/injectFileName', } ` ); @@ -57,7 +57,7 @@ export class Stories2SnapsConverter { const { dir, name } = path.parse(storyshotFile); const { storiesExtensions } = this.options; - return storiesExtensions.map(ext => + return storiesExtensions.map((ext) => path.format({ dir: path.dirname(dir), name, diff --git a/addons/storyshots/storyshots-core/src/api/ensureOptionsDefaults.ts b/addons/storyshots/storyshots-core/src/api/ensureOptionsDefaults.ts index 06e0b9108145..25adf62c8343 100644 --- a/addons/storyshots/storyshots-core/src/api/ensureOptionsDefaults.ts +++ b/addons/storyshots/storyshots-core/src/api/ensureOptionsDefaults.ts @@ -25,6 +25,7 @@ function getIntegrityOptions({ integrityOptions }: StoryshotsOptions) { }; } +// @ts-ignore function ensureOptionsDefaults(options: StoryshotsOptions) { const { suite = 'Storyshots', @@ -49,7 +50,7 @@ function ensureOptionsDefaults(options: StoryshotsOptions) { testMethod, snapshotSerializers, integrityOptions, - }; + } as any; } export default ensureOptionsDefaults; diff --git a/addons/storyshots/storyshots-core/src/api/index.ts b/addons/storyshots/storyshots-core/src/api/index.ts index e77be5ede88f..b2f2a3f325a3 100644 --- a/addons/storyshots/storyshots-core/src/api/index.ts +++ b/addons/storyshots/storyshots-core/src/api/index.ts @@ -14,7 +14,7 @@ const methods: TestMethod[] = ['beforeAll', 'beforeEach', 'afterEach', 'afterAll function callTestMethodGlobals( testMethod: { [key in TestMethod]?: Function & { timeout?: number } } & { [key in string]: any } ) { - methods.forEach(method => { + methods.forEach((method) => { if (typeof testMethod[method] === 'function') { global[method](testMethod[method], testMethod[method].timeout); } @@ -70,10 +70,10 @@ function testStorySnapshots(options: StoryshotsOptions = {}) { } return acc; }, - [] as Array<{ + [] as { kind: string; children: any[]; - }> + }[] ); if (data.length) { diff --git a/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts b/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts index 4f3538d9a275..f11563e7ce5d 100644 --- a/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts +++ b/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts @@ -4,15 +4,6 @@ import glob from 'glob'; import { describe, it } from 'global'; import dedent from 'ts-dedent'; -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace,no-redeclare - namespace jest { - interface Matchers<R, T> { - notToBeAbandoned(stories2snapsConverter: any): R; - } - } -} - expect.extend({ notToBeAbandoned(storyshots, stories2snapsConverter) { const abandonedStoryshots = storyshots.filter((fileName: string) => { @@ -59,6 +50,7 @@ function integrityTest(integrityOptions: any, stories2snapsConverter: any) { const snapshotExtension = stories2snapsConverter.getSnapshotExtension(); const storyshots = glob.sync(`**/*${snapshotExtension}`, integrityOptions); + // @ts-ignore expect(storyshots).notToBeAbandoned(stories2snapsConverter); }); }); diff --git a/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts b/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts index 55f197980b50..a2564022f4e6 100644 --- a/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts +++ b/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts @@ -11,7 +11,7 @@ function snapshotTest({ item, asyncJest, framework, testMethod, testMethodParams it( name, () => - new Promise(done => + new Promise((done) => testMethod({ done, story: item, diff --git a/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts b/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts index 5ed9e544b7b4..8e25cc610a25 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts @@ -6,4 +6,5 @@ export type SupportedFramework = | 'riot' | 'react-native' | 'svelte' - | 'vue'; + | 'vue' + | 'rax'; diff --git a/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts index 63296e55cd25..aa55a1a1d2d3 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/helpers.ts @@ -45,7 +45,7 @@ const extractNgModuleMetadata = (importItem: any): NgModule => { } const ngModuleDecorator: NgModule | undefined = decorators.find( - decorator => decorator instanceof NgModule + (decorator) => decorator instanceof NgModule ); if (!ngModuleDecorator) { return null; @@ -58,7 +58,7 @@ const getExistenceOfComponentInModules = ( declarations: any[], imports: any[] ): boolean => { - if (declarations && declarations.some(declaration => declaration === component)) { + if (declarations && declarations.some((declaration) => declaration === component)) { // Found component in declarations array return true; } @@ -66,7 +66,7 @@ const getExistenceOfComponentInModules = ( return false; } - return imports.some(importItem => { + return imports.some((importItem) => { const extractedNgModuleMetadata = extractNgModuleMetadata(importItem); if (!extractedNgModuleMetadata) { // Not an NgModule diff --git a/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts index ea3a5fbfbaa6..0b7f2685ee7e 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts @@ -7,17 +7,17 @@ import { StoryshotsOptions } from '../../api/StoryshotsOptions'; function setupAngularJestPreset() { // Needed to prevent "Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten." - require.requireActual('core-js'); - require.requireActual('core-js/modules/es.promise'); - // require.requireActual('core-js/es6/reflect'); - // require.requireActual('core-js/es7/reflect'); + jest.requireActual('core-js'); + jest.requireActual('core-js/modules/es.promise'); + // jest.requireActual('core-js/es6/reflect'); + // jest.requireActual('core-js/es7/reflect'); // Angular + Jest + Storyshots = Crazy Shit: - // We need to require 'jest-preset-angular/setupJest' before any storybook code - // is running inside jest - one of the things that `jest-preset-angular/setupJest` does is + // We need to require 'jest-preset-angular/build/setupJest' before any storybook code + // is running inside jest - one of the things that `jest-preset-angular/build/setupJest` does is // extending the `window.Reflect` with all the needed metadata functions, that are required // for emission of the TS decorations like 'design:paramtypes' - require.requireActual('jest-preset-angular/setupJest'); + jest.requireActual('jest-preset-angular/build/setupJest'); } function test(options: StoryshotsOptions): boolean { @@ -29,13 +29,13 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { setupAngularJestPreset(); - const storybook = require.requireActual('@storybook/angular'); + const storybook = jest.requireActual('@storybook/angular'); configure({ ...options, storybook }); return { framework: 'angular' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for angular'); }, diff --git a/addons/storyshots/storyshots-core/src/frameworks/angular/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/renderTree.ts index 77b926157785..2e55f4bcaebb 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/renderTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/renderTree.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies -import AngularSnapshotSerializer from 'jest-preset-angular/AngularSnapshotSerializer'; +import AngularSnapshotSerializer from 'jest-preset-angular/build/AngularSnapshotSerializer'; // eslint-disable-next-line import/no-extraneous-dependencies -import HTMLCommentSerializer from 'jest-preset-angular/HTMLCommentSerializer'; +import HTMLCommentSerializer from 'jest-preset-angular/build/HTMLCommentSerializer'; // eslint-disable-next-line import/no-extraneous-dependencies import { TestBed } from '@angular/core/testing'; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts b/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts new file mode 100644 index 000000000000..76c88463876a --- /dev/null +++ b/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts @@ -0,0 +1,52 @@ +import { getPreviewFile, getMainFile } from './configure'; + +// eslint-disable-next-line global-require, jest/no-mocks-import +jest.mock('fs', () => require('../../../../../__mocks__/fs')); +const setupFiles = (files: Record<string, string>) => { + // eslint-disable-next-line no-underscore-dangle, global-require + require('fs').__setMockFiles(files); +}; + +describe('preview files', () => { + it.each` + filepath + ${'preview.ts'} + ${'preview.tsx'} + ${'preview.js'} + ${'preview.jsx'} + ${'config.ts'} + ${'config.tsx'} + ${'config.js'} + ${'config.jsx'} + `('resolves a valid preview file from $filepath', ({ filepath }) => { + setupFiles({ [`test/${filepath}`]: 'true' }); + + expect(getPreviewFile('test/')).toEqual(`test/${filepath}`); + }); + + it('returns false when none of the paths exist', () => { + setupFiles(Object.create(null)); + + expect(getPreviewFile('test/')).toEqual(false); + }); +}); + +describe('main files', () => { + it.each` + filepath + ${'main.ts'} + ${'main.tsx'} + ${'main.js'} + ${'main.jsx'} + `('resolves a valid main file path from $filepath', ({ filepath }) => { + setupFiles({ [`test/${filepath}`]: 'true' }); + + expect(getMainFile('test/')).toEqual(`test/${filepath}`); + }); + + it('returns false when none of the paths exist', () => { + setupFiles(Object.create(null)); + + expect(getPreviewFile('test/')).toEqual(false); + }); +}); diff --git a/addons/storyshots/storyshots-core/src/frameworks/configure.ts b/addons/storyshots/storyshots-core/src/frameworks/configure.ts index fd2689eb27d2..3ba9c866346e 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/configure.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/configure.ts @@ -21,37 +21,19 @@ interface Output { files: string[]; } -const getPreviewFile = (configDir: string): string | false => { - const preview = path.join(configDir, 'preview.js'); - const previewTS = path.join(configDir, 'preview.ts'); - const config = path.join(configDir, 'config.js'); - const configTS = path.join(configDir, 'config.ts'); - - if (isFile(previewTS)) { - return previewTS; - } - if (isFile(preview)) { - return preview; - } - if (isFile(configTS)) { - return configTS; - } - if (isFile(config)) { - return config; - } +const supportedExtensions = ['ts', 'tsx', 'js', 'jsx']; - return false; -}; +const resolveFile = (configDir: string, supportedFilenames: string[]) => + supportedFilenames + .flatMap((filename) => + supportedExtensions.map((ext) => path.join(configDir, `${filename}.${ext}`)) + ) + .find(isFile) || false; -const getMainFile = (configDir: string): string | false => { - const main = path.join(configDir, 'main.js'); +export const getPreviewFile = (configDir: string): string | false => + resolveFile(configDir, ['preview', 'config']); - if (isFile(main)) { - return main; - } - - return false; -}; +export const getMainFile = (configDir: string): string | false => resolveFile(configDir, ['main']); function getConfigPathParts(input: string): Output { const configDir = path.resolve(input); @@ -66,18 +48,15 @@ function getConfigPathParts(input: string): Output { output.files.push(preview); } if (main) { - const { stories = [] } = require.requireActual(main); + const { stories = [] } = jest.requireActual(main); output.stories = stories.map( (pattern: string | { path: string; recursive: boolean; match: string }) => { const { path: basePath, recursive, match } = toRequireContext(pattern); + const regex = new RegExp(match); + // eslint-disable-next-line no-underscore-dangle - return global.__requireContext( - configDir, - basePath, - recursive, - new RegExp(match.slice(1, -1)) - ); + return global.__requireContext(configDir, basePath, recursive, regex); } ); } @@ -102,8 +81,8 @@ function configure( const { files, stories } = getConfigPathParts(configPath); - files.forEach(f => { - require.requireActual(f); + files.forEach((f) => { + jest.requireActual(f); }); if (stories && stories.length) { diff --git a/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts b/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts index 2a09368902c3..0ea2ed2eaf5b 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts @@ -11,17 +11,17 @@ const isDirectory = (source: string) => fs.lstatSync(source).isDirectory(); function getLoaders(): Loader[] { return fs .readdirSync(__dirname) - .map(name => path.join(__dirname, name)) + .map((name) => path.join(__dirname, name)) .filter(isDirectory) - .map(framework => path.join(framework, loaderScriptName)) + .map((framework) => path.join(framework, loaderScriptName)) .filter(fs.existsSync) - .map(loader => require(loader).default); + .map((loader) => require(loader).default); } function loadFramework(options: StoryshotsOptions) { const loaders = getLoaders(); - const loader = loaders.find(frameworkLoader => frameworkLoader.test(options)); + const loader = loaders.find((frameworkLoader) => frameworkLoader.test(options)); if (!loader) { throw new Error( diff --git a/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts index 5a720e978d3a..9ca3cd51c88d 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts @@ -10,13 +10,13 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'html'; - const storybook = require.requireActual('@storybook/html'); + const storybook = jest.requireActual('@storybook/html'); configure({ ...options, storybook }); return { framework: 'html' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for HTML'); }, diff --git a/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts index 09eaa79a0698..eac1ef7d64a1 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts @@ -15,13 +15,13 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'preact'; - const storybook = require.requireActual('@storybook/preact'); + const storybook = jest.requireActual('@storybook/preact'); configure({ ...options, storybook }); return { framework: 'preact' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for preact'); }, diff --git a/addons/storyshots/storyshots-core/src/frameworks/rax/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/rax/loader.ts new file mode 100644 index 000000000000..9ea737584e50 --- /dev/null +++ b/addons/storyshots/storyshots-core/src/frameworks/rax/loader.ts @@ -0,0 +1,33 @@ +import global from 'global'; +import configure from '../configure'; +import hasDependency from '../hasDependency'; +import { Loader } from '../Loader'; +import { StoryshotsOptions } from '../../api/StoryshotsOptions'; + +function test(options: StoryshotsOptions): boolean { + return options.framework === 'rax' || (!options.framework && hasDependency('@storybook/rax')); +} + +function load(options: StoryshotsOptions) { + global.STORYBOOK_ENV = 'rax'; + + const storybook = jest.requireActual('@storybook/rax'); + + configure({ ...options, storybook }); + + return { + framework: 'rax' as const, + renderTree: jest.requireActual('./renderTree').default, + renderShallowTree: () => { + throw new Error('Shallow renderer is not supported for rax'); + }, + storybook, + }; +} + +const raxLoader: Loader = { + load, + test, +}; + +export default raxLoader; diff --git a/addons/storyshots/storyshots-core/src/frameworks/rax/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/rax/renderTree.ts new file mode 100644 index 000000000000..6046f0f6cc60 --- /dev/null +++ b/addons/storyshots/storyshots-core/src/frameworks/rax/renderTree.ts @@ -0,0 +1,12 @@ +// eslint-disable-next-line import/no-unresolved +import raxTestRenderer from 'rax-test-renderer'; + +function getRenderedTree(story: any, context: any, { renderer, ...rendererOptions }: any) { + const storyElement = story.render(); + const currentRenderer = renderer || raxTestRenderer.create; + const tree = currentRenderer(storyElement, rendererOptions); + + return tree; +} + +export default getRenderedTree; diff --git a/addons/storyshots/storyshots-core/src/frameworks/react-native/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/react-native/loader.ts index e1eab01b8758..29d89154e613 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react-native/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react-native/loader.ts @@ -20,11 +20,11 @@ function configure(options: StoryshotsOptions, storybook: any) { } const resolvedConfigPath = path.resolve(configPath); - require.requireActual(resolvedConfigPath); + jest.requireActual(resolvedConfigPath); } function load(options: StoryshotsOptions) { - const storybook = require.requireActual('@storybook/react-native'); + const storybook = jest.requireActual('@storybook/react-native'); configure(options, storybook); diff --git a/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts index 54383dce00f0..7d6e4388394f 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts @@ -8,14 +8,14 @@ function test(options: StoryshotsOptions): boolean { } function load(options: StoryshotsOptions) { - const storybook = require.requireActual('@storybook/react'); + const storybook = jest.requireActual('@storybook/react'); configure({ ...options, storybook }); return { framework: 'react' as const, - renderTree: require.requireActual('./renderTree').default, - renderShallowTree: require.requireActual('./renderShallowTree').default, + renderTree: jest.requireActual('./renderTree').default, + renderShallowTree: jest.requireActual('./renderShallowTree').default, storybook, }; } diff --git a/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts index 2d989a6e44a4..e9302c054b72 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts @@ -1,8 +1,11 @@ // eslint-disable-next-line import/no-extraneous-dependencies +import React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies import reactTestRenderer from 'react-test-renderer'; function getRenderedTree(story: any, context: any, { renderer, ...rendererOptions }: any) { - const storyElement = story.render(); + const StoryFn = story.render; + const storyElement = React.createElement(StoryFn); const currentRenderer = renderer || reactTestRenderer.create; const tree = currentRenderer(storyElement, rendererOptions); diff --git a/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts index 3223c5cf3895..bd2f7ce499af 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts @@ -5,7 +5,7 @@ import { Loader } from '../Loader'; import { StoryshotsOptions } from '../../api/StoryshotsOptions'; function mockRiotToIncludeCompiler() { - jest.mock('riot', () => require.requireActual('riot/riot.js')); + jest.mock('riot', () => jest.requireActual('riot/riot.js')); } function test(options: StoryshotsOptions): boolean { @@ -16,13 +16,13 @@ function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'riot'; mockRiotToIncludeCompiler(); - const storybook = require.requireActual('@storybook/riot'); + const storybook = jest.requireActual('@storybook/riot'); configure({ ...options, storybook }); return { framework: 'riot' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for riot'); }, diff --git a/addons/storyshots/storyshots-core/src/frameworks/riot/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/riot/renderTree.ts index ea866224600c..4800a1c3c737 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/riot/renderTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/riot/renderTree.ts @@ -1,6 +1,6 @@ import { document } from 'global'; -const riotForStorybook = require.requireActual('@storybook/riot'); +const riotForStorybook = jest.requireActual('@storybook/riot'); function bootstrapADocumentAndReturnANode() { const rootElement = document.createElement('div'); @@ -12,10 +12,11 @@ function bootstrapADocumentAndReturnANode() { function makeSureThatResultIsRenderedSomehow({ context, result, rootElement }: any) { if (!rootElement.firstChild) { + const { kind, name } = context; riotForStorybook.render({ storyFn: () => result, - selectedKind: context.kind, - selectedStory: context.name, + kind, + name, }); } } diff --git a/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts index 4ae5b02acfd3..b0ba76277558 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts @@ -13,13 +13,13 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'svelte'; - const storybook = require.requireActual('@storybook/svelte'); + const storybook = jest.requireActual('@storybook/svelte'); configure({ ...options, storybook }); return { framework: 'svelte' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for svelte'); }, diff --git a/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts index 2433026458a5..4ab5fad6fa36 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts @@ -5,7 +5,7 @@ import { Loader } from '../Loader'; import { StoryshotsOptions } from '../../api/StoryshotsOptions'; function mockVueToIncludeCompiler() { - jest.mock('vue', () => require.requireActual('vue/dist/vue.common.js')); + jest.mock('vue', () => jest.requireActual('vue/dist/vue.common.js')); } function test(options: StoryshotsOptions): boolean { @@ -16,13 +16,13 @@ function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'vue'; mockVueToIncludeCompiler(); - const storybook = require.requireActual('@storybook/vue'); + const storybook = jest.requireActual('@storybook/vue'); configure({ ...options, storybook }); return { framework: 'vue' as const, - renderTree: require.requireActual('./renderTree').default, + renderTree: jest.requireActual('./renderTree').default, renderShallowTree: () => { throw new Error('Shallow renderer is not supported for vue'); }, diff --git a/addons/storyshots/storyshots-core/src/test-bodies.ts b/addons/storyshots/storyshots-core/src/test-bodies.ts index 7fef69e0a145..d85fbe646a59 100644 --- a/addons/storyshots/storyshots-core/src/test-bodies.ts +++ b/addons/storyshots/storyshots-core/src/test-bodies.ts @@ -21,10 +21,20 @@ export const snapshotWithOptions = ( const result = renderTree(story, context, optionsOrCallOptions(options, story)); function match(tree: any) { + let target = tree; + const isReact = story.parameters.framework === 'react'; + + if (isReact && typeof tree.childAt === 'function') { + target = tree.childAt(0); + } + if (isReact && Array.isArray(tree.children)) { + [target] = tree.children; + } + if (snapshotFileName) { - expect(tree).toMatchSpecificSnapshot(snapshotFileName); + expect(target).toMatchSpecificSnapshot(snapshotFileName); } else { - expect(tree).toMatchSnapshot(); + expect(target).toMatchSnapshot(); } if (typeof tree.unmount === 'function') { diff --git a/addons/storyshots/storyshots-core/src/typings.d.ts b/addons/storyshots/storyshots-core/src/typings.d.ts index f334e98658b3..3f7c7d2cc952 100644 --- a/addons/storyshots/storyshots-core/src/typings.d.ts +++ b/addons/storyshots/storyshots-core/src/typings.d.ts @@ -2,5 +2,6 @@ declare module 'global'; declare module 'jest-preset-angular/*'; declare module 'preact-render-to-json'; declare module 'react-test-renderer*'; +declare module 'rax-test-renderer*'; declare module '@storybook/core/server'; declare module 'babel-plugin-require-context-hook/register'; diff --git a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.enzyme.test.js.snap b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.enzyme.test.js.snap index 75ec281cdcdc..2c7cbf790b68 100644 --- a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.enzyme.test.js.snap +++ b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.enzyme.test.js.snap @@ -246,7 +246,7 @@ exports[`Storyshots Welcome MDX to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> </InlineCode> .) @@ -450,7 +450,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> </InlineCode> .) diff --git a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallow.test.js.snap b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallow.test.js.snap index 445c7ba9a4fa..caccf51b2609 100644 --- a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallow.test.js.snap +++ b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallow.test.js.snap @@ -139,7 +139,7 @@ exports[`Storyshots Welcome MDX to Storybook 1`] = ` </InlineCode> stories located at  <InlineCode> - src/stories/index.js + src/stories/1-Button.stories.js </InlineCode> .) </p> @@ -213,7 +213,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` </InlineCode> stories located at  <InlineCode> - src/stories/index.js + src/stories/1-Button.stories.js </InlineCode> .) </p> diff --git a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap index 445c7ba9a4fa..caccf51b2609 100644 --- a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap +++ b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.shallowWithOptions.test.js.snap @@ -139,7 +139,7 @@ exports[`Storyshots Welcome MDX to Storybook 1`] = ` </InlineCode> stories located at  <InlineCode> - src/stories/index.js + src/stories/1-Button.stories.js </InlineCode> .) </p> @@ -213,7 +213,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` </InlineCode> stories located at  <InlineCode> - src/stories/index.js + src/stories/1-Button.stories.js </InlineCode> .) </p> diff --git a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.snapshotWithOptionsFunction.test.js.snap b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.snapshotWithOptionsFunction.test.js.snap index 30257648479e..8a70ef0ff961 100644 --- a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.snapshotWithOptionsFunction.test.js.snap +++ b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.snapshotWithOptionsFunction.test.js.snap @@ -213,7 +213,7 @@ exports[`Storyshots Welcome MDX to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> .) </p> @@ -387,7 +387,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> .) </p> diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/Async.stories.js b/addons/storyshots/storyshots-core/stories/required_with_context/Async.stories.js index ca2cef9114f7..d6e03732318b 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/Async.stories.js +++ b/addons/storyshots/storyshots-core/stories/required_with_context/Async.stories.js @@ -31,6 +31,4 @@ export default { }; export const withTimeout = () => <AsyncTestComponent />; -withTimeout.story = { - name: `with ${TIMEOUT}ms timeout simulating async operation`, -}; +withTimeout.storyName = `with ${TIMEOUT}ms timeout simulating async operation`; diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/Button.stories.js b/addons/storyshots/storyshots-core/stories/required_with_context/Button.stories.js index 1fc8a7b33e07..23ef66e18e57 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/Button.stories.js +++ b/addons/storyshots/storyshots-core/stories/required_with_context/Button.stories.js @@ -21,6 +21,4 @@ export const withSomeEmoji = () => ( </Button> ); -withSomeEmoji.story = { - name: 'with some emoji', -}; +withSomeEmoji.storyName = 'with some emoji'; diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/Welcome.stories.js b/addons/storyshots/storyshots-core/stories/required_with_context/Welcome.stories.js index df42de0bc96f..c1ffd1fbe5c5 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/Welcome.stories.js +++ b/addons/storyshots/storyshots-core/stories/required_with_context/Welcome.stories.js @@ -13,6 +13,4 @@ export default { export const toStorybook = () => <Welcome showApp={linkTo('Button')} />; -toStorybook.story = { - name: 'to Storybook', -}; +toStorybook.storyName = 'to Storybook'; diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.foo b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.foo index 4662f49c8761..9f31125b1177 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.foo +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.foo @@ -117,7 +117,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> .) </p> diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.storyshot b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.storyshot index 97712dfd14e0..357ca8ea27ac 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.storyshot +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Welcome.stories.storyshot @@ -117,7 +117,7 @@ exports[`Storyshots Welcome MDX to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> .) </p> @@ -291,7 +291,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` } } > - src/stories/index.js + src/stories/1-Button.stories.js </code> .) </p> diff --git a/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js b/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js index d5e9116c05e6..ceaa1447deff 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js @@ -21,7 +21,7 @@ class AnotherStories2SnapsConverter extends Stories2SnapsConverter { const [fileName] = name.split('@'); - return storiesExtensions.map(ext => + return storiesExtensions.map((ext) => path.format({ dir: path.dirname(dir), name: fileName, @@ -38,7 +38,7 @@ initStoryshots({ config: ({ configure }) => configure(() => { // eslint-disable-next-line global-require - require('../stories/directly_required'); + require('./directly_required'); }, module), test: multiSnapshotWithOptions(), }); diff --git a/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js b/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js index 6cd8a9a684e4..a21a0172c629 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js @@ -4,7 +4,7 @@ import initStoryshots, { shallowSnapshot } from '../dist'; initStoryshots({ framework: 'react', configPath: path.join(__dirname, '..', '.storybook'), - test: data => + test: (data) => shallowSnapshot({ ...data, }), diff --git a/addons/storyshots/storyshots-puppeteer/README.md b/addons/storyshots/storyshots-puppeteer/README.md index 718c0c012435..ce6919b987a7 100644 --- a/addons/storyshots/storyshots-puppeteer/README.md +++ b/addons/storyshots/storyshots-puppeteer/README.md @@ -8,9 +8,11 @@ Add the following modules into your app. npm install @storybook/addon-storyshots-puppeteer puppeteer --save-dev ``` +⚠️ As of Storybook 5.3 `puppeteer` is no more included in addon dependencies and must be added to your project directly. + ## Configure Storyshots for Puppeteeer tests -/\*\ **React-native** is **not supported** by this test function. +⚠️ **React-native** is **not supported** by this test function. When willing to run Puppeteer tests for your stories, you have two options: @@ -20,6 +22,7 @@ When willing to run Puppeteer tests for your stories, you have two options: Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served) ## _puppeteerTest_ + Allows to define arbitrary Puppeteer tests as `story.parameters.puppeteerTest` function. You can either create a new Storyshots instance or edit the one you previously used: @@ -32,17 +35,16 @@ initStoryshots({ suite: 'Puppeteer storyshots', test: puppeteerTest() }); ``` Then, in your stories: + ```js export const myExample = () => { ... }; -myExample.story = { - parameters: { - async puppeteerTest(page) { - const element = await page.$('<some-selector>'); - await element.click(); - expect(something).toBe(something); - }, +myExample.parameters = { + async puppeteerTest(page) { + const element = await page.$('<some-selector>'); + await element.click(); + expect(something).toBe(something); }, }; ``` @@ -78,7 +80,10 @@ import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; initStoryshots({ suite: 'Puppeteer storyshots', - test: puppeteerTest({ storybookUrl: 'file:///path/to/my/storybook-static' }), + test: puppeteerTest({ + storybookUrl: 'file:///path/to/my/storybook-static', + // storybookUrl: 'file://${path.resolve(__dirname, '../storybook-static')}' + }), }); ``` @@ -88,12 +93,14 @@ You might use `getGotoOptions` to specify options when the storybook is navigati ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; + const getGotoOptions = ({ context, url }) => { return { waitUntil: 'networkidle0', }; }; + initStoryshots({ suite: 'Puppeteer storyshots', test: puppeteerTest({ storybookUrl: 'http://localhost:6006', getGotoOptions }), @@ -125,7 +132,7 @@ import initStoryshots from '@storybook/addon-storyshots'; import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; import puppeteer from 'puppeteer'; -(async function() { +(async function () { initStoryshots({ suite: 'Puppeteer storyshots', test: puppeteerTest({ @@ -209,18 +216,20 @@ This can be achieved by adding a step before running the test ie: `npm run build If you run the Puppeteer storyshots against a running Storybook in dev mode, you don't have to worry about the stories being up-to-date because the dev-server is watching changes and rebuilds automatically. ## _axeTest_ + Runs [Axe](https://www.deque.com/axe/) accessibility checks and verifies that they pass using [jest-puppeteer-axe](https://github.com/WordPress/gutenberg/tree/master/packages/jest-puppeteer-axe). ```js import initStoryshots from '@storybook/addon-storyshots'; import { axeTest } from '@storybook/addon-storyshots-puppeteer'; -axeTest({ suite: 'A11y checks', test: axeTest() }); +initStoryshots({ suite: 'A11y checks', test: axeTest() }); ``` For configuration, it uses the same `story.parameters.a11y` parameter as [`@storybook/addon-a11y`](https://github.com/storybookjs/storybook/tree/next/addons/a11y#parameters) ## _imageSnapshots_ + Generates and compares screenshots of your stories using [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot). ```js @@ -246,14 +255,14 @@ const getMatchOptions = ({ context: { kind, story }, url }) => { }; }; const beforeScreenshot = (page, { context: { kind, story }, url }) => { - return new Promise(resolve => + return new Promise((resolve) => setTimeout(() => { resolve(); }, 600) ); }; const afterScreenshot = ({ image, context }) => { - return new Promise(resolve => + return new Promise((resolve) => setTimeout(() => { resolve(); }, 600) @@ -261,7 +270,12 @@ const afterScreenshot = ({ image, context }) => { }; initStoryshots({ suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot, afterScreenshot }), + test: imageSnapshot({ + storybookUrl: 'http://localhost:6006', + getMatchOptions, + beforeScreenshot, + afterScreenshot, + }), }); ``` diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index 71a0687c3116..cc46a1438abc 100644 --- a/addons/storyshots/storyshots-puppeteer/package.json +++ b/addons/storyshots/storyshots-puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storyshots-puppeteer", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Image snapshots addition to StoryShots based on puppeteer", "keywords": [ "addon", @@ -16,41 +16,49 @@ "directory": "addons/storyshots/storyshots-puppeteer" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", - "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../../scripts/prepare.js" }, "dependencies": { - "@hypnosphi/jest-puppeteer-axe": "^1.4.0", "@storybook/csf": "0.0.1", - "@storybook/node-logger": "5.3.0-rc.0", + "@storybook/node-logger": "6.0.0-beta.21", "@types/jest-image-snapshot": "^2.8.0", + "@wordpress/jest-puppeteer-axe": "^1.5.0", "core-js": "^3.0.1", - "jest-image-snapshot": "^2.8.2", + "jest-image-snapshot": "^3.0.1", "regenerator-runtime": "^0.13.3" }, "devDependencies": { + "@storybook/csf": "0.0.1", "@types/puppeteer": "^2.0.0" }, "peerDependencies": { - "@storybook/addon-storyshots": "5.3.0-rc.0", - "puppeteer": "^1.12.2 || ^2.0.0" + "@storybook/addon-storyshots": "6.0.0-beta.21", + "puppeteer": "^2.0.0 || ^3.0.0" }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a", "peerDependenciesMeta": { "puppeteer": { "optional": true } + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } } } diff --git a/addons/storyshots/storyshots-puppeteer/src/__tests__/url.test.ts b/addons/storyshots/storyshots-puppeteer/src/__tests__/url.test.ts index 4bac493cc6f2..d0f8ddbddf07 100644 --- a/addons/storyshots/storyshots-puppeteer/src/__tests__/url.test.ts +++ b/addons/storyshots/storyshots-puppeteer/src/__tests__/url.test.ts @@ -1,46 +1,50 @@ +import { toId } from '@storybook/csf'; + import { constructUrl } from '../url'; jest.mock('@storybook/node-logger'); +const id = toId('someKind', 'someStory'); + describe('Construct URL for Storyshots', () => { it('can use a url without path and without query params', () => { - expect(constructUrl('http://localhost:9001', 'someKind', 'someStory')).toEqual( + expect(constructUrl('http://localhost:9001', id)).toEqual( 'http://localhost:9001/iframe.html?id=somekind--somestory' ); }); it('can use a url without path (but slash) and without query params', () => { - expect(constructUrl('http://localhost:9001/', 'someKind', 'someStory')).toEqual( + expect(constructUrl('http://localhost:9001/', id)).toEqual( 'http://localhost:9001/iframe.html?id=somekind--somestory' ); }); it('can use a url without path and with query params', () => { - expect(constructUrl('http://localhost:9001?hello=world', 'someKind', 'someStory')).toEqual( + expect(constructUrl('http://localhost:9001?hello=world', id)).toEqual( 'http://localhost:9001/iframe.html?id=somekind--somestory&hello=world' ); }); it('can use a url without path (buth slash) and with query params', () => { - expect(constructUrl('http://localhost:9001/?hello=world', 'someKind', 'someStory')).toEqual( + expect(constructUrl('http://localhost:9001/?hello=world', id)).toEqual( 'http://localhost:9001/iframe.html?id=somekind--somestory&hello=world' ); }); it('can use a url with some path and query params', () => { - expect( - constructUrl('http://localhost:9001/nice-path?hello=world', 'someKind', 'someStory') - ).toEqual('http://localhost:9001/nice-path/iframe.html?id=somekind--somestory&hello=world'); + expect(constructUrl('http://localhost:9001/nice-path?hello=world', id)).toEqual( + 'http://localhost:9001/nice-path/iframe.html?id=somekind--somestory&hello=world' + ); }); it('can use a url with some path (slash) and query params', () => { - expect( - constructUrl('http://localhost:9001/nice-path/?hello=world', 'someKind', 'someStory') - ).toEqual('http://localhost:9001/nice-path/iframe.html?id=somekind--somestory&hello=world'); + expect(constructUrl('http://localhost:9001/nice-path/?hello=world', id)).toEqual( + 'http://localhost:9001/nice-path/iframe.html?id=somekind--somestory&hello=world' + ); }); it('can use a url with file protocol', () => { - expect(constructUrl('file://users/storybook', 'someKind', 'someStory')).toEqual( + expect(constructUrl('file://users/storybook', id)).toEqual( 'file://users/storybook/iframe.html?id=somekind--somestory' ); }); diff --git a/addons/storyshots/storyshots-puppeteer/src/axeTest.ts b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts index 920af43c09e9..02ad85001608 100644 --- a/addons/storyshots/storyshots-puppeteer/src/axeTest.ts +++ b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts @@ -1,4 +1,4 @@ -import '@hypnosphi/jest-puppeteer-axe'; +import '@wordpress/jest-puppeteer-axe'; import { defaultCommonConfig, CommonConfig } from './config'; import { puppeteerTest } from './puppeteerTest'; diff --git a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts index 49083c46ea66..fbdd5233e5c1 100644 --- a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts +++ b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts @@ -19,7 +19,7 @@ export const puppeteerTest = (customConfig: Partial<PuppeteerTestConfig> = {}) = let page: Page; // Hold ref to the page to screenshot. const testFn = async ({ context }: any) => { - const { kind, framework, name } = context; + const { kind, framework, name, id } = context; if (framework === 'react-native') { // Skip tests since RN is not a browser environment. logger.error( @@ -29,7 +29,7 @@ export const puppeteerTest = (customConfig: Partial<PuppeteerTestConfig> = {}) = return; } - const url = constructUrl(storybookUrl, kind, name); + const url = constructUrl(storybookUrl, id); const options = { context, url }; if (testBody.filter != null && !testBody.filter(options)) { return; diff --git a/addons/storyshots/storyshots-puppeteer/src/url.ts b/addons/storyshots/storyshots-puppeteer/src/url.ts index 6c5a1eaef648..f9f75a90cfff 100644 --- a/addons/storyshots/storyshots-puppeteer/src/url.ts +++ b/addons/storyshots/storyshots-puppeteer/src/url.ts @@ -1,10 +1,6 @@ -import { toId } from '@storybook/csf'; - import { URL } from 'url'; -export const constructUrl = (storybookUrl: string, kind: string, story: string) => { - const id = toId(kind, story); - +export const constructUrl = (storybookUrl: string, id: string) => { const storyUrl = `/iframe.html?id=${id}`; const { protocol, host, pathname, search } = new URL(storybookUrl); const pname = pathname.replace(/\/$/, ''); // removes trailing / diff --git a/addons/storysource/README.md b/addons/storysource/README.md index cce9a9e99a43..3b3c1ea1e69c 100644 --- a/addons/storysource/README.md +++ b/addons/storysource/README.md @@ -18,29 +18,33 @@ You can add configuration for this addon by using a preset or by using the addon ### Install using preset -Add the following to your `.storybook/presets.js` exports: +Add the following to your `.storybook/main.js` exports: ```js -module.exports = ['@storybook/addon-storysource/preset']; +module.exports = { + addons: ['@storybook/addon-storysource'], +}; ``` -You can pass configurations into the addon-storysource loader in your `.storybook/presets.js` file, e.g.: +You can pass configurations into the addon-storysource loader in your `.storybook/main.js` file, e.g.: -```javascript -module.exports = [ - { - name: '@storybook/addon-storysource/preset', - options: { - rule: { - // test: [/\.stories\.jsx?$/], This is default - include: [path.resolve(__dirname, '../src')], // You can specify directories - }, - loaderOptions: { - prettierConfig: { printWidth: 80, singleQuote: false }, +```js +module.exports = { + addons: [ + { + name: '@storybook/addon-storysource', + options: { + rule: { + // test: [/\.stories\.jsx?$/], This is default + include: [path.resolve(__dirname, '../src')], // You can specify directories + }, + loaderOptions: { + prettierConfig: { printWidth: 80, singleQuote: false }, + }, }, }, - }, -]; + ], +}; ``` ## Loader Options diff --git a/addons/storysource/docs/demo.gif b/addons/storysource/docs/demo.gif index 2f388596b5e5..e46a4f561fcf 100644 Binary files a/addons/storysource/docs/demo.gif and b/addons/storysource/docs/demo.gif differ diff --git a/addons/storysource/loader.js b/addons/storysource/loader.js deleted file mode 100644 index cde76952e169..000000000000 --- a/addons/storysource/loader.js +++ /dev/null @@ -1,6 +0,0 @@ -const deprecate = require('util-deprecate'); - -deprecate(() => {}, -'@storybook/addon-storysource/loader is deprecated, please use @storybook/source-loader instead.')(); - -module.exports = require('@storybook/source-loader'); diff --git a/addons/storysource/package.json b/addons/storysource/package.json index 8def523c07b5..1c8c34fc2bef 100644 --- a/addons/storysource/package.json +++ b/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Stories addon for storybook", "keywords": [ "addon", @@ -16,38 +16,46 @@ "directory": "addons/storysource" }, "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/index.js", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/router": "5.3.0-rc.0", - "@storybook/source-loader": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/router": "6.0.0-beta.21", + "@storybook/source-loader": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "estraverse": "^4.2.0", - "loader-utils": "^1.2.3", - "prettier": "^1.16.4", + "loader-utils": "^2.0.0", + "prettier": "^2.0.5", "prop-types": "^15.7.2", - "react-syntax-highlighter": "^11.0.2", - "regenerator-runtime": "^0.13.3", - "util-deprecate": "^1.0.2" + "react": "^16.9.17", + "react-syntax-highlighter": "^12.2.1", + "regenerator-runtime": "^0.13.3" + }, + "devDependencies": { + "@types/react": "^16.9.27", + "@types/react-syntax-highlighter": "^11.0.4" }, "peerDependencies": { "@storybook/source-loader": "*", - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/storysource/src/StoryPanel.js b/addons/storysource/src/StoryPanel.js deleted file mode 100644 index 30ddeb8cf58c..000000000000 --- a/addons/storysource/src/StoryPanel.js +++ /dev/null @@ -1,188 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { styled } from '@storybook/theming'; -import { Link } from '@storybook/router'; -import { SyntaxHighlighter } from '@storybook/components'; - -import createElement from 'react-syntax-highlighter/dist/esm/create-element'; -import { EVENT_ID } from './events'; - -const StyledStoryLink = styled(Link)(({ theme }) => ({ - display: 'block', - textDecoration: 'none', - borderRadius: theme.appBorderRadius, - color: 'inherit', - - '&:hover': { - background: theme.background.hoverable, - }, -})); - -const SelectedStoryHighlight = styled.div(({ theme }) => ({ - background: theme.background.hoverable, - borderRadius: theme.appBorderRadius, -})); - -const StyledSyntaxHighlighter = styled(SyntaxHighlighter)(({ theme }) => ({ - fontSize: theme.typography.size.s2 - 1, -})); - -const areLocationsEqual = (a, b) => - a.startLoc.line === b.startLoc.line && - a.startLoc.col === b.startLoc.col && - a.endLoc.line === b.endLoc.line && - a.endLoc.col === b.endLoc.col; - -const getLocationKeys = locationsMap => - locationsMap - ? Array.from(Object.keys(locationsMap)).sort( - (key1, key2) => locationsMap[key1].startLoc.line - locationsMap[key2].startLoc.line - ) - : []; - -export default class StoryPanel extends Component { - state = { source: 'loading source...' }; - - componentDidMount() { - this.mounted = true; - const { api } = this.props; - - api.on(EVENT_ID, this.listener); - } - - componentDidUpdate() { - if (this.selectedStoryRef) { - this.selectedStoryRef.scrollIntoView(); - } - } - - componentWillUnmount() { - const { api } = this.props; - - api.off(EVENT_ID, this.listener); - } - - setSelectedStoryRef = ref => { - this.selectedStoryRef = ref; - }; - - listener = ({ edition: { source }, location: { currentLocation, locationsMap } }) => { - const locationsKeys = getLocationKeys(locationsMap); - this.setState({ - source, - currentLocation, - locationsMap, - locationsKeys, - }); - }; - - createPart = (rows, stylesheet, useInlineStyles) => - rows.map((node, i) => - createElement({ - node, - stylesheet, - useInlineStyles, - key: `code-segement${i}`, - }) - ); - - createStoryPart = (rows, stylesheet, useInlineStyles, location, id) => { - const { currentLocation } = this.state; - const first = location.startLoc.line - 1; - const last = location.endLoc.line; - - const storyRows = rows.slice(first, last); - const story = this.createPart(storyRows, stylesheet, useInlineStyles); - const storyKey = `${first}-${last}`; - - if (location && currentLocation && areLocationsEqual(location, currentLocation)) { - return ( - <SelectedStoryHighlight key={storyKey} ref={this.setSelectedStoryRef}> - {story} - </SelectedStoryHighlight> - ); - } - - return ( - <StyledStoryLink to={`/story/${id}`} key={storyKey}> - {story} - </StyledStoryLink> - ); - }; - - createParts = (rows, stylesheet, useInlineStyles) => { - const { locationsMap, locationsKeys } = this.state; - - const parts = []; - let lastRow = 0; - - locationsKeys.forEach(key => { - const location = locationsMap[key]; - const first = location.startLoc.line - 1; - const last = location.endLoc.line; - - const start = this.createPart(rows.slice(lastRow, first), stylesheet, useInlineStyles); - const storyPart = this.createStoryPart(rows, stylesheet, useInlineStyles, location, key); - - parts.push(start); - parts.push(storyPart); - - lastRow = last; - }); - - const lastPart = this.createPart(rows.slice(lastRow), stylesheet, useInlineStyles); - - parts.push(lastPart); - - return parts; - }; - - lineRenderer = ({ rows, stylesheet, useInlineStyles }) => { - const { locationsMap, locationsKeys } = this.state; - - // because of the usage of lineRenderer, all lines will be wrapped in a span - // these spans will receive all classes on them for some reason - // which makes colours casecade incorrectly - // this removed that list of classnames - const myrows = rows.map(({ properties, ...rest }) => ({ - ...rest, - properties: { className: [] }, - })); - - if (!locationsMap || !locationsKeys.length) { - return this.createPart(myrows, stylesheet, useInlineStyles); - } - - const parts = this.createParts(myrows, stylesheet, useInlineStyles); - - return <span>{parts}</span>; - }; - - render() { - const { active } = this.props; - const { source } = this.state; - - return active ? ( - <StyledSyntaxHighlighter - language="jsx" - showLineNumbers="true" - renderer={this.lineRenderer} - format={false} - copyable={false} - padded - > - {source} - </StyledSyntaxHighlighter> - ) : null; - } -} - -StoryPanel.propTypes = { - active: PropTypes.bool.isRequired, - api: PropTypes.shape({ - selectStory: PropTypes.func.isRequired, - emit: PropTypes.func, - on: PropTypes.func, - off: PropTypes.func, - }).isRequired, -}; diff --git a/addons/storysource/src/StoryPanel.tsx b/addons/storysource/src/StoryPanel.tsx new file mode 100644 index 000000000000..dd4c1c7b51ab --- /dev/null +++ b/addons/storysource/src/StoryPanel.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { API } from '@storybook/api'; +import { styled } from '@storybook/theming'; +import { Link } from '@storybook/router'; +import { + SyntaxHighlighter, + SyntaxHighlighterProps, + SyntaxHighlighterRendererProps, + createSyntaxHighlighterElement, +} from '@storybook/components'; + +import { SourceBlock, LocationsMap } from '@storybook/source-loader'; +import { Story } from '@storybook/api/dist/lib/stories'; + +const StyledStoryLink = styled(Link)<{ to: string; key: string }>(({ theme }) => ({ + display: 'block', + textDecoration: 'none', + borderRadius: theme.appBorderRadius, + color: 'inherit', + + '&:hover': { + background: theme.background.hoverable, + }, +})); + +const SelectedStoryHighlight = styled.div(({ theme }) => ({ + background: theme.background.hoverable, + borderRadius: theme.appBorderRadius, +})); + +const StyledSyntaxHighlighter = styled(SyntaxHighlighter)<SyntaxHighlighterProps>(({ theme }) => ({ + fontSize: theme.typography.size.s2 - 1, +})); + +const areLocationsEqual = (a: SourceBlock, b: SourceBlock): boolean => + a.startLoc.line === b.startLoc.line && + a.startLoc.col === b.startLoc.col && + a.endLoc.line === b.endLoc.line && + a.endLoc.col === b.endLoc.col; + +interface StoryPanelProps { + api: API; +} + +interface SourceParams { + source: string; + locationsMap: LocationsMap; +} +export const StoryPanel: React.FC<StoryPanelProps> = ({ api }) => { + const [state, setState] = React.useState<SourceParams & { currentLocation?: SourceBlock }>({ + source: 'loading source...', + locationsMap: {}, + }); + + const story: Story | undefined = api.getCurrentStoryData() as Story; + const selectedStoryRef = React.useRef<HTMLDivElement>(null); + React.useEffect(() => { + if (story) { + const { + parameters: { + // @ts-ignore + storySource: { source, locationsMap } = { source: '', locationsMap: {} }, + } = {}, + } = story; + const currentLocation = locationsMap + ? locationsMap[ + Object.keys(locationsMap).find((key: string) => { + const sourceLoaderId = key.split('--'); + return story.id.endsWith(sourceLoaderId[sourceLoaderId.length - 1]); + }) + ] + : undefined; + setState({ source, locationsMap, currentLocation }); + } + }, [story ? story.id : null]); + React.useEffect(() => { + if (selectedStoryRef.current) { + selectedStoryRef.current.scrollIntoView(); + } + }, [selectedStoryRef.current]); + + const { source, locationsMap, currentLocation } = state; + + const createPart = ({ rows, stylesheet, useInlineStyles }: SyntaxHighlighterRendererProps) => + rows.map((node, i) => + createSyntaxHighlighterElement({ + node, + stylesheet, + useInlineStyles, + key: `code-segement${i}`, + }) + ); + + const createStoryPart = ({ + rows, + stylesheet, + useInlineStyles, + location, + id, + refId, + }: SyntaxHighlighterRendererProps & { location: SourceBlock; id: string; refId?: string }) => { + const first = location.startLoc.line - 1; + const last = location.endLoc.line; + + const storyRows = rows.slice(first, last); + const storySource = createPart({ rows: storyRows, stylesheet, useInlineStyles }); + const storyKey = `${first}-${last}`; + + if (currentLocation && areLocationsEqual(location, currentLocation)) { + return ( + <SelectedStoryHighlight key={storyKey} ref={selectedStoryRef}> + {storySource} + </SelectedStoryHighlight> + ); + } + return ( + <StyledStoryLink to={refId ? `/story/${refId}_${id}` : `/story/${id}`} key={storyKey}> + {storySource} + </StyledStoryLink> + ); + }; + + const createParts = ({ rows, stylesheet, useInlineStyles }: SyntaxHighlighterRendererProps) => { + const parts = []; + let lastRow = 0; + + Object.keys(locationsMap).forEach((key) => { + const location = locationsMap[key]; + const first = location.startLoc.line - 1; + const last = location.endLoc.line; + const { kind, refId } = story; + // source loader ids are different from story id + const sourceIdParts = key.split('--'); + const id = api.storyId(kind, sourceIdParts[sourceIdParts.length - 1]); + const start = createPart({ rows: rows.slice(lastRow, first), stylesheet, useInlineStyles }); + const storyPart = createStoryPart({ rows, stylesheet, useInlineStyles, location, id, refId }); + + parts.push(start); + parts.push(storyPart); + + lastRow = last; + }); + + const lastPart = createPart({ rows: rows.slice(lastRow), stylesheet, useInlineStyles }); + + parts.push(lastPart); + + return parts; + }; + + const lineRenderer = ({ + rows, + stylesheet, + useInlineStyles, + }: SyntaxHighlighterRendererProps): React.ReactNode => { + // because of the usage of lineRenderer, all lines will be wrapped in a span + // these spans will receive all classes on them for some reason + // which makes colours casecade incorrectly + // this removed that list of classnames + const myrows = rows.map(({ properties, ...rest }) => ({ + ...rest, + properties: { className: [] }, + })); + + if (!locationsMap || !Object.keys(locationsMap).length) { + return createPart({ rows: myrows, stylesheet, useInlineStyles }); + } + + const parts = createParts({ rows: myrows, stylesheet, useInlineStyles }); + + return <span>{parts}</span>; + }; + return ( + <StyledSyntaxHighlighter + language="jsx" + showLineNumbers + renderer={lineRenderer} + format={false} + copyable={false} + padded + > + {source} + </StyledSyntaxHighlighter> + ); +}; diff --git a/addons/storysource/src/events.js b/addons/storysource/src/events.js deleted file mode 100644 index 80572092f96e..000000000000 --- a/addons/storysource/src/events.js +++ /dev/null @@ -1,3 +0,0 @@ -export const ADDON_ID = 'storybook/source-loader'; -export const PANEL_ID = `${ADDON_ID}/panel`; -export const EVENT_ID = `${ADDON_ID}/set`; diff --git a/addons/storysource/src/events.ts b/addons/storysource/src/events.ts new file mode 100644 index 000000000000..9436b8a75114 --- /dev/null +++ b/addons/storysource/src/events.ts @@ -0,0 +1,2 @@ +export const ADDON_ID = 'storybook/source-loader'; +export const PANEL_ID = `${ADDON_ID}/panel`; diff --git a/addons/storysource/src/index.js b/addons/storysource/src/index.js deleted file mode 100644 index 0bd2bb285dc3..000000000000 --- a/addons/storysource/src/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { ADDON_ID, PANEL_ID, EVENT_ID } from './events'; -import { withStorySource } from './preview'; - -export { ADDON_ID, PANEL_ID, EVENT_ID, withStorySource }; - -if (module && module.hot && module.hot.decline) { - module.hot.decline(); -} diff --git a/addons/storysource/src/index.ts b/addons/storysource/src/index.ts new file mode 100644 index 000000000000..a5cd9202f547 --- /dev/null +++ b/addons/storysource/src/index.ts @@ -0,0 +1,7 @@ +import { ADDON_ID, PANEL_ID } from './events'; + +export { ADDON_ID, PANEL_ID }; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/addons/storysource/src/manager.js b/addons/storysource/src/manager.js deleted file mode 100644 index 4ca5c8414056..000000000000 --- a/addons/storysource/src/manager.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import addons from '@storybook/addons'; - -import StoryPanel from './StoryPanel'; -import { ADDON_ID, PANEL_ID } from '.'; - -export function register() { - addons.register(ADDON_ID, api => { - addons.addPanel(PANEL_ID, { - title: 'Story', - render: ({ active, key }) => <StoryPanel key={key} api={api} active={active} />, - paramKey: 'storysource', - }); - }); -} diff --git a/addons/storysource/src/manager.tsx b/addons/storysource/src/manager.tsx new file mode 100644 index 000000000000..1d5351c1cac5 --- /dev/null +++ b/addons/storysource/src/manager.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import addons from '@storybook/addons'; + +import { StoryPanel } from './StoryPanel'; +import { ADDON_ID, PANEL_ID } from '.'; + +export function register() { + addons.register(ADDON_ID, (api) => { + addons.addPanel(PANEL_ID, { + title: 'Story', + render: ({ active, key }) => (active ? <StoryPanel key={key} api={api} /> : null), + paramKey: 'storysource', + }); + }); +} diff --git a/addons/storysource/src/preset.js b/addons/storysource/src/preset.js index 34ff01a97f17..3bf0a6d9fab6 100644 --- a/addons/storysource/src/preset.js +++ b/addons/storysource/src/preset.js @@ -24,8 +24,8 @@ function webpack(webpackConfig = {}, options = {}) { }; } -function addons(entry = []) { +function managerEntries(entry = []) { return [...entry, require.resolve('@storybook/addon-storysource/register')]; } -module.exports = { webpack, addons }; +module.exports = { webpack, managerEntries }; diff --git a/addons/storysource/src/preview.js b/addons/storysource/src/preview.js deleted file mode 100644 index 93a15a9ed92e..000000000000 --- a/addons/storysource/src/preview.js +++ /dev/null @@ -1,21 +0,0 @@ -import addons from '@storybook/addons'; -import { EVENT_ID } from './events'; - -const getLocation = (context, locationsMap) => locationsMap[context.id]; - -function setStorySource(context, source, locationsMap) { - const currentLocation = getLocation(context, locationsMap); - - addons.getChannel().emit(EVENT_ID, { - source, - currentLocation, - locationsMap, - }); -} - -export function withStorySource(source, locationsMap = {}) { - return (storyFn, context) => { - setStorySource(context, source, locationsMap); - return storyFn(); - }; -} diff --git a/addons/storysource/theming-light-dark.png b/addons/storysource/theming-light-dark.png new file mode 100644 index 000000000000..e162bc2e0d89 Binary files /dev/null and b/addons/storysource/theming-light-dark.png differ diff --git a/addons/centered/tsconfig.json b/addons/storysource/tsconfig.json similarity index 100% rename from addons/centered/tsconfig.json rename to addons/storysource/tsconfig.json diff --git a/addons/toolbars/README.md b/addons/toolbars/README.md new file mode 100644 index 000000000000..4fc67c669dfd --- /dev/null +++ b/addons/toolbars/README.md @@ -0,0 +1,226 @@ +<center> + <img src="https://raw.githubusercontent.com/storybookjs/storybook/next/addons/toolbars/docs/hero.gif" width="100%" /> +</center> + +<h1>Storybook Addon Toolbars</h1> + +The Toolbars addon controls global story rendering options from Storybook's toolbar UI. It's a general purpose addon that can be used to: + +- set a theme for your components +- set your components' internationalization (i18n) locale +- configure just about anything in Storybook that makes use of a global variable + +Toolbars is built on top of [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs) (SB6.0+): dynamic variables that trigger a story re-render when they are set. + +- [Get started](#get-started) + - [Installation](#installation) + - [Configure menu UI](#configure-menu-ui) + - [Create a decorator](#create-a-decorator) +- [Advanced usage](#advanced-usage) + - [Advanced menu configuration](#advanced-menu-configuration) + - [Consuming global args from within a story](#consuming-global-args-from-within-a-story) + - [Consuming global args from within an addon](#consuming-global-args-from-within-an-addon) +- [FAQs](#faqs) + - [How does this compare to `addon-contexts`?](#how-does-this-compare-to-addon-contexts) + +## Get started + +To get started with `addon-toolbars`: + +1. [install the addon](#installation), +2. [configure the menu UI](#configure-menu-ui) +3. [Create a decorator to implement custom logic](#create-a-decorator). + +### Installation + +First, install the package: + +```sh +npm install @storybook/addon-toolbars -D # or yarn +``` + +Then add it to your `.storybook/main.js` config: + +```js +module.exports = { + addons: ['@storybook/addon-toolbars'], +}; +``` + +### Configure menu UI + +Addon-toolbars has a simple, declarative syntax for configuring toolbar menus. You can add toolbars by adding `globalArgTypes` with a `toolbar` annotation, in `.storybook/preview.js`: + +```js +export const globalArgTypes = { + theme: { + name: 'Theme' + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + // array of plain string values or MenuItem shape (see below) + items: ['light', 'dark'], + }, + }, +}; +``` + +You should see a dropdown in your toolbar with options `light` and `dark`. + +### Create a decorator + +Now, let's wire it up! We can consume our new `theme` global arg in a decorator using the `context.globalArgs.theme` value. + +For example, suppose you are using `styled-components`. You can add a theme provider decorator to your `.storybook/preview.js` config: + +```ts +import { ThemeProvider } from 'styled-components'; +import { StoryContext, StoryGetter, StoryWrapper } from '@storybook/addons'; + +const withThemeProvider: StoryWrapper = (Story: StoryGetter, context: StoryContext) => { + // context.globalArgs.theme here will be either 'light' or 'dark' + // getTheme being a function retrieving the actual theme object from that value + const theme = getTheme(context.globalArgs.theme); + + return ( + <ThemeProvider theme={theme}> + <Story {...context} /> + </ThemeProvider> + ); +}; + +export const decorators = [withThemeProvider]; +``` + +## Advanced usage + +The previous section shows the common case. There are two advanced use cases: (1) [advanced menu configurations](#advanced-menu-configuration), (2) [consuming global args inside a story](#consuming-global-args-from-within-a-story). + +### Advanced menu configuration + +The default menu configuration is simple: everything's a string! However, the Toolbars addon also support configuration options to tweak the appearance of the menu: + +```ts +type MenuItem { + /** + * The string value of the menu that gets set in the global args + */ + value: string, + /** + * The main text of the title + */ + title: string, + /** + * A string that gets shown in left side of the menu, if set + */ + left?: string, + /** + * A string that gets shown in right side of the menu, if set + */ + right?: string, + /** + * An icon that gets shown in the toolbar if this item is selected + */ + icon?: icon, +} +``` + +Thus if you want to show right-justified flags for an internationalization locale, you might set up the following configuration in `.storybook/preview.js`: + +```js +export const globalArgTypes = { + locale: { + name: 'Locale', + description: 'Internationalization locale', + defaultValue: 'en', + toolbar: { + icon: 'globe', + items: [ + { value: 'en', right: '🇺🇸', title: 'English' }, + { value: 'fr', right: '🇫🇷', title: 'Français' }, + { value: 'es', right: '🇪🇸', title: 'Español' }, + { value: 'zh', right: '🇨🇳', title: '中文' }, + { value: 'kr', right: '🇰🇷', title: '한국어' }, + ], + }, + }, +}; +``` + +### Consuming global args from within a story + +The recommended usage, as shown in the examples above, is to consume global args from within a decorator and implement a global setting that applies to all stories. But sometimes it's useful to use toolbar options inside individual stories. + +Storybook's `globalArgs` are available via the story context: + +```js +const getCaptionForLocale = (locale) => { + switch(locale) { + case 'es': return 'Hola!'; + case 'fr': return 'Bonjour!'; + case 'kr': return '안녕하세요!'; + case 'zh': return '你好!'; + default: + return 'Hello!', + } +} + +export const StoryWithLocale = (args, { globalArgs: { locale } }) => { + const caption = getCaptionForLocale(locale); + return <>{caption}</>; +}; +``` + +**NOTE:** In Storybook 6.0, if you set the global option `passArgsFirst: false` for backwards compatibility, the story context is passes as the second argument: + +```js +export const StoryWithLocale = ({ globalArgs: { locale } }) => { + const caption = getCaptionForLocale(locale); + return <>{caption}</>; +}; +``` + +### Consuming global args from within an addon + +There is a hook available in `@storybook/api` to retrieve the global args: `useGlobalArgs()` + +Following the previous example of the ThemeProvider, if you want for instance to display the current theme inside a Panel: + +```js +import { useGlobalArgs } from '@storybook/api'; +import { AddonPanel, Placeholder, Separator, Source, Spaced, Title } from '@storybook/components'; + +const ThemePanel = props => { + const [{ theme: themeName }] = useGlobalArgs(); + const theme = getTheme(themeName); + + return ( + <AddonPanel {...props}> + {theme ? ( + <Spaced row={3} outer={1}> + <Title>{theme.name} +

The full theme object/p> + + + ) : ( + No theme selected + )} + + ); +}; +``` + +## FAQs + +### How does this compare to `addon-contexts`? + +`Addon-toolbars` is the successor to `addon-contexts`, which provided convenient global toolbars in Storybook's toolbar. + +The primary difference between the two packages is that `addon-toolbars` makes use of Storybook's new **Story Args** feature, which has the following advantages: + +- **Standardization**. Args are built into Storybook in 6.x. Since `addon-toolbars` is based on args, you don't need to learn any addon-specific APIs to use it. + +- **Ergonomics**. Global args are easy to consume [in stories](#consuming-global-args-from-within-a-story), in [Storybook Docs](https://github.com/storybookjs/storybook/tree/master/addons/docs), or even in other addons. + +* **Framework compatibility**. Args are completely framework-independent, so `addon-toolbars` is compatible with React, Vue, Angular, etc. out of the box with no framework logic needed in the addon. diff --git a/addons/toolbars/docs/hero.gif b/addons/toolbars/docs/hero.gif new file mode 100644 index 000000000000..0a07b42c403c Binary files /dev/null and b/addons/toolbars/docs/hero.gif differ diff --git a/addons/toolbars/package.json b/addons/toolbars/package.json new file mode 100644 index 000000000000..85d94aac88e1 --- /dev/null +++ b/addons/toolbars/package.json @@ -0,0 +1,46 @@ +{ + "name": "@storybook/addon-toolbars", + "version": "6.0.0-beta.21", + "description": "Storybook toolbars addon", + "keywords": [ + "addon", + "storybook", + "theming", + "i18n", + "internationalization" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/next/addons/toolbars", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "addons/toolbars" + }, + "license": "MIT", + "main": "dist/register.js", + "files": [ + "dist/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "core-js": "^3.0.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/addons/toolbars/preset.js b/addons/toolbars/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/toolbars/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/toolbars/register.js b/addons/toolbars/register.js new file mode 100644 index 000000000000..06b5a5887266 --- /dev/null +++ b/addons/toolbars/register.js @@ -0,0 +1 @@ +export * from './dist/register'; diff --git a/addons/toolbars/src/components/MenuToolbar.tsx b/addons/toolbars/src/components/MenuToolbar.tsx new file mode 100644 index 000000000000..161108e36d51 --- /dev/null +++ b/addons/toolbars/src/components/MenuToolbar.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; +import { useGlobalArgs } from '@storybook/api'; +import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; +import { NormalizedToolbarArgType } from '../types'; + +export type MenuToolbarProps = NormalizedToolbarArgType & { id: string }; + +export const MenuToolbar: FC = ({ + id, + name, + description, + toolbar: { icon, items }, +}) => { + const [globalArgs, updateGlobalArgs] = useGlobalArgs(); + const selectedValue = globalArgs[id]; + const active = selectedValue != null; + const selectedItem = active && items.find((item) => item.value === selectedValue); + + return ( + { + const links = items.map((item) => { + const { value, left, title, right } = item; + return { + id: value, + left, + title, + right, + active: selectedValue === value, + onClick: () => { + updateGlobalArgs({ [id]: value }); + onHide(); + }, + }; + }); + return ; + }} + closeOnClick + > + + + + + ); +}; diff --git a/addons/toolbars/src/components/ToolbarManager.tsx b/addons/toolbars/src/components/ToolbarManager.tsx new file mode 100644 index 000000000000..215ae20c9080 --- /dev/null +++ b/addons/toolbars/src/components/ToolbarManager.tsx @@ -0,0 +1,37 @@ +import React, { FC } from 'react'; +import { useGlobalArgTypes } from '@storybook/api'; +import { Separator } from '@storybook/components'; + +import { ToolbarArgType } from '../types'; +import { MenuToolbar } from './MenuToolbar'; + +const normalize = (key: string, argType: ToolbarArgType) => ({ + ...argType, + name: argType.name || key, + description: argType.description || key, + toolbar: { + ...argType.toolbar, + items: argType.toolbar.items.map((item) => + typeof item === 'string' ? { value: item, title: item } : item + ), + }, +}); + +/** + * A smart component for handling manager-preview interactions. + */ +export const ToolbarManager: FC = () => { + const globalArgTypes = useGlobalArgTypes(); + const keys = Object.keys(globalArgTypes).filter((key) => !!globalArgTypes[key].toolbar); + if (!keys.length) return null; + + return ( + <> + + {keys.map((key) => { + const normalizedConfig = normalize(key, globalArgTypes[key] as ToolbarArgType); + return ; + })} + + ); +}; diff --git a/addons/toolbars/src/constants.ts b/addons/toolbars/src/constants.ts new file mode 100644 index 000000000000..1be8012f12e3 --- /dev/null +++ b/addons/toolbars/src/constants.ts @@ -0,0 +1,2 @@ +export const ID = 'addon-toolbars' as const; +export const PARAM = 'toolbars' as const; diff --git a/addons/toolbars/src/preset/index.ts b/addons/toolbars/src/preset/index.ts new file mode 100644 index 000000000000..0e20177db06c --- /dev/null +++ b/addons/toolbars/src/preset/index.ts @@ -0,0 +1,3 @@ +export function managerEntries(entry: any[] = []) { + return [...entry, require.resolve('../register')]; +} diff --git a/addons/toolbars/src/register.tsx b/addons/toolbars/src/register.tsx new file mode 100644 index 000000000000..552f00c81ed5 --- /dev/null +++ b/addons/toolbars/src/register.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import addons, { types } from '@storybook/addons'; +import { ToolbarManager } from './components/ToolbarManager'; +import { ID } from './constants'; + +addons.register(ID, (api) => + addons.add(ID, { + title: ID, + type: types.TOOL, + match: () => true, + render: () => , + }) +); diff --git a/addons/toolbars/src/types.ts b/addons/toolbars/src/types.ts new file mode 100644 index 000000000000..6e9b38ddf9d0 --- /dev/null +++ b/addons/toolbars/src/types.ts @@ -0,0 +1,27 @@ +import { IconsProps } from '@storybook/components'; +import { ArgType } from '@storybook/api'; + +export interface ToolbarItem { + value: string; + icon?: IconsProps['icon']; + left?: string; + right?: string; + title?: string; +} + +export interface NormalizedToolbarConfig { + icon?: IconsProps['icon']; + items: ToolbarItem[]; +} + +export type NormalizedToolbarArgType = ArgType & { + toolbar: NormalizedToolbarConfig; +}; + +export type ToolbarConfig = NormalizedToolbarConfig & { + items: string[] | ToolbarItem[]; +}; + +export type ToolbarArgType = ArgType & { + toolbar: ToolbarConfig; +}; diff --git a/addons/toolbars/tsconfig.json b/addons/toolbars/tsconfig.json new file mode 100644 index 000000000000..eac4a67bed71 --- /dev/null +++ b/addons/toolbars/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env", "jest"] + }, + "include": ["src/**/*"], + "exclude": ["src/**.test.ts"] +} diff --git a/addons/viewport/README.md b/addons/viewport/README.md index 853c64491b53..9cbc5d51d780 100644 --- a/addons/viewport/README.md +++ b/addons/viewport/README.md @@ -24,7 +24,7 @@ within `.storybook/main.js`: ```js module.exports = { - addons: ['@storybook/addon-viewport/register'], + addons: ['@storybook/addon-viewport'], }; ``` @@ -35,7 +35,7 @@ You should now be able to see the viewport addon icon in the the toolbar at the The viewport addon is configured by story parameters with the `viewport` key. To configure globally, import `addParameters` from your app layer in your `preview.js` file. ```js -import { addParameters } from '@storybook/react'; +import { addParameters } from '@storybook/client-api'; addParameters({ viewport: { @@ -53,6 +53,12 @@ Options can take a object with the following keys: Setting this property to, let say `iphone6`, will make `iPhone 6` the default device/viewport for all stories. Default is `'responsive'` which fills 100% of the preview area. +### disable : Boolean + +--- + +Disable viewport addon per component, story or global. + ### viewports : Object --- @@ -97,14 +103,17 @@ Parameters can be configured for a whole set of stories or a single story via th export default { title: 'Stories', parameters: { - viewport: { defaultViewport: 'iphone6' }, + viewport: { + viewports: INITIAL_VIEWPORTS, + defaultViewport: 'iphone6' + }, }; }; export const myStory = () =>

; -myStory.story = { - parameters: { - viewport: { defaultViewport: 'iphonex' }, +myStory.parameters = { + viewport: { + defaultViewport: 'iphonex' }, }; ``` @@ -116,7 +125,7 @@ myStory.story = { The default viewports being used is [`MINIMAL_VIEWPORTS`](src/defaults.ts). If you'd like to use a more granular list of devices, you can use [`INITIAL_VIEWPORTS`](src/defaults.ts) like so in your `.storybook/preview.js` file. ```js -import { addParameters } from '@storybook/react'; +import { addParameters } from '@storybook/client-api'; import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; addParameters({ @@ -131,7 +140,7 @@ addParameters({ This will replace all previous devices with `Kindle Fire 2` and `Kindle Fire HD` by calling `addParameters` with the two devices as `viewports` in `.storybook/preview.js` file. ```js -import { addParameters } from '@storybook/react'; +import { addParameters } from '@storybook/client-api'; const customViewports = { kindleFire2: { diff --git a/addons/viewport/package.json b/addons/viewport/package.json index c784002d1a0b..39178131f0ea 100644 --- a/addons/viewport/package.json +++ b/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook addon to change the viewport size to mobile", "keywords": [ "addon", @@ -16,39 +16,44 @@ "directory": "addons/viewport" }, "license": "MIT", + "main": "dist/preview.js", + "types": "dist/preview.d.ts", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/preview.js", - "types": "dist/preview.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "memoizerific": "^1.11.3", "prop-types": "^15.7.2", - "util-deprecate": "^1.0.2" - }, - "devDependencies": { - "@types/util-deprecate": "^1.0.0" + "regenerator-runtime": "^0.13.3" }, "peerDependencies": { - "react": "*" + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/addons/viewport/src/Tool.tsx b/addons/viewport/src/Tool.tsx index 98d38e5addd7..ee4bf351ec6f 100644 --- a/addons/viewport/src/Tool.tsx +++ b/addons/viewport/src/Tool.tsx @@ -35,7 +35,7 @@ const baseViewports: ViewportItem[] = [responsiveViewport]; const toLinks = memoize(50)((list: ViewportItem[], active: LinkBase, set, state, close): Link[] => { return list - .map(i => { + .map((i) => { switch (i.id) { case responsiveViewport.id: { if (active.id === i.id) { @@ -135,7 +135,7 @@ export const ViewportTool: FunctionComponent = memo( }); const list = toList(viewports); - if (!list.find(i => i.id === defaultViewport)) { + if (!list.find((i) => i.id === defaultViewport)) { console.warn( `Cannot find "defaultViewport" of "${defaultViewport}" in addon-viewport configs, please check the "viewports" setting in the configuration.` ); @@ -151,9 +151,9 @@ export const ViewportTool: FunctionComponent = memo( const { selected, isRotated } = state; const item = - list.find(i => i.id === selected) || - list.find(i => i.id === defaultViewport) || - list.find(i => i.default) || + list.find((i) => i.id === selected) || + list.find((i) => i.id === defaultViewport) || + list.find((i) => i.default) || responsiveViewport; const ref = useRef(); @@ -203,8 +203,7 @@ export const ViewportTool: FunctionComponent = memo( margin: `auto`, transition: 'width .3s, height .3s', position: 'relative', - border: `${theme.layoutMargin}px solid black`, - borderRadius: theme.appBorderRadius, + border: `1px solid black`, boxShadow: '0 0 100px 1000px rgba(0,0,0,0.5), 0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)', diff --git a/addons/viewport/src/defaults.ts b/addons/viewport/src/defaults.ts index 8a67ec92d3a4..6fd59c4e3eee 100644 --- a/addons/viewport/src/defaults.ts +++ b/addons/viewport/src/defaults.ts @@ -92,8 +92,8 @@ export const INITIAL_VIEWPORTS: ViewportMap = { galaxys9: { name: 'Galaxy S9', styles: { - height: '1480px', - width: '720px', + height: '740px', + width: '360px', }, type: 'mobile', }, diff --git a/addons/viewport/src/legacy_preview/index.ts b/addons/viewport/src/legacy_preview/index.ts deleted file mode 100644 index de7b43e50a02..000000000000 --- a/addons/viewport/src/legacy_preview/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import deprecate from 'util-deprecate'; - -export { INITIAL_VIEWPORTS, DEFAULT_VIEWPORT, MINIMAL_VIEWPORTS } from '../defaults'; - -export const configureViewport = deprecate(() => {}, -'configureViewport is no longer supported, use .addParameters({ viewport }) instead'); diff --git a/addons/viewport/src/models/Viewport.ts b/addons/viewport/src/models/Viewport.ts index c96a7ac003ec..76a5eb096777 100644 --- a/addons/viewport/src/models/Viewport.ts +++ b/addons/viewport/src/models/Viewport.ts @@ -4,11 +4,6 @@ export interface Viewport { name: string; styles: Styles; type: 'desktop' | 'mobile' | 'tablet' | 'other'; - /* - * @deprecated - * Deprecated option? - */ - default?: boolean; } export interface ViewportStyles { diff --git a/addons/viewport/src/models/ViewportAddonParameter.ts b/addons/viewport/src/models/ViewportAddonParameter.ts index e0f826f9586b..84e278d91f9c 100644 --- a/addons/viewport/src/models/ViewportAddonParameter.ts +++ b/addons/viewport/src/models/ViewportAddonParameter.ts @@ -4,9 +4,4 @@ export interface ViewportAddonParameter { disable?: boolean; defaultViewport?: string; viewports?: ViewportMap; - /* - * @deprecated - * The viewport parameter `onViewportChange` is no longer supported - */ - onViewportChange?: never; } diff --git a/addons/viewport/src/preview.ts b/addons/viewport/src/preview.ts index 9f198c8b135f..05f32144967d 100644 --- a/addons/viewport/src/preview.ts +++ b/addons/viewport/src/preview.ts @@ -1,6 +1 @@ -export { - configureViewport, - DEFAULT_VIEWPORT, - INITIAL_VIEWPORTS, - MINIMAL_VIEWPORTS, -} from './legacy_preview'; +export { INITIAL_VIEWPORTS, DEFAULT_VIEWPORT, MINIMAL_VIEWPORTS } from './defaults'; diff --git a/app/angular/package.json b/app/angular/package.json index f558168729d2..6fc3e81ce749 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/angular" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,26 +22,38 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@storybook/node-logger": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@storybook/node-logger": "6.0.0-beta.21", + "@types/webpack-env": "^1.15.2", + "autoprefixer": "^9.7.6", "core-js": "^3.0.1", - "fork-ts-checker-webpack-plugin": "^3.0.1", + "fork-ts-checker-webpack-plugin": "^4.0.3", "global": "^4.3.2", + "postcss-loader": "^3.0.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", "regenerator-runtime": "^0.13.3", "sass-loader": "^8.0.0", "strip-json-comments": "^3.0.1", "ts-loader": "^6.0.1", - "tsconfig-paths-webpack-plugin": "^3.2.0" + "tsconfig-paths-webpack-plugin": "^3.2.0", + "webpack": "^4.43.0" }, "devDependencies": { - "@types/autoprefixer": "^9.4.0", - "@types/webpack-env": "^1.14.0", - "webpack": "^4.33.0" + "@types/autoprefixer": "^9.4.0" }, "peerDependencies": { "@angular-devkit/build-angular": ">=0.8.9", @@ -59,11 +64,9 @@ "@angular/forms": ">=6.0.0", "@angular/platform-browser": ">=6.0.0", "@angular/platform-browser-dynamic": ">=6.0.0", - "autoprefixer": "^8.1.0", - "babel-loader": "^7.0.0 || ^8.0.0", + "@babel/core": "*", "rxjs": "^6.0.0", "typescript": "^3.4.0", - "webpack": "^4.32.0", "zone.js": "^0.8.29 || ^0.9.0 || ^0.10.0" }, "engines": { @@ -72,5 +75,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/angular/src/client/index.ts b/app/angular/src/client/index.ts index b235d2b3b634..823d2918762b 100644 --- a/app/angular/src/client/index.ts +++ b/app/angular/src/client/index.ts @@ -9,10 +9,10 @@ export { raw, } from './preview'; +export { StoryFnAngularReturnType as IStory } from './preview/types'; + export { moduleMetadata } from './preview/angular/decorators'; -// tsc wants to use NodeModule instead of WebpackModule -declare const module: any; if (module && module.hot && module.hot.decline) { module.hot.decline(); } diff --git a/app/angular/src/client/preview/angular/components/app.component.ts b/app/angular/src/client/preview/angular/components/app.component.ts index 7856bf8f8947..f0a094788c9d 100644 --- a/app/angular/src/client/preview/angular/components/app.component.ts +++ b/app/angular/src/client/preview/angular/components/app.component.ts @@ -50,7 +50,7 @@ export class AppComponent implements OnInit, OnDestroy { ChangeDetectorRef ); - this.subscription = this.data.subscribe(newData => { + this.subscription = this.data.subscribe((newData) => { this.setProps(instance, newData); childChangeDetectorRef.markForCheck(); // Must detect changes on the current component in order to update any changes in child component's @HostBinding properties (angular/angular#22560) diff --git a/app/angular/src/client/preview/angular/helpers.ts b/app/angular/src/client/preview/angular/helpers.ts index b5a2170a244b..b747cb3db557 100644 --- a/app/angular/src/client/preview/angular/helpers.ts +++ b/app/angular/src/client/preview/angular/helpers.ts @@ -69,7 +69,7 @@ const extractNgModuleMetadata = (importItem: any): NgModule => { } const ngModuleDecorator: NgModule | undefined = decorators.find( - decorator => decorator instanceof NgModule + (decorator) => decorator instanceof NgModule ); if (!ngModuleDecorator) { return null; @@ -82,7 +82,7 @@ const getExistenceOfComponentInModules = ( declarations: any[], imports: any[] ): boolean => { - if (declarations && declarations.some(declaration => declaration === component)) { + if (declarations && declarations.some((declaration) => declaration === component)) { // Found component in declarations array return true; } @@ -90,7 +90,7 @@ const getExistenceOfComponentInModules = ( return false; } - return imports.some(importItem => { + return imports.some((importItem) => { const extractedNgModuleMetadata = extractNgModuleMetadata(importItem); if (!extractedNgModuleMetadata) { // Not an NgModule @@ -161,8 +161,8 @@ const draw = (newModule: DynamicComponentType): void => { platform = platformBrowserDynamic(); promises.push(platform.bootstrapModule(newModule)); } else { - Promise.all(promises).then(modules => { - modules.forEach(mod => mod.destroy()); + Promise.all(promises).then((modules) => { + modules.forEach((mod) => mod.destroy()); insertDynamicRoot(); promises = []; diff --git a/app/angular/src/client/preview/types.ts b/app/angular/src/client/preview/types.ts index 2b0349a4a79c..47ab69bdad10 100644 --- a/app/angular/src/client/preview/types.ts +++ b/app/angular/src/client/preview/types.ts @@ -20,10 +20,6 @@ export interface IStorybookStory { render: () => any; } -// @deprecated Use IStorybookSection instead -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface IStoribookSection extends IStorybookSection {} - export interface IStorybookSection { kind: string; stories: IStorybookStory[]; diff --git a/app/angular/src/demo/button.component.ts b/app/angular/src/demo/button.component.ts index 7d3f76ab9dc7..da804e507b7d 100644 --- a/app/angular/src/demo/button.component.ts +++ b/app/angular/src/demo/button.component.ts @@ -2,9 +2,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'storybook-button-component', - template: ` - - `, + template: ` `, styles: [ ` button { diff --git a/app/angular/src/server/__tests__/angular-cli_config.test.ts b/app/angular/src/server/__tests__/angular-cli_config.test.ts index fb7ec1935646..25c22a7e3ea9 100644 --- a/app/angular/src/server/__tests__/angular-cli_config.test.ts +++ b/app/angular/src/server/__tests__/angular-cli_config.test.ts @@ -70,4 +70,13 @@ describe('angular-cli_config', () => { expect(projectConfig).toBe(null); expect(config).toBe(baseConfig); }); + + it('should return empty `buildOptions.budgets` by default', () => { + const config = getAngularCliWebpackConfigOptions(__dirname as Path); + expect(config).toMatchObject({ + buildOptions: { + budgets: [], + }, + }); + }); }); diff --git a/app/angular/src/server/__tests__/angular.json b/app/angular/src/server/__tests__/angular.json index b04b1d60b447..d703e396ff22 100644 --- a/app/angular/src/server/__tests__/angular.json +++ b/app/angular/src/server/__tests__/angular.json @@ -75,13 +75,6 @@ "scripts": [], "assets": ["src/favicon.ico", "src/assets"] } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], - "exclude": ["**/node_modules/**"] - } } } }, @@ -95,13 +88,6 @@ "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "angular-cli:serve" } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": "e2e/tsconfig.e2e.json", - "exclude": ["**/node_modules/**"] - } } } } diff --git a/app/angular/src/server/__tests__/ts_config.test.ts b/app/angular/src/server/__tests__/ts_config.test.ts index 046001787ad8..1b84a549f06d 100644 --- a/app/angular/src/server/__tests__/ts_config.test.ts +++ b/app/angular/src/server/__tests__/ts_config.test.ts @@ -20,6 +20,9 @@ describe('ts_config', () => { expect(config).toEqual({ transpileOnly: true, + compilerOptions: { + emitDecoratorMetadata: true, + }, configFile: 'tsconfig.json', }); }); @@ -31,6 +34,9 @@ describe('ts_config', () => { expect(config).toEqual({ transpileOnly: true, + compilerOptions: { + emitDecoratorMetadata: true, + }, }); }); }); diff --git a/app/angular/src/server/angular-cli_config.ts b/app/angular/src/server/angular-cli_config.ts index fd98d622cb84..89f649667b11 100644 --- a/app/angular/src/server/angular-cli_config.ts +++ b/app/angular/src/server/angular-cli_config.ts @@ -47,13 +47,17 @@ function getTsConfigOptions(tsConfigPath: Path) { } export function getAngularCliConfig(dirToSearch: string) { - const fname = path.join(dirToSearch, 'angular.json'); + const possibleConfigNames = ['angular.json', 'workspace.json']; + const possibleConfigPaths = possibleConfigNames.map((name) => path.join(dirToSearch, name)); - if (!fs.existsSync(fname)) { + const validIndex = possibleConfigPaths.findIndex((configPath) => fs.existsSync(configPath)); + + if (validIndex === -1) { + logger.error(`Could not find angular.json using ${possibleConfigPaths[0]}`); return undefined; } - return JSON.parse(stripJsonComments(fs.readFileSync(fname, 'utf8'))); + return JSON.parse(stripJsonComments(fs.readFileSync(possibleConfigPaths[validIndex], 'utf8'))); } export function getLeadingAngularCliProject(ngCliConfig: any) { @@ -67,9 +71,26 @@ export function getLeadingAngularCliProject(ngCliConfig: any) { throw new Error('angular.json must have projects entry.'); } - const fallbackProject = defaultProject && projects[defaultProject]; - const firstProject = projects[Object.keys(projects)[0]]; - return projects.storybook || fallbackProject || firstProject; + let projectName; + const firstProjectName = Object.keys(projects)[0]; + if (projects.storybook) { + projectName = 'storybook'; + } else if (defaultProject && projects[defaultProject]) { + projectName = defaultProject; + } else if (projects[firstProjectName]) { + projectName = firstProjectName; + } + + const project = projects[projectName]; + if (!project) { + logger.error(`Could not find angular project '${projectName}' in angular.json.`); + } else { + logger.info(`=> Using angular project '${projectName}' for configuring Storybook.`); + } + if (project && !project.architect.build) { + logger.error(`architect.build is not defined for project '${projectName}'.`); + } + return project; } export function getAngularCliWebpackConfigOptions(dirToSearch: Path) { @@ -89,6 +110,10 @@ export function getAngularCliWebpackConfigOptions(dirToSearch: Path) { const projectRoot = path.resolve(dirToSearch, project.root); const tsConfigPath = path.resolve(dirToSearch, projectOptions.tsConfig) as Path; const tsConfig = getTsConfigOptions(tsConfigPath); + const budgets = projectOptions.budgets || []; + const scripts = projectOptions.scripts || []; + const outputPath = projectOptions.outputPath || 'dist/storybook-angular'; + const styles = projectOptions.styles || []; return { root: dirToSearch, @@ -101,6 +126,10 @@ export function getAngularCliWebpackConfigOptions(dirToSearch: Path) { optimization: {}, ...projectOptions, assets: normalizedAssets, + budgets, + scripts, + styles, + outputPath, }, }; } diff --git a/app/angular/src/server/angular-cli_utils.ts b/app/angular/src/server/angular-cli_utils.ts index 72469eda51ad..75d2d6763d3f 100644 --- a/app/angular/src/server/angular-cli_utils.ts +++ b/app/angular/src/server/angular-cli_utils.ts @@ -45,7 +45,7 @@ function isStylingRule(rule: RuleSetRule) { } export function filterOutStylingRules(config: Configuration) { - return config.module.rules.filter(rule => !isStylingRule(rule)); + return config.module.rules.filter((rule) => !isStylingRule(rule)); } export function isBuildAngularInstalled() { diff --git a/app/angular/src/server/create-fork-ts-checker-plugin.ts b/app/angular/src/server/create-fork-ts-checker-plugin.ts index f8881492f3d9..594fe5a25384 100644 --- a/app/angular/src/server/create-fork-ts-checker-plugin.ts +++ b/app/angular/src/server/create-fork-ts-checker-plugin.ts @@ -3,7 +3,7 @@ import { logger } from '@storybook/node-logger'; import { Options } from 'ts-loader'; -export default function(tsLoaderOptions: Partial) { +export default function (tsLoaderOptions: Partial) { if (tsLoaderOptions && tsLoaderOptions.configFile) { return new ForkTsCheckerWebpackPlugin({ tsconfig: tsLoaderOptions.configFile, diff --git a/app/angular/src/server/framework-preset-angular.ts b/app/angular/src/server/framework-preset-angular.ts index 8521d9c07b88..5b0239b58a72 100644 --- a/app/angular/src/server/framework-preset-angular.ts +++ b/app/angular/src/server/framework-preset-angular.ts @@ -51,7 +51,6 @@ export function webpack( }, resolve: { ...config.resolve, - extensions: ['.ts', '.tsx', ...config.resolve.extensions], }, plugins: [ ...config.plugins, diff --git a/app/angular/src/server/ngx-template-loader/index.ts b/app/angular/src/server/ngx-template-loader/index.ts index 6e2d2381ee14..10ff4359f567 100644 --- a/app/angular/src/server/ngx-template-loader/index.ts +++ b/app/angular/src/server/ngx-template-loader/index.ts @@ -26,7 +26,7 @@ const replaceStringsWithRequires = (string: string) => { }); }; -export default function(source: string) { +export default function (source: string) { const styleProperty = 'styles'; const templateProperty = 'template'; diff --git a/app/angular/src/server/options.ts b/app/angular/src/server/options.ts index 4d703cf796d2..6fe610be208f 100644 --- a/app/angular/src/server/options.ts +++ b/app/angular/src/server/options.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line: no-var-requires const packageJson = require('../../package.json'); export default { diff --git a/app/angular/src/server/ts_config.ts b/app/angular/src/server/ts_config.ts index 2613f61db337..bb0581335f0b 100644 --- a/app/angular/src/server/ts_config.ts +++ b/app/angular/src/server/ts_config.ts @@ -10,10 +10,13 @@ function resolveTsConfig(tsConfigPath: string): string | undefined { return undefined; } -export default function(configDir: string) { +export default function (configDir: string) { const configFilePath = resolveTsConfig(path.resolve(configDir, 'tsconfig.json')); return { transpileOnly: true, + compilerOptions: { + emitDecoratorMetadata: true, + }, configFile: configFilePath || undefined, }; } diff --git a/app/angular/tsconfig.json b/app/angular/tsconfig.json index 6c12e1098876..8a9ea247510b 100644 --- a/app/angular/tsconfig.json +++ b/app/angular/tsconfig.json @@ -3,7 +3,7 @@ "compileOnSave": false, "compilerOptions": { "outDir": "dist", - "types": [], + "types": ["webpack-env"], "rootDir": "./src", "resolveJsonModule": true } diff --git a/app/aurelia/README.md b/app/aurelia/README.md new file mode 100644 index 000000000000..fb3507ebb761 --- /dev/null +++ b/app/aurelia/README.md @@ -0,0 +1,23 @@ +# Storybook for Aurelia + +Storybook for Aurelia is a UI development environment for your Aurelia components. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-aurelia-app +npx -p @storybook/cli sb init +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/polymer/bin/build.js b/app/aurelia/bin/build.js similarity index 100% rename from app/polymer/bin/build.js rename to app/aurelia/bin/build.js diff --git a/app/polymer/bin/index.js b/app/aurelia/bin/index.js similarity index 100% rename from app/polymer/bin/index.js rename to app/aurelia/bin/index.js diff --git a/app/aurelia/demo.d.ts b/app/aurelia/demo.d.ts new file mode 100644 index 000000000000..0183b7ef451b --- /dev/null +++ b/app/aurelia/demo.d.ts @@ -0,0 +1,4 @@ +declare module '@storybook/aurelia/demo' { + export const Button: any; + export const Welcome: any; +} diff --git a/app/aurelia/demo.js b/app/aurelia/demo.js new file mode 100644 index 000000000000..64bcd548d7e8 --- /dev/null +++ b/app/aurelia/demo.js @@ -0,0 +1,5 @@ +/* eslint-disable global-require */ +module.exports = { + Welcome: require('./dist/demo/welcome').default, + Button: require('./dist/demo/button').default, +}; diff --git a/app/aurelia/package.json b/app/aurelia/package.json new file mode 100644 index 000000000000..2e3559997744 --- /dev/null +++ b/app/aurelia/package.json @@ -0,0 +1,58 @@ +{ + "name": "@storybook/aurelia", + "version": "6.0.0-beta.21", + "description": "Storybook for Aurelia: Develop Aurelia Components in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/app/aurelia", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/aurelia" + }, + "license": "MIT", + "main": "dist/client/index.js", + "types": "dist/client/index.d.ts", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addon-knobs": "6.0.0-beta.21", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@storybook/node-logger": "6.0.0-beta.21", + "fork-ts-checker-webpack-plugin": "^4.0.3", + "global": "^4.3.2", + "ts-loader": "^6.0.1", + "webpack": "^4.33.0" + }, + "devDependencies": { + "@types/node": "^14.0.10", + "@types/webpack-env": "^1.15.1", + "css-loader": "^3.0.0", + "file-loader": "^4.2.0", + "html-webpack-plugin": "^3.0.0", + "htmlhint": "^0.11.0", + "node-sass": "^4.12.0", + "rimraf": "^3.0.2", + "sass-loader": "^8.0.0", + "style-loader": "^0.23.0", + "typescript": "latest", + "webpack": "^4.33.0" + }, + "peerDependencies": { + "aurelia": "dev" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/app/aurelia/src/client/index.ts b/app/aurelia/src/client/index.ts new file mode 100644 index 000000000000..2f61014c86b1 --- /dev/null +++ b/app/aurelia/src/client/index.ts @@ -0,0 +1,6 @@ +export * from './preview'; + +declare const module: any; +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/app/aurelia/src/client/preview/decorators.ts b/app/aurelia/src/client/preview/decorators.ts new file mode 100644 index 000000000000..25b97e3cd639 --- /dev/null +++ b/app/aurelia/src/client/preview/decorators.ts @@ -0,0 +1,45 @@ +import { StoryFn } from '@storybook/addons'; +import { IRegistry, IContainer } from 'aurelia'; +import { StoryFnAureliaReturnType } from './types'; + +export const addRegistries = (...items: IRegistry[]) => ( + storyFn: StoryFn +) => { + const story = storyFn(); + story.items = story.items || []; + story.items.push(...items); + + return { + ...story, + items, + }; +}; + +export interface Component { + item?: unknown; + aliases?: string[]; +} + +export const addComponents = (...components: Component[] | unknown[]) => ( + storyFn: StoryFn +) => { + const story = storyFn(); + story.components = story.components || []; + story.components.push(...components); + + return { + ...story, + components, + }; +}; + +export const addContainer = (container: IContainer) => ( + storyFn: StoryFn +) => { + const story = storyFn(); + + return { + ...story, + container, + }; +}; diff --git a/app/aurelia/src/client/preview/globals.ts b/app/aurelia/src/client/preview/globals.ts new file mode 100644 index 000000000000..7b7c2bf07135 --- /dev/null +++ b/app/aurelia/src/client/preview/globals.ts @@ -0,0 +1,3 @@ +import { window } from 'global'; + +window.STORYBOOK_ENV = 'aurelia'; diff --git a/app/aurelia/src/client/preview/index.ts b/app/aurelia/src/client/preview/index.ts new file mode 100644 index 000000000000..909bac8c3ac3 --- /dev/null +++ b/app/aurelia/src/client/preview/index.ts @@ -0,0 +1,104 @@ +import { Constructable, CustomElement } from 'aurelia'; +/* eslint-disable prefer-destructuring */ +import { start } from '@storybook/core/client'; +import { ClientStoryApi, Loadable } from '@storybook/addons'; +import { text, boolean, number, date } from '@storybook/addon-knobs'; + +import './globals'; +import render from './render'; +import { IStorybookSection, StoryFnAureliaReturnType } from './types'; +import { addRegistries, addContainer, Component, addComponents } from './decorators'; + +const framework = 'Aurelia'; + +interface ClientApi extends ClientStoryApi> { + setAddon(addon: any): void; + configure(loader: Loadable, module: NodeModule): void; + getStorybook(): IStorybookSection[]; + clearDecorators(): void; + forceReRender(): void; + raw: () => any; // todo add type + load: (...args: any[]) => void; +} + +const api = start(render); + +export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { + return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ + framework, + }); +}; + +export { StoryFnAureliaReturnType, addRegistries, addContainer, Component, addComponents }; + +export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; +export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; +export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; +export const setAddon: ClientApi['setAddon'] = api.clientApi.setAddon; +export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; +export const getStorybook: ClientApi['getStorybook'] = api.clientApi.getStorybook; +export const raw: ClientApi['raw'] = api.clientApi.raw; + +export function generateKnobsFor(CustomElementClass: Constructable) { + const def = CustomElement.getDefinition(CustomElementClass); + const bindables = def && def.bindables; + + if (!bindables) return class {}; + const result = class {} as any; + const elementConstructed = new CustomElementClass() as any; + + Object.keys(bindables) + .map((y) => bindables[y]) + .forEach((bindableDef) => { + const bindable = bindableDef.property; + const currentVal = elementConstructed[bindable]; + switch (typeof currentVal) { + case 'boolean': + result[bindable] = boolean(bindable, elementConstructed[bindable]); + return; + case 'string': + result[bindable] = text(bindable, elementConstructed[bindable] || 'lorem ipsum'); + return; + case 'number': + case 'bigint': + result[bindable] = number(bindable, elementConstructed[bindable] || 0); + return; + case 'undefined': + if (bindable.toLocaleLowerCase().includes('is')) { + result[bindable] = boolean(bindable, elementConstructed[bindable]); + return; + } + if ( + bindable.toLocaleLowerCase().includes('count') || + bindable.toLocaleLowerCase().includes('max') || + bindable.toLocaleLowerCase().includes('min') + ) { + result[bindable] = number(bindable, elementConstructed[bindable] || 0); + return; + } + if ( + bindable.toLocaleLowerCase().includes('date') || + bindable.toLocaleLowerCase().includes('time') + ) { + result[bindable] = date(bindable, elementConstructed[bindable] || new Date()); + return; + } + result[bindable] = text(bindable, elementConstructed[bindable] || 'lorem ipsum'); + return; + case 'object': + if (currentVal instanceof Date) { + result[bindable] = date(bindable, elementConstructed[bindable] || new Date()); + return; + } + if (currentVal instanceof Date) { + result[bindable] = date(bindable, elementConstructed[bindable] || new Date()); + return; + } + return; + default: + result[bindable] = text(bindable, elementConstructed[bindable] || 'lorem ipsum'); + } + }); + return result; +} diff --git a/app/aurelia/src/client/preview/render.ts b/app/aurelia/src/client/preview/render.ts new file mode 100644 index 000000000000..64a06c1ac0c2 --- /dev/null +++ b/app/aurelia/src/client/preview/render.ts @@ -0,0 +1,82 @@ +import { document } from 'global'; +import { + Aurelia, + INode, + JitHtmlBrowserConfiguration, + DebugConfiguration, + CustomElement, + Constructable, + IViewModel, +} from 'aurelia'; +import { RenderMainArgs } from './types'; +import { generateKnobsFor } from '.'; + +const host = document.getElementById('root'); // the root iframe provided by storybook +let previousAurelia: Aurelia; +export default async function render({ + storyFn, + selectedKind, + selectedStory, + showMain, + showError, +}: RenderMainArgs) { + const element = storyFn(); + + if (!element) { + showError({ + title: `Expecting an Aurelia component from the story: "${selectedStory}" of "${selectedKind}".`, + description: ` + Did you forget to return the Aurelia component from the story? + Use "() => ({ template: '' })" when defining the story. + `, + }); + } + showMain(); + + if (previousAurelia) { + await previousAurelia.stop().wait(); + } + + previousAurelia = new Aurelia(element.container); + if (element.items && element.items.length > 0) { + previousAurelia.register(...element.items); + } else { + previousAurelia.register(JitHtmlBrowserConfiguration, DebugConfiguration); + } + + if (element.components && element.components.length > 0) { + previousAurelia.container.register(...element.components); + } + + const isConstructable = element.state && element.state.prototype; + let { template } = element; + if (element.customElement) { + const def = CustomElement.getDefinition(element.customElement); + template = `<${def.name} ${Object.keys(def.bindables) + .map((key) => `${def.bindables[key].attribute}.bind="${def.bindables[key].property}" `) + .join(' ')} >`; + previousAurelia.register(element.customElement); + } + + let State: Constructable = class {}; + if (element.state) { + State = isConstructable ? element.state : State; + } else if (element.customElement) { + State = generateKnobsFor(element.customElement); + } + + const App = CustomElement.define({ name: 'app', template }, State as Constructable); + + let app: IViewModel; + if ((element.customElement || element.state) && !isConstructable) { + app = Object.assign(new App(), element.state || State); + } + + await previousAurelia + .app({ + host, + component: app || App, + }) + .start() + .wait(); +} diff --git a/app/aurelia/src/client/preview/types.ts b/app/aurelia/src/client/preview/types.ts new file mode 100644 index 000000000000..9f399b5a9f61 --- /dev/null +++ b/app/aurelia/src/client/preview/types.ts @@ -0,0 +1,35 @@ +import { StoryFn } from '@storybook/addons'; +import { IRegistry, IContainer, Constructable } from 'aurelia'; +import { Component } from './decorators'; + +export interface RenderMainArgs { + storyFn: StoryFn>; + selectedKind: string; + selectedStory: string; + showMain: () => void; + showError: (args: ShowErrorArgs) => void; + showException: (...args: any[]) => void; + forceRender: boolean; +} +export interface StoryFnAureliaReturnType { + customElement: Constructable; + components: Component[] | unknown[]; + template: unknown; + items: IRegistry[]; + container: IContainer; + state: any; +} +export interface ShowErrorArgs { + title: string; + description: string; +} + +export interface IStorybookStory { + name: string; + render: () => any; +} + +export interface IStorybookSection { + kind: string; + stories: IStorybookStory[]; +} diff --git a/app/aurelia/src/demo/button.ts b/app/aurelia/src/demo/button.ts new file mode 100644 index 000000000000..86cac656b57c --- /dev/null +++ b/app/aurelia/src/demo/button.ts @@ -0,0 +1,28 @@ +import { customElement, bindable } from 'aurelia'; + +@customElement({ + name: 'storybook-button-component', + template: ` + + `, +}) +export default class Button { + @bindable() + text = ''; + + @bindable() + onClick: MouseEvent; +} diff --git a/app/aurelia/src/demo/index.ts b/app/aurelia/src/demo/index.ts new file mode 100644 index 000000000000..9463e32d9b59 --- /dev/null +++ b/app/aurelia/src/demo/index.ts @@ -0,0 +1,2 @@ +export { default as Button } from './button'; +export { default as Welcome } from './welcome'; diff --git a/app/aurelia/src/demo/welcome.ts b/app/aurelia/src/demo/welcome.ts new file mode 100644 index 000000000000..109911b7de2e --- /dev/null +++ b/app/aurelia/src/demo/welcome.ts @@ -0,0 +1,79 @@ +import { bindable, customElement } from 'aurelia'; + +@customElement({ + name: 'storybook-welcome-component', + template: ` +
+

Welcome to storybook

+

This is a UI component dev environment for your app.

+

+ We've added some basic stories inside the + src/stories directory.
+ A story is a single state of one or more UI components. You can have as many stories as you + want.
+ (Basically a story is like a visual test case.) +

+

+ See these sample + stories for a component + called Button . +

+

+ Just like that, you can add your own components as stories.
+ You can also edit those components and see changes right away.
+ (Try editing the Button stories located at + src/stories/index.js.) +

+

+ Usually we create stories with smaller UI components in the app.
+ Have a look at the + + Writing Stories + + section in our documentation. +

+

+ NOTE:
+ Have a look at the .storybook/webpack.config.js to add + webpack loaders and plugins you are using in this project. +

+
+ + `, +}) +export default class Welcome { + @bindable() + showApp: MouseEvent; +} diff --git a/app/aurelia/src/server/__tests__/create-fork-ts-checker-plugin.test.ts b/app/aurelia/src/server/__tests__/create-fork-ts-checker-plugin.test.ts new file mode 100644 index 000000000000..02d82e734f25 --- /dev/null +++ b/app/aurelia/src/server/__tests__/create-fork-ts-checker-plugin.test.ts @@ -0,0 +1,35 @@ +import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; +import getTsLoaderOptions from '../ts_config'; +import createForkTsCheckerInstance from '../create-fork-ts-checker-plugin'; + +// eslint-disable-next-line global-require, jest/no-mocks-import +jest.mock('fs', () => require('../../../../../__mocks__/fs')); +jest.mock('path', () => ({ + resolve: () => 'tsconfig.json', +})); +jest.mock('@storybook/node-logger'); + +const setupFiles = (files: any) => { + // eslint-disable-next-line no-underscore-dangle, global-require + require('fs').__setMockFiles(files); +}; + +describe('create-fork-ts-checker-plugin.test', () => { + it('should create a ForkTsCheckerWebpackPlugin instance', () => { + setupFiles({ 'tsconfig.json': '{}' }); + + const tsLoaderOptions = getTsLoaderOptions('.foo'); + + // todo resolve any + const instance: any = createForkTsCheckerInstance(tsLoaderOptions); + + expect(instance).toBeInstanceOf(ForkTsCheckerWebpackPlugin); + expect(instance.tsconfig).toEqual(tsLoaderOptions.configFile); + }); + + it('should create a ForkTsCheckerWebpackPlugin instance without passing options', () => { + // add proper typing + const instance = createForkTsCheckerInstance({} as any); + expect(instance).toBeInstanceOf(ForkTsCheckerWebpackPlugin); + }); +}); diff --git a/app/aurelia/src/server/__tests__/ts_config.test.ts b/app/aurelia/src/server/__tests__/ts_config.test.ts new file mode 100644 index 000000000000..046001787ad8 --- /dev/null +++ b/app/aurelia/src/server/__tests__/ts_config.test.ts @@ -0,0 +1,36 @@ +import getTsLoaderOptions from '../ts_config'; + +// eslint-disable-next-line global-require, jest/no-mocks-import +jest.mock('fs', () => require('../../../../../__mocks__/fs')); +jest.mock('path', () => ({ + resolve: () => 'tsconfig.json', +})); +jest.mock('@storybook/node-logger'); + +const setupFiles = (files: any) => { + // eslint-disable-next-line no-underscore-dangle, global-require + require('fs').__setMockFiles(files); +}; + +describe('ts_config', () => { + it('should return the config with the path to the tsconfig.json', () => { + setupFiles({ 'tsconfig.json': '{}' }); + + const config = getTsLoaderOptions('.foo'); + + expect(config).toEqual({ + transpileOnly: true, + configFile: 'tsconfig.json', + }); + }); + + it('should return object with transpileOnly: true when there is no tsconfig.json', () => { + setupFiles({}); + + const config = getTsLoaderOptions('.foo'); + + expect(config).toEqual({ + transpileOnly: true, + }); + }); +}); diff --git a/app/ember/src/server/build.js b/app/aurelia/src/server/build.ts similarity index 100% rename from app/ember/src/server/build.js rename to app/aurelia/src/server/build.ts diff --git a/app/aurelia/src/server/create-fork-ts-checker-plugin.ts b/app/aurelia/src/server/create-fork-ts-checker-plugin.ts new file mode 100644 index 000000000000..594fe5a25384 --- /dev/null +++ b/app/aurelia/src/server/create-fork-ts-checker-plugin.ts @@ -0,0 +1,16 @@ +import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; +import { logger } from '@storybook/node-logger'; + +import { Options } from 'ts-loader'; + +export default function (tsLoaderOptions: Partial) { + if (tsLoaderOptions && tsLoaderOptions.configFile) { + return new ForkTsCheckerWebpackPlugin({ + tsconfig: tsLoaderOptions.configFile, + async: false, + }); + } + + logger.info('=> Using default options for ForkTsCheckerWebpackPlugin'); + return new ForkTsCheckerWebpackPlugin(); +} diff --git a/app/aurelia/src/server/framework-preset-aurelia.ts b/app/aurelia/src/server/framework-preset-aurelia.ts new file mode 100644 index 000000000000..13b8f813948b --- /dev/null +++ b/app/aurelia/src/server/framework-preset-aurelia.ts @@ -0,0 +1,50 @@ +import { Configuration } from 'webpack'; +import createForkTsCheckerInstance from './create-fork-ts-checker-plugin'; +import getTsLoaderOptions from './ts_config'; + +export function webpack( + config: Configuration, + { configDir }: { configDir: string } +): Configuration { + const tsLoaderOptions = getTsLoaderOptions(configDir); + return { + ...config, + resolve: { + ...config.resolve, + extensions: [...config.resolve.extensions, '.ts', '.js'], + modules: [...config.resolve.modules, 'src', 'node_modules'], + }, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, + { + test: /\.css$/i, + issuer: [{ not: [{ test: /\.html$/i }] }], + use: ['style-loader', 'css-loader'], + }, + { + test: /\.css$/i, + issuer: [{ test: /\.html$/i }], + // CSS required in templates cannot be extracted safely + // because Aurelia would try to require it again in runtime + use: 'css-loader', + }, + { + test: /\.scss$/, + use: ['style-loader', 'css-loader', 'sass-loader'], + issuer: /\.[tj]s$/i, + }, + { + test: /\.scss$/, + use: ['css-loader', 'sass-loader'], + issuer: /\.html?$/i, + }, + { test: /\.ts$/i, use: ['ts-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ }, + { test: /\.html$/i, use: '@aurelia/webpack-loader', exclude: /node_modules/ }, + ], + }, + plugins: [...config.plugins, createForkTsCheckerInstance(tsLoaderOptions)], + }; +} diff --git a/app/ember/src/server/index.js b/app/aurelia/src/server/index.ts similarity index 100% rename from app/ember/src/server/index.js rename to app/aurelia/src/server/index.ts diff --git a/app/aurelia/src/server/options.ts b/app/aurelia/src/server/options.ts new file mode 100644 index 000000000000..cfb411ee844d --- /dev/null +++ b/app/aurelia/src/server/options.ts @@ -0,0 +1,7 @@ +const packageJson = require('../../package.json'); + +export default { + packageJson, + framework: 'aurelia', + frameworkPresets: [require.resolve('./framework-preset-aurelia.js')], +}; diff --git a/app/aurelia/src/server/ts_config.ts b/app/aurelia/src/server/ts_config.ts new file mode 100644 index 000000000000..e26ab3f056d6 --- /dev/null +++ b/app/aurelia/src/server/ts_config.ts @@ -0,0 +1,19 @@ +import fs from 'fs'; +import path from 'path'; +import { logger } from '@storybook/node-logger'; + +function resolveTsConfig(tsConfigPath: string): string | undefined { + if (fs.existsSync(tsConfigPath)) { + logger.info('=> Found custom tsconfig.json'); + return tsConfigPath; + } + return undefined; +} + +export default function (configDir: string) { + const configFilePath = resolveTsConfig(path.resolve(configDir, 'tsconfig.json')); + return { + transpileOnly: true, + configFile: configFilePath || undefined, + }; +} diff --git a/app/polymer/standalone.js b/app/aurelia/standalone.js similarity index 100% rename from app/polymer/standalone.js rename to app/aurelia/standalone.js diff --git a/app/aurelia/tsconfig.json b/app/aurelia/tsconfig.json new file mode 100644 index 000000000000..00d522bf81d1 --- /dev/null +++ b/app/aurelia/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "outDir": "dist", + "types": [ + "webpack-env" + ], + "rootDir": "./src", + "resolveJsonModule": true + } +} \ No newline at end of file diff --git a/app/aurelia/typings.d.ts b/app/aurelia/typings.d.ts new file mode 100644 index 000000000000..690e93343de2 --- /dev/null +++ b/app/aurelia/typings.d.ts @@ -0,0 +1,5 @@ +declare module '@storybook/core/*'; +declare module 'global'; + +// will be provided by the webpack define plugin +declare var NODE_ENV: string | undefined; diff --git a/app/ember/package.json b/app/ember/package.json index ec0ae48cc166..f46c339b92e8 100644 --- a/app/ember/package.json +++ b/app/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/master/app/ember", "bugs": { @@ -12,13 +12,6 @@ "directory": "app/ember" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "jsnext:main": "src/client/index.js", "bin": { @@ -26,22 +19,32 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@ember/test-helpers": "^1.5.0", - "@storybook/core": "5.3.0-rc.0", + "@ember/test-helpers": "^1.7.0", + "@storybook/core": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", - "babel-plugin-ember-modules-api-polyfill": "^2.4.0", - "ember-cli-htmlbars-inline-precompile": "^1.0.3", - "ember-source": "^3.4.0" + "@babel/core": "*", + "babel-plugin-ember-modules-api-polyfill": "^2.12.0", + "babel-plugin-htmlbars-inline-precompile": "2 || 3", + "ember-source": "^3.16.0", + "react": "*", + "react-dom": "*" }, "engines": { "node": ">=8.0.0" @@ -49,5 +52,5 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/ember/src/client/index.js b/app/ember/src/client/index.ts similarity index 100% rename from app/ember/src/client/index.js rename to app/ember/src/client/index.ts diff --git a/app/ember/src/client/preview/globals.js b/app/ember/src/client/preview/globals.ts similarity index 100% rename from app/ember/src/client/preview/globals.js rename to app/ember/src/client/preview/globals.ts diff --git a/app/ember/src/client/preview/index.js b/app/ember/src/client/preview/index.js deleted file mode 100644 index df20b1c1352a..000000000000 --- a/app/ember/src/client/preview/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { start } from '@storybook/core/client'; - -import './globals'; -import render from './render'; - -const { configure: coreConfigure, clientApi, forceReRender } = start(render); - -export const { - setAddon, - addDecorator, - addParameters, - clearDecorators, - getStorybook, - raw, -} = clientApi; - -const framework = 'ember'; -export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); - -export { forceReRender }; diff --git a/app/ember/src/client/preview/index.ts b/app/ember/src/client/preview/index.ts new file mode 100644 index 000000000000..1b159f531b64 --- /dev/null +++ b/app/ember/src/client/preview/index.ts @@ -0,0 +1,22 @@ +import { start } from '@storybook/core/client'; + +import './globals'; +import render from './render'; + +const { configure: coreConfigure, clientApi, forceReRender } = start(render); + +export const { + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, + raw, +} = clientApi; + +const framework = 'ember'; +export const storiesOf = (...args: any) => + clientApi.storiesOf(...args).addParameters({ framework }); +export const configure = (...args: any) => coreConfigure(...args, framework); + +export { forceReRender }; diff --git a/app/ember/src/client/preview/render.js b/app/ember/src/client/preview/render.js deleted file mode 100644 index 05fad4a429cf..000000000000 --- a/app/ember/src/client/preview/render.js +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable no-undef */ -import { window, document } from 'global'; -import dedent from 'ts-dedent'; - -const rootEl = document.getElementById('root'); - -const config = window.require(`${window.STORYBOOK_NAME}/config/environment`); -const app = window.require(`${window.STORYBOOK_NAME}/app`).default.create({ - autoboot: false, - rootElement: rootEl, - ...config.APP, -}); - -let lastPromise = app.boot(); -let hasRendered = false; - -function render(options, el) { - const { template, context = {}, element } = options; - - if (hasRendered) { - lastPromise = lastPromise.then(instance => instance.destroy()); - } - - lastPromise = lastPromise - .then(() => { - const appInstancePrivate = app.buildInstance(); - return appInstancePrivate.boot().then(() => appInstancePrivate); - }) - .then(instance => { - instance.register( - 'component:story-mode', - Ember.Component.extend({ - layout: template || options, - ...context, - }) - ); - - const component = instance.lookup('component:story-mode'); - - if (element) { - component.appendTo(element); - - element.appendTo(el); - } else { - component.appendTo(el); - } - hasRendered = true; - - return instance; - }); -} - -export default function renderMain({ - storyFn, - selectedKind, - selectedStory, - showMain, - showError, - // forceRender, -}) { - const element = storyFn(); - - if (!element) { - showError({ - title: `Expecting a Ember element from the story: "${selectedStory}" of "${selectedKind}".`, - description: dedent` - Did you forget to return the Ember element from the story? - Use "() => hbs('{{component}}')" or "() => { return { - template: hbs\`{{component}}\` - } }" when defining the story. - `, - }); - return; - } - - showMain(); - render(element, rootEl); -} diff --git a/app/ember/src/client/preview/render.ts b/app/ember/src/client/preview/render.ts new file mode 100644 index 000000000000..cc86ccb25fe9 --- /dev/null +++ b/app/ember/src/client/preview/render.ts @@ -0,0 +1,78 @@ +import { window, document } from 'global'; +import dedent from 'ts-dedent'; +import { RenderContext, ElementArgs, OptionsArgs } from './types'; + +declare let Ember: any; + +const rootEl = document.getElementById('root'); + +const config = window.require(`${window.STORYBOOK_NAME}/config/environment`); +const app = window.require(`${window.STORYBOOK_NAME}/app`).default.create({ + autoboot: false, + rootElement: rootEl, + ...config.APP, +}); + +let lastPromise = app.boot(); +let hasRendered = false; +let isRendering = false; + +function render(options: OptionsArgs, el: ElementArgs) { + if (isRendering) return; + isRendering = true; + + const { template, context = {}, element } = options; + + if (hasRendered) { + lastPromise = lastPromise.then((instance: any) => instance.destroy()); + } + + lastPromise = lastPromise + .then(() => { + const appInstancePrivate = app.buildInstance(); + return appInstancePrivate.boot().then(() => appInstancePrivate); + }) + .then((instance: any) => { + instance.register( + 'component:story-mode', + Ember.Component.extend({ + layout: template || options, + ...context, + }) + ); + + const component = instance.lookup('component:story-mode'); + + if (element) { + component.appendTo(element); + + element.appendTo(el); + } else { + component.appendTo(el); + } + hasRendered = true; + isRendering = false; + + return instance; + }); +} + +export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) { + const element = storyFn(); + + if (!element) { + showError({ + title: `Expecting a Ember element from the story: "${name}" of "${kind}".`, + description: dedent` + Did you forget to return the Ember element from the story? + Use "() => hbs('{{component}}')" or "() => { return { + template: hbs\`{{component}}\` + } }" when defining the story. + `, + }); + return; + } + + showMain(); + render(element, rootEl); +} diff --git a/app/ember/src/client/preview/types.ts b/app/ember/src/client/preview/types.ts new file mode 100644 index 000000000000..b8d96b2b7d55 --- /dev/null +++ b/app/ember/src/client/preview/types.ts @@ -0,0 +1,16 @@ +export { RenderContext } from '@storybook/core'; + +export interface ShowErrorArgs { + title: string; + description: string; +} + +export interface ElementArgs { + el: HTMLElement; +} + +export interface OptionsArgs { + template: any; + context: any; + element: any; +} diff --git a/app/polymer/src/server/build.js b/app/ember/src/server/build.ts old mode 100755 new mode 100644 similarity index 100% rename from app/polymer/src/server/build.js rename to app/ember/src/server/build.ts diff --git a/app/ember/src/server/framework-preset-babel-ember.js b/app/ember/src/server/framework-preset-babel-ember.js deleted file mode 100644 index 10439b8fb7cb..000000000000 --- a/app/ember/src/server/framework-preset-babel-ember.js +++ /dev/null @@ -1,15 +0,0 @@ -import { precompile } from 'ember-source/dist/ember-template-compiler'; - -export function babel(config) { - const babelConfigPlugins = config.plugins || []; - - const extraPlugins = [ - [require.resolve('babel-plugin-htmlbars-inline-precompile'), { precompile }], - [require.resolve('babel-plugin-ember-modules-api-polyfill')], - ]; - - return { - ...config, - plugins: [].concat(babelConfigPlugins, extraPlugins), - }; -} diff --git a/app/ember/src/server/framework-preset-babel-ember.ts b/app/ember/src/server/framework-preset-babel-ember.ts new file mode 100644 index 000000000000..7ba0eea11ab9 --- /dev/null +++ b/app/ember/src/server/framework-preset-babel-ember.ts @@ -0,0 +1,26 @@ +import { precompile } from 'ember-source/dist/ember-template-compiler'; +import { Configuration } from 'webpack'; // eslint-disable-line + +export function babel(config: Configuration) { + const babelConfigPlugins = config.plugins || []; + + const extraPlugins = [ + [ + require.resolve('babel-plugin-htmlbars-inline-precompile'), + { + precompile, + modules: { + 'ember-cli-htmlbars': 'hbs', + 'ember-cli-htmlbars-inline-precompile': 'default', + 'htmlbars-inline-precompile': 'default', + }, + }, + ], + [require.resolve('babel-plugin-ember-modules-api-polyfill')], + ]; + + return { + ...config, + plugins: [].concat(babelConfigPlugins, extraPlugins), + }; +} diff --git a/app/polymer/src/server/index.js b/app/ember/src/server/index.ts old mode 100755 new mode 100644 similarity index 100% rename from app/polymer/src/server/index.js rename to app/ember/src/server/index.ts diff --git a/app/ember/src/server/options.js b/app/ember/src/server/options.js deleted file mode 100644 index b5b03fad7e64..000000000000 --- a/app/ember/src/server/options.js +++ /dev/null @@ -1,7 +0,0 @@ -import packageJson from '../../package.json'; - -export default { - packageJson, - framework: 'ember', - frameworkPresets: [require.resolve('./framework-preset-babel-ember.js')], -}; diff --git a/app/ember/src/server/options.ts b/app/ember/src/server/options.ts new file mode 100644 index 000000000000..f268bb9fbde2 --- /dev/null +++ b/app/ember/src/server/options.ts @@ -0,0 +1,7 @@ +const packageJson = require('../../package.json'); + +export default { + packageJson, + framework: 'ember', + frameworkPresets: [require.resolve('./framework-preset-babel-ember.js')], +}; diff --git a/app/ember/src/typings.d.ts b/app/ember/src/typings.d.ts new file mode 100644 index 000000000000..f8360cbb3808 --- /dev/null +++ b/app/ember/src/typings.d.ts @@ -0,0 +1,3 @@ +declare module '@storybook/core/*'; +declare module 'ember-source/dist/ember-template-compiler'; +declare module 'global'; diff --git a/app/ember/tsconfig.json b/app/ember/tsconfig.json new file mode 100644 index 000000000000..94fbbf981593 --- /dev/null +++ b/app/ember/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "types": ["webpack-env"], + "resolveJsonModule": true + }, + "include": ["src/**/*", "package.json"], + "exclude": ["src/**/*.test.*"] +} \ No newline at end of file diff --git a/app/html/package.json b/app/html/package.json index 0f31b1b6c968..b16b46f116a1 100644 --- a/app/html/package.json +++ b/app/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/html" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,21 +22,31 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", - "html-loader": "^0.5.5", + "html-loader": "^1.0.0", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0" + "@babel/core": "*", + "react": "*", + "react-dom": "*" }, "engines": { "node": ">=8.0.0" @@ -51,5 +54,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/html/src/client/preview/render.ts b/app/html/src/client/preview/render.ts index 103ecf65bf5b..a885d4d9ad96 100644 --- a/app/html/src/client/preview/render.ts +++ b/app/html/src/client/preview/render.ts @@ -1,17 +1,17 @@ import { document, Node } from 'global'; import dedent from 'ts-dedent'; -import { RenderMainArgs } from './types'; +import { RenderContext } from './types'; const rootElement = document.getElementById('root'); export default function renderMain({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain, showError, forceRender, -}: RenderMainArgs) { +}: RenderContext) { const element = storyFn(); showMain(); @@ -27,7 +27,7 @@ export default function renderMain({ rootElement.appendChild(element); } else { showError({ - title: `Expecting an HTML snippet or DOM node from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the HTML snippet from the story? Use "() => " or when defining the story. diff --git a/app/html/src/client/preview/types.ts b/app/html/src/client/preview/types.ts index 36432e1a4558..d12d2266fbaf 100644 --- a/app/html/src/client/preview/types.ts +++ b/app/html/src/client/preview/types.ts @@ -1,4 +1,4 @@ -import { StoryFn } from '@storybook/addons'; +export { RenderContext } from '@storybook/core'; export type StoryFnHtmlReturnType = string | Node; @@ -16,12 +16,3 @@ export interface ShowErrorArgs { title: string; description: string; } - -export interface RenderMainArgs { - storyFn: () => StoryFn; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - forceRender: boolean; -} diff --git a/app/html/src/server/options.ts b/app/html/src/server/options.ts index 1d349ea837db..33d4bf3928ea 100644 --- a/app/html/src/server/options.ts +++ b/app/html/src/server/options.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line: no-var-requires const packageJson = require('../../package.json'); export default { diff --git a/app/marionette/README.md b/app/marionette/README.md new file mode 100644 index 000000000000..fffc49bd35d5 --- /dev/null +++ b/app/marionette/README.md @@ -0,0 +1,25 @@ +# Storybook for Marionette.js + +--- + +Storybook for Marionette.js is a UI development environment for your Marionette.js components. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-app +npx -p @storybook/cli sb init +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. \ No newline at end of file diff --git a/app/marionette/bin/build.js b/app/marionette/bin/build.js new file mode 100755 index 000000000000..26142ec0af29 --- /dev/null +++ b/app/marionette/bin/build.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +require('../dist/server/build'); diff --git a/app/react-native-server/bin/index.js b/app/marionette/bin/index.js similarity index 100% rename from app/react-native-server/bin/index.js rename to app/marionette/bin/index.js diff --git a/app/marionette/package.json b/app/marionette/package.json new file mode 100644 index 000000000000..1457920afc10 --- /dev/null +++ b/app/marionette/package.json @@ -0,0 +1,52 @@ +{ + "name": "@storybook/marionette", + "version": "6.0.0-beta.21", + "description": "Storybook for Marionette: Develop Marionette.js component in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/app/marionette", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/marionette" + }, + "license": "MIT", + "main": "dist/client/index.js", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/core": "6.0.0-beta.21", + "common-tags": "^1.8.0", + "core-js": "^3.0.1", + "global": "^4.3.2", + "html-loader": "^1.0.0", + "regenerator-runtime": "^0.13.3" + }, + "devDependencies": { + "backbone.marionette": "*" + }, + "peerDependencies": { + "@babel/core": "*", + "backbone": "*", + "backbone.marionette": "*", + "react": "*", + "react-dom": "*", + "underscore": "*" + }, + "engines": { + "node": ">=8.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/app/marionette/src/client/index.js b/app/marionette/src/client/index.js new file mode 100644 index 000000000000..c3f87077b852 --- /dev/null +++ b/app/marionette/src/client/index.js @@ -0,0 +1,15 @@ +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, + forceReRender, + raw, + load, +} from './preview'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/app/marionette/src/client/preview/element_check.js b/app/marionette/src/client/preview/element_check.js new file mode 100644 index 000000000000..61d71cc68d4b --- /dev/null +++ b/app/marionette/src/client/preview/element_check.js @@ -0,0 +1,20 @@ +import Marionette from 'backbone.marionette'; + +const allMarionetteViewConstructors = [ + 'View', + 'CompositeView', + 'CollectionView', + 'NextCollectionView', +]; +const viewConstructorsSupportedByMarionette = allMarionetteViewConstructors + .filter((constructorName) => constructorName in Marionette) + .map((constructorName) => Marionette[constructorName]); + +// accepts an element and return true if renderable else return false +const isMarionetteRenderable = (element) => { + return viewConstructorsSupportedByMarionette.find( + (Constructor) => element instanceof Constructor + ); +}; + +export default isMarionetteRenderable; diff --git a/app/marionette/src/client/preview/globals.js b/app/marionette/src/client/preview/globals.js new file mode 100644 index 000000000000..168d2f7c49ac --- /dev/null +++ b/app/marionette/src/client/preview/globals.js @@ -0,0 +1,3 @@ +import { window } from 'global'; + +window.STORYBOOK_ENV = 'marionette'; diff --git a/app/marionette/src/client/preview/index.js b/app/marionette/src/client/preview/index.js new file mode 100644 index 000000000000..0128a027102c --- /dev/null +++ b/app/marionette/src/client/preview/index.js @@ -0,0 +1,22 @@ +import { start } from '@storybook/core/client'; + +import './globals'; +import render from './render'; + +const { load: coreLoad, clientApi, configApi, forceReRender } = start(render); + +export const { + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, + raw, +} = clientApi; + +const framework = 'marionette'; +export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); +export const load = (...args) => coreLoad(...args, framework); + +export const { configure } = configApi; +export { forceReRender }; diff --git a/app/marionette/src/client/preview/render.js b/app/marionette/src/client/preview/render.js new file mode 100644 index 000000000000..c236aa3b4cfc --- /dev/null +++ b/app/marionette/src/client/preview/render.js @@ -0,0 +1,40 @@ +import { document } from 'global'; +import { stripIndents } from 'common-tags'; +import Marionette from 'backbone.marionette'; +import isMarionetteRenderable from './element_check'; + +const rootEl = document.getElementById('root'); +const rootRegion = new Marionette.Region({ el: rootEl }); + +function render(view) { + rootRegion.show(view); +} + +export default function renderMain({ storyFn, kind, name, showMain, showError }) { + const element = storyFn(); + + if (!element) { + showError({ + title: `Expecting a Marionette View from the story: "${name}" of "${kind}".`, + description: stripIndents` + Did you forget to return the React element from the story? + Use "() => ()" or "() => { return ; }" when defining the story. + `, + }); + return; + } + + if (!isMarionetteRenderable(element)) { + showError({ + title: `Expecting a valid Marionette View from the story: "${name}" of "${kind}".`, + description: stripIndents` + Seems like you are not returning a correct Marionette View from the story. + Could you double check that? + `, + }); + return; + } + + render(element); + showMain(); +} diff --git a/app/marionette/src/server/build.js b/app/marionette/src/server/build.js new file mode 100755 index 000000000000..d8abf06a4396 --- /dev/null +++ b/app/marionette/src/server/build.js @@ -0,0 +1,4 @@ +import { buildStatic } from '@storybook/core/server'; +import options from './options'; + +buildStatic(options); diff --git a/app/marionette/src/server/framework-preset-marionette.js b/app/marionette/src/server/framework-preset-marionette.js new file mode 100644 index 000000000000..df8a5ec614e1 --- /dev/null +++ b/app/marionette/src/server/framework-preset-marionette.js @@ -0,0 +1,3 @@ +export function webpack(config) { + return config; +} diff --git a/app/marionette/src/server/index.js b/app/marionette/src/server/index.js new file mode 100755 index 000000000000..774d96025a84 --- /dev/null +++ b/app/marionette/src/server/index.js @@ -0,0 +1,4 @@ +import { buildDev } from '@storybook/core/server'; +import options from './options'; + +buildDev(options); diff --git a/app/marionette/src/server/options.js b/app/marionette/src/server/options.js new file mode 100644 index 000000000000..a1ea7a38f6d8 --- /dev/null +++ b/app/marionette/src/server/options.js @@ -0,0 +1,6 @@ +import packageJson from '../../package.json'; + +export default { + packageJson, + frameworkPresets: [require.resolve('./framework-preset-marionette.js')], +}; diff --git a/app/marionette/standalone.js b/app/marionette/standalone.js new file mode 100644 index 000000000000..1b1febe0d3bb --- /dev/null +++ b/app/marionette/standalone.js @@ -0,0 +1,8 @@ +const build = require('@storybook/core/standalone'); +const frameworkOptions = require('./dist/server/options').default; + +async function buildStandalone(options) { + return build(options, frameworkOptions); +} + +module.exports = buildStandalone; diff --git a/app/marko/package.json b/app/marko/package.json index 282a5f047aec..fd920f6e07e6 100644 --- a/app/marko/package.json +++ b/app/marko/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/marko", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Marko: Develop Marko Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,34 +15,38 @@ "directory": "app/marko" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "bin": { "build-storybook": "./bin/build.js", "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@marko/webpack": "^2.0.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", + "@marko/webpack": "^2.1.0", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", - "marko": "^4.15.2" + "@babel/core": "*", + "marko": "^4.15.2", + "react": "*", + "react-dom": "*", + "webpack": "^4" }, "engines": { "node": ">=8.0.0" @@ -50,5 +54,5 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/marko/src/client/preview/render.js b/app/marko/src/client/preview/render.js index 688359beaacb..f314429c8384 100644 --- a/app/marko/src/client/preview/render.js +++ b/app/marko/src/client/preview/render.js @@ -6,21 +6,24 @@ import { logger } from '@storybook/client-logger'; const rootEl = document.getElementById('root'); let activeComponent = null; // currently loaded marko component. let activeTemplate = null; // template for the currently loaded component. +let activeStoryFn = null; // used to determine if we've switched stories. export default function renderMain({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain, showError, parameters, // forceRender, }) { + const isSameStory = activeStoryFn === storyFn; const config = storyFn(); + activeStoryFn = storyFn; if (!config || !(config.appendTo || config.component || parameters.component)) { showError({ - title: `Expecting an object with a component property to be returned from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting an object with a component property to be returned from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the component from the story? Use "() => ({ component: MyComponent, input: { hello: 'world' } })" when defining the story. @@ -43,7 +46,7 @@ export default function renderMain({ } else { const template = config.component || parameters.component; - if (activeTemplate === template) { + if (isSameStory && activeTemplate === template) { // When rendering the same template with new input, we reuse the same instance. activeComponent.input = config.input; activeComponent.update(); @@ -53,10 +56,7 @@ export default function renderMain({ } activeTemplate = template; - activeComponent = activeTemplate - .renderSync(config.input) - .appendTo(rootEl) - .getComponent(); + activeComponent = activeTemplate.renderSync(config.input).appendTo(rootEl).getComponent(); } } diff --git a/app/marko/src/demo/Welcome.marko b/app/marko/src/demo/Welcome.marko index cd0daa81798b..4580889dd3b4 100644 --- a/app/marko/src/demo/Welcome.marko +++ b/app/marko/src/demo/Welcome.marko @@ -28,5 +28,5 @@ a {
-

Welcome to Storybook for Marko

+

Welcome to storybook

diff --git a/app/mithril/package.json b/app/mithril/package.json index 208449fe5c91..11b664752398 100644 --- a/app/mithril/package.json +++ b/app/mithril/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/mithril", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Mithril: Develop Mithril Component in isolation.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/mithril" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,26 +22,37 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/core": "^7.6.2", + "@babel/core": "^7.9.6", "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", "@types/mithril": "^2.0.0", + "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { "mithril": "^1.1.6" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", - "mithril": "^1.1.6" + "@babel/core": "*", + "mithril": "^1.1.6", + "react": "*", + "react-dom": "*" }, "engines": { "node": ">=8.0.0" @@ -56,5 +60,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/mithril/src/client/preview/render.ts b/app/mithril/src/client/preview/render.ts index a368489d5055..285e68a387b9 100644 --- a/app/mithril/src/client/preview/render.ts +++ b/app/mithril/src/client/preview/render.ts @@ -4,22 +4,16 @@ import { document } from 'global'; import m from 'mithril'; import dedent from 'ts-dedent'; -import { RenderMainArgs } from './types'; +import { RenderContext } from './types'; const rootEl = document.getElementById('root'); -export default function renderMain({ - storyFn, - selectedKind, - selectedStory, - showMain, - showError, -}: RenderMainArgs) { +export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) { const element = storyFn(); if (!element) { const error = { - title: `Expecting a Mithril element from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a Mithril element from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the Mithril element from the story? Use "() => MyComp" or "() => { return MyComp; }" when defining the story. diff --git a/app/mithril/src/client/preview/types.ts b/app/mithril/src/client/preview/types.ts index c9755ab82eb0..2e49c6bb73af 100644 --- a/app/mithril/src/client/preview/types.ts +++ b/app/mithril/src/client/preview/types.ts @@ -1,5 +1,7 @@ import m from 'mithril'; +export { RenderContext } from '@storybook/core'; + export interface IStorybookStory { name: string; render: () => any; @@ -16,13 +18,3 @@ export interface ShowErrorArgs { title: string; description: string; } - -export interface RenderMainArgs { - storyFn: () => StoryFnMithrilReturnType; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - showException: (err: Error) => void; - forceRender: boolean; -} diff --git a/app/polymer/README.md b/app/polymer/README.md deleted file mode 100644 index d2b25e666537..000000000000 --- a/app/polymer/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Storybook for Polymer - -Storybook for polymer is a UI development environment for your Polymer components. -With it, you can visualize different states of your UI components and develop them interactively. - -> Storybook for Polymer is at the **EXPERIMENTAL** stage! - -![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) - -Storybook runs outside of your app. -So you can develop UI components in isolation without worrying about app specific dependencies and requirements. - -## Getting Started - -```sh -cd my-polymer-app -npx -p @storybook/cli sb init -``` - -For more information visit: [storybook.js.org](https://storybook.js.org) - ---- - -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. - -## Polymer Notes - -- This is super super experimental, if you want to use this, expect some bugs, and missing features. -- We're looking for help to support this. If you're a member of the Polymer community and like this project, please help us! - If you need any onboarding from us, we're happy to help you in any way! diff --git a/app/polymer/package.json b/app/polymer/package.json deleted file mode 100644 index 0b8d95d2b478..000000000000 --- a/app/polymer/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@storybook/polymer", - "version": "5.3.0-rc.0", - "description": "Storybook for Polymer: Develop Polymer components in isolation with Hot Reloading.", - "keywords": [ - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/app/polymer", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "app/polymer" - }, - "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/client/index.js", - "bin": { - "build-storybook": "./bin/build.js", - "start-storybook": "./bin/index.js", - "storybook-server": "./bin/index.js" - }, - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/core": "5.3.0-rc.0", - "@webcomponents/webcomponentsjs": "^1.2.0", - "core-js": "^3.0.1", - "global": "^4.3.2", - "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0", - "webpack": "^4.33.0" - }, - "devDependencies": { - "lit-html": "^1.0.0", - "polymer-webpack-loader": "^2.0.3" - }, - "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", - "lit-html": "^1.0.0", - "polymer-webpack-loader": "^2.0.2" - }, - "engines": { - "node": ">=8.0.0" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/app/polymer/src/client/preview/globals.js b/app/polymer/src/client/preview/globals.js deleted file mode 100644 index ddb4205926a8..000000000000 --- a/app/polymer/src/client/preview/globals.js +++ /dev/null @@ -1,6 +0,0 @@ -import '@webcomponents/webcomponentsjs/webcomponents-lite'; -import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter'; - -import { window } from 'global'; - -window.STORYBOOK_ENV = 'polymer'; diff --git a/app/polymer/src/client/preview/index.js b/app/polymer/src/client/preview/index.js deleted file mode 100644 index 8ccdb4865d0b..000000000000 --- a/app/polymer/src/client/preview/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { start } from '@storybook/core/client'; - -import './globals'; -import render from './render'; - -const { configure: coreConfigure, clientApi, forceReRender } = start(render); - -export const { - setAddon, - addDecorator, - addParameters, - clearDecorators, - getStorybook, - raw, -} = clientApi; - -const framework = 'polymer'; -export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); - -export { forceReRender }; diff --git a/app/polymer/src/client/preview/render.js b/app/polymer/src/client/preview/render.js deleted file mode 100644 index b691510f0354..000000000000 --- a/app/polymer/src/client/preview/render.js +++ /dev/null @@ -1,45 +0,0 @@ -import { document } from 'global'; -import dedent from 'ts-dedent'; -import { render, TemplateResult } from 'lit-html'; - -const rootElement = document.getElementById('root'); - -export default function renderMain({ - storyFn, - selectedKind, - selectedStory, - showMain, - showError, - forceRender, -}) { - const element = storyFn(); - - if (!element) { - showError({ - title: `Expecting a Polymer component from the story: "${selectedStory}" of "${selectedKind}".`, - description: dedent` - Did you forget to return the Polymer component from the story? - Use "() => '<your-component-name></your-component-name\>'" when defining the story. - `, - }); - return; - } - - showMain(); - if (typeof element === 'string') { - rootElement.innerHTML = element; - } else if (element instanceof TemplateResult) { - // `render` stores the TemplateInstance in the Node and tries to update based on that. - // Since we reuse `rootElement` for all stories, remove the stored instance first. - // But forceRender means that it's the same story, so we want too keep the state in that case. - if (!forceRender || !rootElement.querySelector('[id="root-inner"]')) { - rootElement.innerHTML = '
'; - } - const renderTo = rootElement.querySelector('[id="root-inner"]'); - - render(element, renderTo); - } else { - rootElement.innerHTML = ''; - rootElement.appendChild(element); - } -} diff --git a/app/polymer/src/server/framework-preset-polymer.js b/app/polymer/src/server/framework-preset-polymer.js deleted file mode 100644 index c1c615b7d114..000000000000 --- a/app/polymer/src/server/framework-preset-polymer.js +++ /dev/null @@ -1,28 +0,0 @@ -import { IgnorePlugin } from 'webpack'; - -export function webpack(config) { - return { - ...config, - module: { - ...config.module, - rules: [ - ...config.module.rules, - { - test: /\.html$/, - use: [ - ...config.module.rules[0].use, - { - loader: require.resolve('polymer-webpack-loader'), - options: { processStyleLinks: true }, - }, - ], - }, - ], - }, - plugins: [ - ...config.plugins, - // See https://github.com/webcomponents/webcomponentsjs/issues/794#issuecomment-386554298 - new IgnorePlugin(/^vertx$/), - ], - }; -} diff --git a/app/polymer/src/server/options.js b/app/polymer/src/server/options.js deleted file mode 100644 index 7d832b7cebcc..000000000000 --- a/app/polymer/src/server/options.js +++ /dev/null @@ -1,7 +0,0 @@ -import packageJson from '../../package.json'; - -export default { - packageJson, - framework: 'polymer', - frameworkPresets: [require.resolve('./framework-preset-polymer.js')], -}; diff --git a/app/preact/package.json b/app/preact/package.json index fe3ce9e4202c..cacb51f75522 100644 --- a/app/preact/package.json +++ b/app/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/preact" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,26 +22,35 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { - "preact": "^8.4.2" + "preact": "^10.4.0" }, "peerDependencies": { - "@babel/core": "7.0.1", - "babel-loader": "^7.0.0 || ^8.0.0", - "preact": "^8.4.2" + "@babel/core": "*", + "preact": "^10.0.0", + "react": "*", + "react-dom": "*" }, "engines": { "node": ">=8.0.0" @@ -56,5 +58,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/preact/src/client/preview/render.tsx b/app/preact/src/client/preview/render.tsx index c9ff924f4ef6..693b5451270e 100644 --- a/app/preact/src/client/preview/render.tsx +++ b/app/preact/src/client/preview/render.tsx @@ -1,23 +1,16 @@ import { h, render } from 'preact'; import { document } from 'global'; import dedent from 'ts-dedent'; -import { RenderMainArgs } from './types'; +import { RenderContext } from './types'; -let renderedStory: Element; const rootElement = document ? document.getElementById('root') : null; -export default function renderMain({ - storyFn, - selectedKind, - selectedStory, - showMain, - showError, -}: RenderMainArgs) { +export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) { const element = storyFn(); if (!element) { showError({ - title: `Expecting a Preact element from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a Preact element from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the Preact element from the story? Use "() => ()" or "() => { return ; }" when defining the story. @@ -26,9 +19,9 @@ export default function renderMain({ return; } - render(null, rootElement, renderedStory); + render(null, rootElement); showMain(); - renderedStory = render(element, rootElement); + render(element, rootElement); } diff --git a/app/preact/src/client/preview/types.ts b/app/preact/src/client/preview/types.ts index d961b76f5c5b..4ba69cc4e252 100644 --- a/app/preact/src/client/preview/types.ts +++ b/app/preact/src/client/preview/types.ts @@ -1,4 +1,6 @@ -import { StoryFn, ClientStoryApi, Loadable } from '@storybook/addons'; +import { ClientStoryApi, Loadable } from '@storybook/addons'; + +export { RenderContext } from '@storybook/core'; export type StoryFnPreactReturnType = string | Node | preact.JSX.Element; @@ -7,15 +9,6 @@ export interface ShowErrorArgs { description: string; } -export interface RenderMainArgs { - storyFn: () => StoryFn; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - forceRender: boolean; -} - export interface IStorybookStory { name: string; render: () => any; diff --git a/app/rax/package.json b/app/rax/package.json index 387eaca19a37..1c2c2da840ca 100644 --- a/app/rax/package.json +++ b/app/rax/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/rax", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Rax: Develop Rax Component in isolation.", "keywords": [ "rax", @@ -16,40 +16,43 @@ "directory": "app/rax" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "bin": { "build-storybook": "./bin/build.js", "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-rc.0", + "@storybook/core": "6.0.0-beta.21", "babel-preset-rax": "^1.0.0-beta.0", "core-js": "^3.0.1", "driver-dom": "^2.0.0", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { "rax": "^1.1.0" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", - "rax": "^0.4.0 || ^1.0.0" + "@babel/core": "*", + "rax": "^0.4.0 || ^1.0.0", + "react": "*", + "react-dom": "*" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/rax/src/client/preview/render.js b/app/rax/src/client/preview/render.js index 2652c257aed9..fc1b4c93f8f7 100644 --- a/app/rax/src/client/preview/render.js +++ b/app/rax/src/client/preview/render.js @@ -8,8 +8,8 @@ const rootElement = document ? document.getElementById('root') : null; export default function renderMain({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain, showError, // forceRender, @@ -18,7 +18,7 @@ export default function renderMain({ if (!Element) { showError({ - title: `Expecting a Rax element from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a Rax element from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the Rax element from the story? Use "() => ()" or "() => { return ; }" when defining the story. diff --git a/app/react-native-server/package.json b/app/react-native-server/package.json deleted file mode 100644 index e58feefd74d4..000000000000 --- a/app/react-native-server/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "@storybook/react-native-server", - "version": "5.3.0-rc.0", - "description": "A better way to develop React Native Components for your app", - "keywords": [ - "react", - "react-native", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/app/react-native-server", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git" - }, - "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "bin": { - "start-storybook": "./bin/index.js", - "storybook-server": "./bin/index.js" - }, - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/channel-websocket": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/ui": "5.3.0-rc.0", - "commander": "^4.0.1", - "core-js": "^3.0.1", - "global": "^4.3.2", - "react": "^16.6.0", - "react-dom": "^16.8.3", - "uuid": "^3.3.2", - "webpack": "^4.33.0", - "ws": "^7.1.2" - }, - "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/app/react-native-server/readme.md b/app/react-native-server/readme.md deleted file mode 100644 index 2a1c211e808e..000000000000 --- a/app/react-native-server/readme.md +++ /dev/null @@ -1,13 +0,0 @@ -# Storybook Server for React Native -Additional package for @storybook/react-native to support a web server and browser UI that runs alongside the RN on-device UI - -For more information visit: [storybook.js.org](https://storybook.js.org) - -## Usage -After setting up Storybook for React Native install this package. - -```shell -npm install --save-dev @storybook/react-native-server -``` - -Then run `npx storybook start` diff --git a/app/react-native-server/src/client/manager/components/PreviewHelp.js b/app/react-native-server/src/client/manager/components/PreviewHelp.js deleted file mode 100644 index 40b37bd11d1e..000000000000 --- a/app/react-native-server/src/client/manager/components/PreviewHelp.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; - -const styles = { - main: { - margin: 15, - maxWidth: 600, - lineHeight: 1.4, - fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif', - }, - - code: { - fontSize: 15, - fontWeight: 600, - padding: '2px 5px', - border: '1px solid #eae9e9', - borderRadius: 4, - backgroundColor: '#f3f2f2', - color: '#3a3a3a', - }, - - codeBlock: { - backgroundColor: '#f3f2f2', - padding: '1px 10px', - margin: '10px 0', - }, -}; - -const PreviewHelp = () => ( -
-

Welcome to storybook

-

This is a UI component dev environment for your app.

-

- We've added some basic stories inside the {storybook/stories}{' '} - directory. A story is a single state of one or more UI components. You can have as many - stories as you want. Basically a story is like a visual test case. -

-

- To see your Storybook stories on the device, you should start your mobile app for the  - <platform> of your choice (typically ios or android). - (Note that due to an implementation detail, your stories will only show up in the left-pane - after your device has connected to this storybook server.) -

-

- For create-react-native-app apps: -

-
-
npm run <platform>
-
-

- For react-native init apps: -

-
-
react-native run-<platform>
-
-
-); - -export { PreviewHelp as default }; diff --git a/app/react-native-server/src/client/manager/index.js b/app/react-native-server/src/client/manager/index.js deleted file mode 100644 index 75acecbeb656..000000000000 --- a/app/react-native-server/src/client/manager/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* global storybookOptions */ -import { location, document } from 'global'; -import renderStorybookUI from '@storybook/ui'; -import Provider from './provider'; - -const rootEl = document.getElementById('root'); -renderStorybookUI(rootEl, new Provider({ url: location.host, options: storybookOptions })); diff --git a/app/react-native-server/src/client/manager/provider.js b/app/react-native-server/src/client/manager/provider.js deleted file mode 100644 index 4d54264b5de3..000000000000 --- a/app/react-native-server/src/client/manager/provider.js +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { Consumer } from '@storybook/api'; -import { Provider } from '@storybook/ui'; -import createChannel from '@storybook/channel-websocket'; -import addons from '@storybook/addons'; -import Events from '@storybook/core-events'; -import uuid from 'uuid'; -import PreviewHelp from './components/PreviewHelp'; - -const mapper = ({ state, api }) => ({ - api, - storiesHash: state.storiesHash, - storyId: state.storyId, - viewMode: state.viewMode, -}); - -export default class ReactProvider extends Provider { - constructor({ url: domain, options }) { - super(); - - const { secured, host, port } = options; - const websocketType = secured ? 'wss' : 'ws'; - let url = `${websocketType}://${domain}`; - - if (options.manualId) { - this.pairedId = uuid(); - url += `/pairedId=${this.pairedId}`; - } - - const channel = this.channel || createChannel({ url }); - - addons.setChannel(channel); - channel.emit(Events.CHANNEL_CREATED, { - host, - pairedId: this.pairedId, - port, - secured, - }); - - this.addons = addons; - this.channel = channel; - this.options = options; - } - - getElements(type) { - return addons.getElements(type); - } - - getConfig() { - return this.addons.getConfig(); - } - - renderPreview() { - return ( - - {({ storiesHash, storyId, api, viewMode }) => { - if (storiesHash[storyId]) { - api.emit(Events.SET_CURRENT_STORY, { storyId }); - } - return viewMode === 'story' ? : null; - }} - - ); - } - - handleAPI(api) { - addons.loadAddons(api); - api.emit(Events.GET_STORIES); - } -} diff --git a/app/react-native-server/src/server/cli.js b/app/react-native-server/src/server/cli.js deleted file mode 100644 index 4f7a538ad603..000000000000 --- a/app/react-native-server/src/server/cli.js +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'path'; -import program from 'commander'; - -export function parseList(str) { - return str.split(','); -} - -function getCli() { - program - .option('-h, --host ', 'host to listen on', 'localhost') - .option('-p, --port ', 'port to listen on', str => parseInt(str, 10), 7007) - .option('-e, --environment [environment]', 'DEVELOPMENT/PRODUCTION environment for webpack') - .option('-i, --manual-id', 'allow multiple users to work with same storybook') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option( - '--https', - 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' - ) - .option( - '--ssl-ca ', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') - .option('--smoke-test', 'Exit after successful start') - .option('--ci', "CI mode (skip interactive prompts, don't open browser") - .option('--quiet', 'Suppress verbose build output') - .parse(process.argv); - - const configDir = path.resolve(program.configDir || './storybook'); - - return { - ...program, - configDir, - }; -} - -export default getCli; diff --git a/app/react-native-server/src/server/index.js b/app/react-native-server/src/server/index.js deleted file mode 100755 index 7fca2b694fde..000000000000 --- a/app/react-native-server/src/server/index.js +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node - -import querystring from 'querystring'; -import ws from 'ws'; -import storybook from '@storybook/core/standalone'; - -import extendOptions from './options'; -import getCli from './cli'; - -export default class Server { - constructor(options) { - this.attachWS = this.attachWS.bind(this); - this.options = extendOptions(options, this.attachWS); - } - - start() { - return storybook(this.options); - } - - attachWS(server) { - this.wsServer = new ws.Server({ server }); - this.wsServer.on('connection', (s, req) => this.handleWS(s, req)); - } - - handleWS(socket, req) { - if (this.options.manualId) { - const params = req.url ? querystring.parse(req.url.substr(1)) : {}; - - if (params.pairedId) { - socket.pairedId = params.pairedId; // eslint-disable-line - } - } - - socket.on('message', data => { - this.wsServer.clients.forEach(c => { - if (!this.options.manualId || (socket.pairedId && socket.pairedId === c.pairedId)) { - c.send(data); - } - }); - }); - } -} - -const server = new Server(getCli()); -server.start(); diff --git a/app/react-native-server/src/server/options.js b/app/react-native-server/src/server/options.js deleted file mode 100644 index 873b3caab164..000000000000 --- a/app/react-native-server/src/server/options.js +++ /dev/null @@ -1,28 +0,0 @@ -import { managerPreset } from '@storybook/core/server'; -import packageJson from '../../package.json'; - -function extendOptions(options, extendServer) { - const { manualId, https: secured, host, port } = options; - const storybookOptions = { manualId, secured, host, port }; - - return { - ...options, - framework: 'react-native', - extendServer, - packageJson, - mode: 'dev', - ignorePreview: true, - corePresets: [ - { - name: managerPreset, - options: { managerEntry: require.resolve('../client/manager') }, - }, - { - name: require.resolve('./rn-options-preset.js'), - options: { storybookOptions }, - }, - ], - }; -} - -export default extendOptions; diff --git a/app/react-native-server/src/server/rn-options-preset.js b/app/react-native-server/src/server/rn-options-preset.js deleted file mode 100644 index 095342d722e4..000000000000 --- a/app/react-native-server/src/server/rn-options-preset.js +++ /dev/null @@ -1,15 +0,0 @@ -import webpack from 'webpack'; - -export async function managerWebpack(config, options) { - const { storybookOptions } = options; - - return { - ...config, - plugins: [ - ...config.plugins, - new webpack.DefinePlugin({ - storybookOptions: JSON.stringify(storybookOptions), - }), - ], - }; -} diff --git a/app/react-native/docs/addons.md b/app/react-native/docs/addons.md deleted file mode 100644 index 543ae31bc5b7..000000000000 --- a/app/react-native/docs/addons.md +++ /dev/null @@ -1,51 +0,0 @@ -# Addons - -Storybook supports addons. You can read more about them [here](https://storybook.js.org/addons/introduction/) - -There is one big difference in React Native is that it has two types of addons: Addons that work in the browser -and addons that work on the app itself (on device addons). - -## Browser addons -Browser addons are default addons to storybook. You create a file called addons.js inside storybook and it is -automatically added inside your browser. - -## On device addons -On device addons are addons that are displayed in your app in addons panel. -To use them you have to create a file called `rn-addons.js` next to your storybook entry. -Because React Native does not dynamically resolve imports, you also have to manually import them. -Example: -**storybook/index.js** -``` -import { getStorybookUI, configure } from '@storybook/react-native'; -import './rn-addons'; -// import stories -configure(() => { - require($PATH_TO_STORIES); -}, module); - -const StorybookUI = getStorybookUI(); -export default StorybookUI; -``` - -**storybook/rn-addons.js** -``` -import '@storybook/addon-ondevice-knobs/register'; -import '@storybook/addon-ondevice-notes/register'; -... -``` - -This step is done automatically when you install Storybook for the first time and also described in [Manual Setup](https://github.com/storybookjs/storybook/blob/master/app/react-native/docs/manual-setup.md) - -## Compatibility -Addon compatibility can be found [here](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -## Performance of on device addons -Because on device addons are inside the app, they are also rerendered on every change. This can reduce performance a lot. - -## Writing the on device addons -On device addons use same addon store and api as web addons. The only difference in api is that you don't have `api` prop -and have to rely on channel for everything. - -The main difference between browser and app addons is that the render has to be supported by React Native (View, Text). -For more info about writing addons read [writing addons](https://storybook.js.org/addons/writing-addons/) section in -storybook documentation. diff --git a/app/react-native/docs/assets/readme/screenshot.png b/app/react-native/docs/assets/readme/screenshot.png deleted file mode 100644 index 2097cf3c2603..000000000000 Binary files a/app/react-native/docs/assets/readme/screenshot.png and /dev/null differ diff --git a/app/react-native/docs/manual-setup.md b/app/react-native/docs/manual-setup.md deleted file mode 100644 index 4fb5be1480c7..000000000000 --- a/app/react-native/docs/manual-setup.md +++ /dev/null @@ -1,105 +0,0 @@ -# Manual Setup - -First, install the `@storybook/react-native` module - -```sh -yarn add @storybook/react-native --dev -``` - -Create a new directory called `storybook` in your project root and create an entry file (index.js) as given below. -(Don't forget to replace "MyApplicationName" with your app name). - -**storybook/index.js** -```js -import { AppRegistry } from 'react-native'; -import { getStorybookUI, configure } from '@storybook/react-native'; -import './rn-addons'; - -// import stories -configure(() => { - // eslint-disable-next-line global-require - require('./stories'); -}, module); - -const StorybookUIRoot = getStorybookUI(); - -AppRegistry.registerComponent('MyApplicationName', () => StorybookUIRoot); -export default StorybookUIRoot; -``` - -Create a file called `rn-addons.js` -In this file you can import on device addons. - -**storybook/rn-addons.js** -``` -import '@storybook/addon-ondevice-knobs/register'; -import '@storybook/addon-ondevice-notes/register'; -... -``` - - -Then write your first story in the `stories` directory like this: - -```js -import { storiesOf } from '@storybook/react-native'; -import React from 'react'; -import { View, Text } from 'react-native'; - -const style = { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF' -}; -const CenteredView = ({ children }) => ( - - {children} - -); - -storiesOf('CenteredView', module) - .add('default view', () => ( - - Hello Storybook - - )); -``` - -Finally replace your app entry with -```js -import './storybook'; -``` - -For example, if your entry app is named App.js/index.js (Expo/Vanilla). You can replace it with the following. - -``` -import StorybookUI from './storybook'; - -export default StorybookUI; -``` - -If you cannot replace your entry point, make sure that the component exported from `./storybook` is displayed -somewhere in your app. `StorybookUI` is a RN `View` component that can be embedded anywhere in your -RN application, e.g. on a tab or within an admin screen. - -## Server support - -If you want to support having a storybook server running install storybook server `npm install --save-dev @storybook/react-native-server` -and add following NPM script into your `package.json` file: - -```json -{ - "scripts": { - "storybook": "storybook start" - } -} -``` - -If you want to have addons inside browser, create a file named `addons.js` file in `storybook`. Here is a list of default addons: - -**storybook/addons.js** -```js -import '@storybook/addon-actions'; -import '@storybook/addon-links'; -``` - diff --git a/app/react-native/docs/server.md b/app/react-native/docs/server.md deleted file mode 100644 index fe4c53636024..000000000000 --- a/app/react-native/docs/server.md +++ /dev/null @@ -1,21 +0,0 @@ -# Storybook server -Since storybook v5 the storybook server is a standalone package. To keep using storybook server make sure to install @storybook/react-native-server package. - -## Benefits of storybook server - -* ### Websockets connection -The main benefit you get from running storybook server is that your app will be listening for websockets connection. -That means that you can create your own tools that integrate with your storybook app. - -* ### IDE Plugins -Having server running allows you to control your storybook view from inside web page or your ide. - -There is a plugin for [JetBrains IDEs](https://plugins.jetbrains.com/plugin/9910-storybook) and there is one -for [VS Code](https://github.com/orta/vscode-react-native-storybooks). - - -* ### Web addons -There are Storybook addons that work with React Native but do not have on device implementations. - - - diff --git a/app/react-native/docs/using-devices.md b/app/react-native/docs/using-devices.md deleted file mode 100644 index 40032f9f5f20..000000000000 --- a/app/react-native/docs/using-devices.md +++ /dev/null @@ -1,27 +0,0 @@ -# Connecting Devices - -In order to work with React Native Storybook, one or more devices should be connected. Stories will only show when devices are available. - -## iOS simulator - -- Start with `react-native run-ios` - -## Android emulator - -- Get your AVD name with `emulator -list-avds` -- Start the emulator `emulator -avd MY_AVD_NAME` -- Forward port 8081 `adb reverse tcp:8081 tcp:8081` -- Forward port 9001 `adb reverse tcp:9001 tcp:9001` -- Start with `react-native run-android` - -### Issues -**Problem**: If you run into a `No such file or directory` error - -**Solution**: You must run the emulator from its directory: `cd $(dirname $(which emulator)) && ./emulator -avd MY_AVD_NAME` - -## Android device - -- Connect your device with adb -- Forward port 8081 `adb reverse tcp:8081 tcp:8081` -- Forward port 9001 `adb reverse tcp:9001 tcp:9001` -- Start with `react-native run-android` diff --git a/app/react-native/package.json b/app/react-native/package.json deleted file mode 100644 index 17636fe745d7..000000000000 --- a/app/react-native/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@storybook/react-native", - "version": "5.3.0-rc.0", - "description": "A better way to develop React Native Components for your app", - "keywords": [ - "react", - "react-native", - "storybook" - ], - "homepage": "https://github.com/storybookjs/storybook/tree/master/app/react-native", - "bugs": { - "url": "https://github.com/storybookjs/storybook/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/storybookjs/storybook.git", - "directory": "app/react-native" - }, - "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], - "main": "dist/index.js", - "scripts": { - "prepare": "node ../../scripts/prepare.js" - }, - "dependencies": { - "@emotion/core": "^10.0.20", - "@emotion/native": "^10.0.14", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/channel-websocket": "5.3.0-rc.0", - "@storybook/channels": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "core-js": "^3.0.1", - "emotion-theming": "^10.0.19", - "react-native-swipe-gestures": "^1.0.4" - }, - "devDependencies": { - "@types/react-native": "^0.57.57", - "react-native": "^0.57.8" - }, - "peerDependencies": { - "react": "*", - "react-native": ">=0.57.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" -} diff --git a/app/react-native/readme.md b/app/react-native/readme.md deleted file mode 100644 index 2786490b0a09..000000000000 --- a/app/react-native/readme.md +++ /dev/null @@ -1,132 +0,0 @@ -# Storybook for React Native - -With Storybook for React Native you can design and develop individual React Native components without running your app. - -![Storybook Screenshot](docs/assets/readme/screenshot.png) - -For more information visit: [storybook.js.org](https://storybook.js.org) - -## Getting Started - -The `storybook` CLI tool can be used to add Storybook to your React Native app. Install the `storybook` tool if necessary and run it from your project directory with these commands: - -```shell -cd my-rn-app -npx -p @storybook/cli sb init -``` - -During installation it will ask if you want to install storybook server. -It allows you to control the storybook from your web browser. - -The next thing you need to do is make Storybook UI visible in your app. - -### CRNA, React Native vanilla - -The easiest way to use Storybook is to replace your App with the Storybook UI, which is possible by replacing `App.js` with a single line of code: - -```js -export default from './storybook'; -``` - -This will get you up and running quickly, but then you lose your app! -There are multiple options here. for example, you can export conditionally: - -```js -import StorybookUI from './storybook'; - -import App from './app'; - -module.exports = __DEV__ ? StorybookUI : App; -``` - -### React Native Navigation, other complex use cases - -`StorybookUI` is a RN `View` component that can be embedded anywhere in your RN application, e.g. on a tab or within an admin screen. - -## Start Storybook server (optional) - -If you want to control storybook from browser/VS Code/websockets you need install and start the server. - -```sh -npm run storybook -``` - -Now, you can open `` to view your storybook menus in the browser. - -## Start App - -To see your Storybook stories on the device, you should start your mobile app for the `` of your choice (typically `ios` or `android`). (Note that due to an implementation detail, your stories will only show up in the left pane of your browser window after your device has connected to this storybook server.) - -For CRNA apps: - -```sh -npm run -``` - -For RN apps: -```sh -react-native run- -``` - -Once your app is started, changing the selected story in web browser will update the story displayed within your mobile app. - -If you are using Android and you get the following error after running the app: `'websocket: connection error', 'Failed to connect to localhost/127.0.0.1:7007'`, you have to forward the port 7007 on your device/emulator to port 7007 on your local machine with the following command: -`adb reverse tcp:7007 tcp:7007` - -## Start Command Parameters - -The following parameters can be passed to the start command: - -``` --h, --host - host to listen on --p, --port - port to listen on ---https - whether server is running on https --c, --config-dir [dir-name] - storybook config directory --e, --environment [environment] - DEVELOPMENT/PRODUCTION environment for webpack --i, --manual-id - allow multiple users to work with same storybook ---smoke-test - Exit after successful start -``` - -## getStorybookUI Options - -You can pass these parameters to getStorybookUI call in your storybook entry point: - -``` -{ - onDeviceUI: Boolean (true) - -- display navigator and addons on the device - disableWebsockets: Boolean (false) - -- allows to display stories without running storybook server. Should be used with onDeviceUI - secured: Boolean (false) - -- use wss/https instead of ws/http - host: String (NativeModules.SourceCode.scriptURL) - -- host to use - port: Number (7007) - -- port to use - query: String ("") - -- additional query string to pass to websockets - isUIHidden: Boolean (false) - -- should the ui be closed initially. - tabOpen: Number (0) - -- which tab should be open. -1 Navigator, 0 Preview, 1 Addons - initialSelection: Object (null) - -- initialize storybook with a specific story. In case a valid object is passed, it will take precedence over `shouldPersistSelection. ex: `{ kind: 'Knobs', story: 'with knobs' }` - shouldPersistSelection: Boolean (true) - -- initialize storybook with the last selected story. - shouldDisableKeyboardAvoidingView: Boolean (false) - -- Disable KeyboardAvoidingView wrapping Storybook's view - keyboardAvoidingViewVerticalOffset: Number (0) - -- With shouldDisableKeyboardAvoidingView=true, this will set the keyboardverticaloffset (https://facebook.github.io/react-native/docs/keyboardavoidingview#keyboardverticaloffset) value for KeyboardAvoidingView wrapping Storybook's view -} -``` - -## Learn More - -Check the `docs` directory in this repo for more advanced setup guides and other info. diff --git a/app/react-native/src/index.ts b/app/react-native/src/index.ts deleted file mode 100644 index a13cd197f7d9..000000000000 --- a/app/react-native/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable prefer-destructuring */ -import Preview from './preview'; - -const preview = new Preview(); - -const rawStoriesOf = preview.api().storiesOf.bind(preview); -export const setAddon = preview.api().setAddon.bind(preview); -export const addDecorator = preview.api().addDecorator.bind(preview); -export const addParameters = preview.api().addParameters.bind(preview); -export const clearDecorators = preview.api().clearDecorators.bind(preview); -export const configure = preview.configure; -export const getStorybook = preview.api().getStorybook.bind(preview); -export const getStorybookUI = preview.getStorybookUI; -export const raw = preview.api().raw.bind(preview); - -export const storiesOf = (...args: any[]) => - rawStoriesOf(...args).addParameters({ framework: 'react-native' }); diff --git a/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx b/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx deleted file mode 100644 index 7bf42d8aba8e..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/absolute-positioned-keyboard-aware-view.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { PureComponent } from 'react'; -import { - Platform, - Keyboard, - Dimensions, - View, - EmitterSubscription, - LayoutChangeEvent, - KeyboardEvent, -} from 'react-native'; - -export interface PreviewDimens { - previewWidth: number; - previewHeight: number; -} - -type Props = { - onLayout: (dimens: PreviewDimens) => void; -} & PreviewDimens; - -// Android changes screen size when keyboard opens. -// To avoid issues we use absolute positioned element with predefined screen size -export default class AbsolutePositionedKeyboardAwareView extends PureComponent { - constructor(props: Props) { - super(props); - this.keyboardDidShowListener = Keyboard.addListener( - 'keyboardDidShow', - this.keyboardDidShowHandler - ); - this.keyboardDidHideListener = Keyboard.addListener( - 'keyboardDidHide', - this.keyboardDidHideHandler - ); - Dimensions.addEventListener('change', this.removeKeyboardOnOrientationChange); - } - - componentWillUnmount() { - this.keyboardDidShowListener.remove(); - this.keyboardDidHideListener.remove(); - Dimensions.removeEventListener('change', this.removeKeyboardOnOrientationChange); - } - - keyboardDidShowHandler = (e: KeyboardEvent) => { - if (Platform.OS === 'android') { - const { previewWidth } = this.props; - // There is bug in RN android that keyboardDidShow event is called when you go from portrait to landscape. - // To make sure that this is keyboard event we check screen width - if (previewWidth === e.endCoordinates.width) { - this.keyboardOpen = true; - } - } - }; - - // When rotating screen from portrait to landscape with keyboard open on android it calls keyboardDidShow, but doesn't call - // keyboardDidHide. To avoid issues we set keyboardOpen to false immediately on keyboardChange. - removeKeyboardOnOrientationChange = () => { - if (Platform.OS === 'android') { - this.keyboardOpen = false; - } - }; - - keyboardDidHideHandler = () => { - if (this.keyboardOpen) { - this.keyboardOpen = false; - } - }; - - onLayoutHandler = ({ nativeEvent }: LayoutChangeEvent) => { - if (!this.keyboardOpen) { - const { width, height } = nativeEvent.layout; - const { onLayout } = this.props; - - onLayout({ - previewHeight: height, - previewWidth: width, - }); - } - }; - - keyboardDidShowListener: EmitterSubscription; - - keyboardDidHideListener: EmitterSubscription; - - keyboardOpen: boolean; - - render() { - const { children, previewWidth, previewHeight } = this.props; - - return ( - - - {children} - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx b/app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx deleted file mode 100644 index c24c4c3f0331..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { PureComponent } from 'react'; -import { SafeAreaView } from 'react-native'; -import styled from '@emotion/native'; -import addons from '@storybook/addons'; -import AddonsList from './list'; -import AddonWrapper from './wrapper'; -import { Label } from '../../Shared/text'; - -const NoAddonContainer = styled.View({ - flex: 1, - alignItems: 'center', - justifyContent: 'center', -}); - -const Container = styled.View(({ theme }) => ({ - flex: 1, - backgroundColor: theme.backgroundColor, -})); - -export default class Addons extends PureComponent<{}, { addonSelected: string }> { - panels = addons.getElements('panel'); - - constructor(props: {}) { - super(props); - - this.state = { - addonSelected: Object.keys(this.panels)[0] || null, - }; - } - - onPressAddon = (addonSelected: string) => { - this.setState({ addonSelected }); - }; - - render() { - const { addonSelected } = this.state; - - if (Object.keys(this.panels).length === 0) { - return ( - - - - - - ); - } - - return ( - - - - - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx b/app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx deleted file mode 100644 index c9b4c2504e6a..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/list.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { PureComponent } from 'react'; -import { ScrollView } from 'react-native'; -import styled from '@emotion/native'; -import { Collection } from '@storybook/addons'; -import Button from '../navigation/button'; - -const Container = styled.View(({ theme }) => ({ - flexDirection: 'row', - borderBottomWidth: 1, - borderBottomColor: theme.borderColor, -})); - -export interface Props { - panels: Collection; - addonSelected: string; - onPressAddon: (id: string) => void; -} - -export default class AddonList extends PureComponent { - renderTab = (id: string, title: string) => { - const { addonSelected, onPressAddon } = this.props; - - return ( - - ); - }; - - render() { - const { panels } = this.props; - const addonKeys = Object.keys(panels); - - return ( - - - {addonKeys.map(id => this.renderTab(id, panels[id].title))} - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx b/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx deleted file mode 100644 index 1b101b4a2254..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/addons/wrapper.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { PureComponent } from 'react'; -import { View, ScrollView, StyleSheet } from 'react-native'; -import { Collection } from '@storybook/addons'; - -export interface Props { - panels: Collection; - addonSelected: string; -} - -const style = StyleSheet.create({ - invisible: { - height: 0, - width: 0, - opacity: 0, - position: 'absolute', - }, - flex: { - flex: 1, - }, -}); - -export default class Wrapper extends PureComponent { - static defaultProps = { - addonSelected: '', - }; - - render() { - const { panels, addonSelected } = this.props; - - const addonKeys = Object.keys(panels); - - return addonKeys.map(id => { - const selected = addonSelected === id; - - return ( - - {panels[id].render({ active: selected, key: id })} - - ); - }); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/animation.ts b/app/react-native/src/preview/components/OnDeviceUI/animation.ts deleted file mode 100644 index f19ef152fd80..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/animation.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Animated } from 'react-native'; -import { NAVIGATOR, PREVIEW, ADDONS } from './navigation/constants'; - -const PREVIEW_SCALE = 0.3; - -const panelWidth = (width: number) => width * (1 - PREVIEW_SCALE - 0.05); - -export const getNavigatorPanelPosition = (animatedValue: Animated.Value, previewWidth: number) => { - return [ - { - transform: [ - { - translateX: animatedValue.interpolate({ - inputRange: [NAVIGATOR, PREVIEW], - outputRange: [0, -panelWidth(previewWidth) - 1], - }), - }, - ], - width: panelWidth(previewWidth), - }, - ]; -}; - -export const getAddonPanelPosition = (animatedValue: Animated.Value, previewWidth: number) => { - return [ - { - transform: [ - { - translateX: animatedValue.interpolate({ - inputRange: [PREVIEW, ADDONS], - outputRange: [previewWidth, previewWidth - panelWidth(previewWidth)], - }), - }, - ], - width: panelWidth(previewWidth), - }, - ]; -}; - -export const getPreviewPosition = ( - animatedValue: Animated.Value, - previewWidth: number, - previewHeight: number, - slideBetweenAnimation: boolean -) => { - const translateX = previewWidth / 2 - (previewWidth * PREVIEW_SCALE) / 2 - 6; - const translateY = -(previewHeight / 2 - (previewHeight * PREVIEW_SCALE) / 2 - 12); - - return { - transform: [ - { - translateX: animatedValue.interpolate({ - inputRange: [NAVIGATOR, PREVIEW, ADDONS], - outputRange: [translateX, 0, -translateX], - }), - }, - { - translateY: animatedValue.interpolate({ - inputRange: [NAVIGATOR, PREVIEW, ADDONS], - outputRange: [translateY, slideBetweenAnimation ? translateY : 0, translateY], - }), - }, - ], - }; -}; - -export const getPreviewScale = (animatedValue: Animated.Value, slideBetweenAnimation: boolean) => { - return { - transform: [ - { - scale: animatedValue.interpolate({ - inputRange: [NAVIGATOR, PREVIEW, ADDONS], - outputRange: [PREVIEW_SCALE, slideBetweenAnimation ? PREVIEW_SCALE : 1, PREVIEW_SCALE], - }), - }, - ], - }; -}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/index.tsx b/app/react-native/src/preview/components/OnDeviceUI/index.tsx deleted file mode 100644 index 2d33bac60b5d..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/index.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { PureComponent } from 'react'; -import { - Keyboard, - KeyboardAvoidingView, - Platform, - Animated, - FlexStyle, - SafeAreaView, - Dimensions, - TouchableOpacity, -} from 'react-native'; -import styled from '@emotion/native'; -import addons from '@storybook/addons'; -import Channel from '@storybook/channels'; -import StoryListView from '../StoryListView'; -import StoryView from '../StoryView'; -import Addons from './addons'; -import Panel from './panel'; -import Navigation from './navigation'; -import AbsolutePositionedKeyboardAwareView, { - PreviewDimens, -} from './absolute-positioned-keyboard-aware-view'; -import { PREVIEW } from './navigation/constants'; -import { - getPreviewPosition, - getPreviewScale, - getAddonPanelPosition, - getNavigatorPanelPosition, -} from './animation'; - -const ANIMATION_DURATION = 300; -const IS_IOS = Platform.OS === 'ios'; - -interface OnDeviceUIProps { - stories: any; - url?: string; - tabOpen?: number; - isUIHidden?: boolean; - shouldDisableKeyboardAvoidingView?: boolean; - keyboardAvoidingViewVerticalOffset?: number; -} - -interface OnDeviceUIState { - tabOpen: number; - slideBetweenAnimation: boolean; - previewWidth: number; - previewHeight: number; -} - -const flex = { flex: 1 }; - -const Preview = styled.View<{ disabled: boolean }>(flex, ({ disabled, theme }) => ({ - borderLeftWidth: disabled ? 0 : 1, - borderTopWidth: disabled ? 0 : 1, - borderRightWidth: disabled ? 0 : 1, - borderBottomWidth: disabled ? 0 : 1, - borderColor: disabled ? 'transparent' : theme.previewBorderColor, -})); - -const absolutePosition: FlexStyle = { position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }; - -export default class OnDeviceUI extends PureComponent { - constructor(props: OnDeviceUIProps) { - super(props); - const tabOpen = props.tabOpen || PREVIEW; - - this.state = { - tabOpen, - slideBetweenAnimation: false, - previewWidth: Dimensions.get('window').width, - previewHeight: Dimensions.get('window').height, - }; - this.animatedValue = new Animated.Value(tabOpen); - this.channel = addons.getChannel(); - } - - onLayout = ({ previewWidth, previewHeight }: PreviewDimens) => { - this.setState({ previewWidth, previewHeight }); - }; - - handleOpenPreview = () => { - this.handleToggleTab(PREVIEW); - }; - - handleToggleTab = (newTabOpen: number) => { - const { tabOpen } = this.state; - if (newTabOpen === tabOpen) { - return; - } - Animated.timing(this.animatedValue, { - toValue: newTabOpen, - duration: ANIMATION_DURATION, - useNativeDriver: true, - }).start(); - this.setState({ - tabOpen: newTabOpen, - // True if swiping between navigator and addons - slideBetweenAnimation: tabOpen + newTabOpen === PREVIEW, - }); - // close the keyboard opened from a TextInput from story list or knobs - if (newTabOpen === PREVIEW) { - Keyboard.dismiss(); - } - }; - - animatedValue: Animated.Value; - - channel: Channel; - - render() { - const { - stories, - url, - isUIHidden, - shouldDisableKeyboardAvoidingView, - keyboardAvoidingViewVerticalOffset, - } = this.props; - - const { tabOpen, slideBetweenAnimation, previewWidth, previewHeight } = this.state; - - const previewWrapperStyles = [ - flex, - getPreviewPosition(this.animatedValue, previewWidth, previewHeight, slideBetweenAnimation), - ]; - - const previewStyles = [flex, getPreviewScale(this.animatedValue, slideBetweenAnimation)]; - - return ( - - - - - - - - - {tabOpen !== PREVIEW ? ( - - ) : null} - - - - - - - - - - - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/bar.tsx b/app/react-native/src/preview/components/OnDeviceUI/navigation/bar.tsx deleted file mode 100644 index 6116c006d7ab..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/bar.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { PureComponent } from 'react'; -import styled from '@emotion/native'; -import Button from './button'; -import { NAVIGATOR, PREVIEW, ADDONS } from './constants'; - -const Container = styled.View(({ theme }) => ({ - flexDirection: 'row', - paddingHorizontal: 8, - backgroundColor: theme.backgroundColor, - borderTopWidth: 1, - borderBottomWidth: 1, - borderColor: theme.borderColor, -})); - -export interface Props { - index: number; - onPress: (id: number) => void; -} - -export default class Bar extends PureComponent { - render() { - const { index, onPress } = this.props; - return ( - - - - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx b/app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx deleted file mode 100644 index ccd90ae02937..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/button.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { PureComponent } from 'react'; -import { TouchableOpacity } from 'react-native'; -import styled from '@emotion/native'; - -const ActiveBorder = styled.View<{ active: boolean }>(({ active, theme }) => ({ - backgroundColor: active ? theme.borderColor : 'transparent', - height: 3, -})); - -const ButtonText = styled.Text<{ active: boolean }>(({ theme, active }) => ({ - color: active ? theme.buttonActiveTextColor : theme.buttonTextColor, - paddingHorizontal: 8, - paddingVertical: 10, - fontSize: 11, -})); - -interface Props { - id: number | string; - active: boolean; - onPress: (id: number | string) => void; -} - -export default class Button extends PureComponent { - onPress = () => { - const { onPress, id } = this.props; - onPress(id); - }; - - render() { - const { active, children } = this.props; - - return ( - - {children} - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/constants.ts b/app/react-native/src/preview/components/OnDeviceUI/navigation/constants.ts deleted file mode 100644 index 7c8f4bcbafeb..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const NAVIGATOR = -1; -export const PREVIEW = 0; -export const ADDONS = 1; diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/index.tsx b/app/react-native/src/preview/components/OnDeviceUI/navigation/index.tsx deleted file mode 100644 index 72ef1b584bda..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable react/destructuring-assignment */ -import React, { PureComponent } from 'react'; -import { View, SafeAreaView } from 'react-native'; -import GestureRecognizer from 'react-native-swipe-gestures'; -import Bar from './bar'; -import VisibilityButton from './visibility-button'; - -const SWIPE_CONFIG = { - velocityThreshold: 0.2, - directionalOffsetThreshold: 80, -}; - -interface Props { - initialUiVisible?: boolean; - tabOpen: number; - onChangeTab: (index: number) => void; -} - -export default class Navigation extends PureComponent { - state = { - isUIVisible: this.props.initialUiVisible !== undefined ? this.props.initialUiVisible : true, - }; - - handleToggleUI = () => { - const { isUIVisible } = this.state; - - this.setState({ isUIVisible: !isUIVisible }); - }; - - handleSwipeLeft = () => { - const { tabOpen, onChangeTab } = this.props; - if (tabOpen < 1) { - onChangeTab(tabOpen + 1); - } - }; - - handleSwipeRight = () => { - const { tabOpen, onChangeTab } = this.props; - if (tabOpen > -1) { - onChangeTab(tabOpen - 1); - } - }; - - render() { - const { tabOpen, onChangeTab } = this.props; - const { isUIVisible } = this.state; - - return ( - - - {isUIVisible && ( - - - - )} - - - - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx b/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx deleted file mode 100644 index 57c4c3923f38..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/navigation/visibility-button.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { PureComponent } from 'react'; -import styled from '@emotion/native'; - -interface Props { - onPress: () => void; -} - -const Touchable = styled.TouchableOpacity({ - backgroundColor: 'transparent', - position: 'absolute', - right: 8, - bottom: 12, - zIndex: 100, -}); - -const HideIcon = styled.Text(({ theme }) => ({ - fontSize: 14, - color: theme.buttonTextColor, -})); - -export default class VisibilityButton extends PureComponent { - render() { - const { onPress } = this.props; - return ( - - - - ); - } -} diff --git a/app/react-native/src/preview/components/OnDeviceUI/panel.tsx b/app/react-native/src/preview/components/OnDeviceUI/panel.tsx deleted file mode 100644 index 63520c02c57d..000000000000 --- a/app/react-native/src/preview/components/OnDeviceUI/panel.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { PureComponent } from 'react'; -import { StyleSheet, Animated } from 'react-native'; -import styled from '@emotion/native'; - -const Container = styled(Animated.View)(({ theme }) => ({ - backgroundColor: theme.backgroundColor, -})); - -interface Props { - style: any[]; -} - -export default class Panel extends PureComponent { - render() { - const { children, style } = this.props; - return {children}; - } -} diff --git a/app/react-native/src/preview/components/Shared/text.ts b/app/react-native/src/preview/components/Shared/text.ts deleted file mode 100644 index 20db572032e9..000000000000 --- a/app/react-native/src/preview/components/Shared/text.ts +++ /dev/null @@ -1,22 +0,0 @@ -import styled from '@emotion/native'; - -export const Header = styled.Text<{ selected: boolean }>( - ({ theme }) => ({ - fontSize: 20, - color: theme.headerTextColor, - }), - ({ selected }) => (selected ? { fontWeight: 'bold' } : {}) -); - -export const Name = styled.Text<{ selected: boolean }>( - ({ theme }) => ({ - fontSize: 16, - color: theme.headerTextColor, - }), - ({ selected }) => (selected ? { fontWeight: 'bold' } : {}) -); - -export const Label = styled.Text(({ theme }) => ({ - fontSize: 18, - color: theme.labelColor, -})); diff --git a/app/react-native/src/preview/components/Shared/theme.ts b/app/react-native/src/preview/components/Shared/theme.ts deleted file mode 100644 index 3c762a7c5b49..000000000000 --- a/app/react-native/src/preview/components/Shared/theme.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const theme = { - backgroundColor: 'white', - headerTextColor: 'black', - labelColor: 'black', - borderColor: '#e6e6e6', - previewBorderColor: '#b3b3b3', - buttonTextColor: '#999999', - buttonActiveTextColor: '#444444', -}; diff --git a/app/react-native/src/preview/components/StoryListView/index.tsx b/app/react-native/src/preview/components/StoryListView/index.tsx deleted file mode 100644 index df1a202ee6dc..000000000000 --- a/app/react-native/src/preview/components/StoryListView/index.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React, { Component, FunctionComponent } from 'react'; -import { SafeAreaView } from 'react-native'; -import styled from '@emotion/native'; -import Events from '@storybook/core-events'; -import addons from '@storybook/addons'; -import { Header, Name } from '../Shared/text'; - -const SearchBar = styled.TextInput( - { - borderTopLeftRadius: 5, - borderTopRightRadius: 5, - borderBottomLeftRadius: 5, - borderBottomRightRadius: 5, - fontSize: 16, - marginHorizontal: 5, - marginVertical: 5, - paddingHorizontal: 5, - paddingVertical: 5, - }, - ({ theme }) => ({ - backgroundColor: theme.borderColor, - color: theme.buttonActiveTextColor, - }) -); - -const HeaderContainer = styled.View({ - paddingVertical: 5, -}); - -interface SectionProps { - title: string; - selected: boolean; -} - -const SectionHeader: FunctionComponent = ({ title, selected }: SectionProps) => ( - -
{title}
-
-); - -interface ListItemProps { - title: string; - kind: string; - selected: boolean; - onPress: () => void; -} - -const ItemTouchable = styled.TouchableOpacity({ - paddingHorizontal: 16, - paddingVertical: 5, -}); - -const ListItem: FunctionComponent = ({ kind, title, selected, onPress }) => ( - - {title} - -); - -interface Props { - stories: any; -} - -interface State { - data: any[]; - originalData: any[]; -} - -const List = styled.SectionList({ - flex: 1, - marginBottom: 40, -}); - -export default class StoryListView extends Component { - constructor(props: Props) { - super(props); - - this.state = { - data: [], - originalData: [], - }; - } - - componentDidMount() { - const channel = addons.getChannel(); - channel.on(Events.STORY_ADDED, this.handleStoryAdded); - channel.on(Events.SELECT_STORY, this.forceReRender); - this.handleStoryAdded(); - } - - componentWillUnmount() { - const channel = addons.getChannel(); - channel.removeListener(Events.STORY_ADDED, this.handleStoryAdded); - channel.removeListener(Events.SELECT_STORY, this.forceReRender); - } - - forceReRender = () => { - this.forceUpdate(); - }; - - handleStoryAdded = () => { - const { stories } = this.props; - - if (stories) { - const data = Object.values( - stories - .raw() - .reduce((acc: { [kind: string]: { title: string; data: any[] } }, story: any) => { - acc[story.kind] = { - title: story.kind, - data: (acc[story.kind] ? acc[story.kind].data : []).concat(story), - }; - - return acc; - }, {}) - ); - - this.setState({ data, originalData: data }); - } - }; - - handleChangeSearchText = (text: string) => { - const query = text.trim(); - const { originalData: data } = this.state; - - if (!query) { - this.setState({ data }); - return; - } - - const checkValue = (value: string) => value.toLowerCase().includes(query.toLowerCase()); - const filteredData = data.reduce((acc, story) => { - const hasTitle = checkValue(story.title); - const hasKind = story.data.some((ref: any) => checkValue(ref.name)); - - if (hasTitle || hasKind) { - acc.push({ - ...story, - // in case the query matches component's title, all of its stories will be shown - data: !hasTitle ? story.data.filter((ref: any) => checkValue(ref.name)) : story.data, - }); - } - - return acc; - }, []); - - this.setState({ data: filteredData }); - }; - - changeStory(storyId: string) { - const channel = addons.getChannel(); - channel.emit(Events.SET_CURRENT_STORY, { storyId }); - } - - render() { - const { stories } = this.props; - const { storyId } = stories.getSelection(); - const selectedStory = stories.fromId(storyId); - const { data } = this.state; - - return ( - - - ( - this.changeStory(item.id)} - /> - )} - renderSectionHeader={({ section: { title } }) => ( - - )} - keyExtractor={(item, index) => item + index} - sections={data} - stickySectionHeadersEnabled={false} - /> - - ); - } -} diff --git a/app/react-native/src/preview/components/StoryView/index.tsx b/app/react-native/src/preview/components/StoryView/index.tsx deleted file mode 100644 index ffdfc229c5c1..000000000000 --- a/app/react-native/src/preview/components/StoryView/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { Component } from 'react'; -import { View, Text } from 'react-native'; -import styled from '@emotion/native'; -import addons from '@storybook/addons'; -import Events from '@storybook/core-events'; - -interface Props { - stories: any; - url: string; - onDevice?: boolean; -} - -const HelpContainer = styled.View` - flex: 1; - padding-horizontal: 15; - padding-vertical: 15; - align-items: center; - justify-content: center; -`; - -export default class StoryView extends Component { - componentDidMount() { - const channel = addons.getChannel(); - channel.on(Events.STORY_RENDER, this.forceReRender); - channel.on(Events.FORCE_RE_RENDER, this.forceReRender); - } - - componentDidUpdate() { - const channel = addons.getChannel(); - const { stories } = this.props; - const { storyId } = stories.getSelection(); - - if (storyId) { - channel.emit(Events.STORY_RENDERED, { storyId }); - } - } - - componentWillUnmount() { - const channel = addons.getChannel(); - channel.removeListener(Events.STORY_RENDER, this.forceReRender); - channel.removeListener(Events.FORCE_RE_RENDER, this.forceReRender); - } - - forceReRender = () => { - this.forceUpdate(); - }; - - renderHelp = () => { - const { url } = this.props; - return ( - - {url && url.length ? ( - - Please open the Storybook UI ({url}) with a web browser and select a story for preview. - - ) : ( - - Please open the Storybook UI with a web browser and select a story for preview. - - )} - - ); - }; - - renderOnDeviceUIHelp = () => ( - - Please open navigator and select a story to preview. - - ); - - render() { - const { onDevice, stories } = this.props; - const { storyId } = stories.getSelection(); - const story = stories.fromId(storyId); - - if (story && story.storyFn) { - const { id, storyFn } = story; - return ( - - {storyFn()} - - ); - } - - if (onDevice) { - return this.renderOnDeviceUIHelp(); - } - - return this.renderHelp(); - } -} diff --git a/app/react-native/src/preview/index.tsx b/app/react-native/src/preview/index.tsx deleted file mode 100644 index d39ccc675b7f..000000000000 --- a/app/react-native/src/preview/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import React, { PureComponent } from 'react'; -import { ThemeProvider } from 'emotion-theming'; - -import addons from '@storybook/addons'; -import Events from '@storybook/core-events'; -import Channel from '@storybook/channels'; -import createChannel from '@storybook/channel-websocket'; -import { StoryStore, ClientApi } from '@storybook/client-api'; -import OnDeviceUI from './components/OnDeviceUI'; -import StoryView from './components/StoryView'; -import { theme } from './components/Shared/theme'; -// @ts-ignore -import getHost from './rn-host-detect'; - -const STORAGE_KEY = 'lastOpenedStory'; - -interface AsyncStorage { - getItem: (key: string) => Promise; - setItem: (key: string, value: T) => Promise; -} - -export type Params = { - onDeviceUI: boolean; - asyncStorage?: AsyncStorage | null; - resetStorybook: boolean; - disableWebsockets: boolean; - query: string; - host: string; - port: number; - secured: boolean; - initialSelection: any; - shouldPersistSelection: boolean; - tabOpen: number; - isUIHidden: boolean; - shouldDisableKeyboardAvoidingView: boolean; - keyboardAvoidingViewVerticalOffset: number; -} & { theme: typeof theme }; - -export default class Preview { - _clientApi: ClientApi; - - _stories: StoryStore; - - _addons: any; - - _decorators: any[]; - - _asyncStorageStoryId: string; - - _asyncStorage: AsyncStorage | null; - - constructor() { - this._addons = {}; - this._decorators = []; - this._stories = new StoryStore({ channel: null }); - this._clientApi = new ClientApi({ storyStore: this._stories }); - } - - api = () => { - return this._clientApi; - }; - - configure = (loadStories: () => void, module: any) => { - loadStories(); - if (module && module.hot) { - module.hot.accept(() => this._sendSetStories()); - // TODO remove all global decorators on dispose - } - }; - - getStorybookUI = (params: Partial = {}) => { - let webUrl: string = null; - let channel: Channel = null; - - if (params.asyncStorage === undefined) { - console.warn( - ` -Starting Storybook v5.3.0, we require to manually pass an asyncStorage prop. Pass null to disable or use one from @react-native-community or react-native itself. - -More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-native-async-storage - `.trim() - ); - } - - if (params.asyncStorage) { - this._asyncStorage = params.asyncStorage; - } - - const onDeviceUI = params.onDeviceUI !== false; - const { initialSelection, shouldPersistSelection } = params; - - try { - channel = addons.getChannel(); - } catch (e) { - // getChannel throws if the channel is not defined, - // which is fine in this case (we will define it below) - } - - if (!channel || params.resetStorybook) { - if (onDeviceUI && params.disableWebsockets) { - channel = new Channel({ async: true }); - this._setInitialStory(initialSelection, shouldPersistSelection); - } else { - const host = getHost(params.host || 'localhost'); - const port = `:${params.port || 7007}`; - - const query = params.query || ''; - const { secured } = params; - const websocketType = secured ? 'wss' : 'ws'; - const httpType = secured ? 'https' : 'http'; - - const url = `${websocketType}://${host}${port}/${query}`; - webUrl = `${httpType}://${host}${port}`; - channel = createChannel({ - url, - async: onDeviceUI, - onError: () => { - this._setInitialStory(initialSelection, shouldPersistSelection); - }, - }); - } - - addons.setChannel(channel); - this._stories.setChannel(channel); - - channel.emit(Events.CHANNEL_CREATED); - } - - channel.on(Events.GET_STORIES, () => this._sendSetStories()); - channel.on(Events.SET_CURRENT_STORY, d => this._selectStoryEvent(d)); - - this._sendSetStories(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const preview = this; - - addons.loadAddons(this._clientApi); - - const appliedTheme = { ...theme, ...params.theme }; - - // react-native hot module loader must take in a Class - https://github.com/facebook/react-native/issues/10991 - return class StorybookRoot extends PureComponent { - render() { - if (onDeviceUI) { - return ( - - - - ); - } - - return ( - - - - ); - } - }; - }; - - _sendSetStories() { - const channel = addons.getChannel(); - const stories = this._stories.extract(); - channel.emit(Events.SET_STORIES, { stories }); - channel.emit(Events.STORIES_CONFIGURED); - } - - _setInitialStory = async (initialSelection: any, shouldPersistSelection = true) => { - const story = await this._getInitialStory(initialSelection, shouldPersistSelection)(); - - if (story) { - this._selectStory(story); - } - }; - - _getInitialStory = (initialSelection: any, shouldPersistSelection = true) => async () => { - let story = null; - if (initialSelection && this._checkStory(initialSelection)) { - story = initialSelection; - } else if (shouldPersistSelection) { - try { - let value = this._asyncStorageStoryId; - if (!value && this._asyncStorage) { - value = JSON.parse(await this._asyncStorage.getItem(STORAGE_KEY)); - this._asyncStorageStoryId = value; - } - - if (this._checkStory(value)) { - story = value; - } - } catch (e) { - // - } - } - - if (story) { - return this._getStory(story); - } - - const stories = this._stories.raw(); - if (stories && stories.length) { - return this._getStory(stories[0].id); - } - - return null; - }; - - _getStory(storyId: string) { - return this._stories.fromId(storyId); - } - - _selectStoryEvent({ storyId }: { storyId: string }) { - if (storyId) { - if (this._asyncStorage) { - this._asyncStorage.setItem(STORAGE_KEY, JSON.stringify(storyId)).catch(() => {}); - } - - const story = this._getStory(storyId); - this._selectStory(story); - } - } - - _selectStory(story: any) { - const channel = addons.getChannel(); - - this._stories.setSelection({ storyId: story.id, viewMode: 'story' }, null); - channel.emit(Events.SELECT_STORY, story); - } - - _checkStory(storyId: string) { - if (!storyId) { - return null; - } - - const story = this._getStory(storyId); - - if (story.storyFn === null) { - return null; - } - - return story; - } -} diff --git a/app/react-native/src/preview/rn-host-detect.js b/app/react-native/src/preview/rn-host-detect.js deleted file mode 100644 index 15f051f804b5..000000000000 --- a/app/react-native/src/preview/rn-host-detect.js +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable */ - -'use strict' - -/* - * It only for Debug Remotely mode for Android - * When __DEV__ === false, we can't use window.require('NativeModules') - */ -function getByRemoteConfig(hostname) { - var remoteModuleConfig = typeof window !== 'undefined' && - window.__fbBatchedBridgeConfig && - window.__fbBatchedBridgeConfig.remoteModuleConfig - if ( - !Array.isArray(remoteModuleConfig) || - hostname !== 'localhost' && hostname !== '127.0.0.1' - ) return { hostname: hostname, passed: false } - - var constants = ( - remoteModuleConfig.find(getConstants) || [] - )[1] - if (constants) { - var serverHost = constants.ServerHost || hostname - return { hostname: serverHost.split(':')[0], passed: true } - } - return { hostname: hostname, passed: false } -} - -function getConstants(config) { - return config && (config[0] === 'AndroidConstants' || config[0] === 'PlatformConstants') -} - -function getByRNRequirePolyfill(hostname) { - var NativeModules - var PlatformConstants - var AndroidConstants - if ( - typeof window === 'undefined' || - !window.__DEV__ || - typeof window.require !== 'function' || - // RN >= 0.56 - // TODO: Get NativeModules for RN >= 0.56 - window.require.name === 'metroRequire' - ) { - return hostname - } - NativeModules = window.require('NativeModules') - - if ( - !NativeModules || - (!NativeModules.PlatformConstants && !NativeModules.AndroidConstants) - ) { - return hostname - } - PlatformConstants = NativeModules.PlatformConstants - AndroidConstants = NativeModules.AndroidConstants - - var serverHost = (PlatformConstants ? - PlatformConstants.ServerHost : - AndroidConstants.ServerHost - ) || hostname - return serverHost.split(':')[0] -} - -/* - * Get React Native server IP if hostname is `localhost` - * On Android emulator, the IP of host is `10.0.2.2` (Genymotion: 10.0.3.2) - */ -export default function getHost(hostname) { - // Check if it in React Native environment - if ( - typeof __fbBatchedBridge !== 'object' || - hostname !== 'localhost' && hostname !== '127.0.0.1' - ) { - return hostname - } - var result = getByRemoteConfig(hostname) - - // Leave if get hostname by remote config successful - if (result.passed) { - return result.hostname - } - - // Otherwise, use RN's require polyfill - return getByRNRequirePolyfill(hostname) -} diff --git a/app/react-native/src/typings.d.ts b/app/react-native/src/typings.d.ts deleted file mode 100644 index 4f33c1f29b7d..000000000000 --- a/app/react-native/src/typings.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -import React, { Component } from 'react'; -import css from '@emotion/css'; -import { - CreateStyled, - CreateStyledComponentExtrinsic, -} from '@emotion/styled-base'; -import ReactNative from 'react-native'; -import { theme } from './preview/components/Shared/theme' - -// https://github.com/emotion-js/emotion/pull/1176/ -// meanwhile: https://github.com/emotion-js/emotion/issues/839#issuecomment-500195354 -declare module '@emotion/native' { - type StyledReactNativeComponents = - | 'ActivityIndicator' - | 'ActivityIndicatorIOS' - | 'ART' - | 'Button' - | 'DatePickerIOS' - | 'DrawerLayoutAndroid' - | 'Image' - | 'ImageBackground' - | 'ImageEditor' - | 'ImageStore' - | 'KeyboardAvoidingView' - | 'ListView' - | 'MapView' - | 'Modal' - | 'NavigatorIOS' - | 'Picker' - | 'PickerIOS' - | 'ProgressBarAndroid' - | 'ProgressViewIOS' - | 'ScrollView' - | 'SegmentedControlIOS' - | 'Slider' - | 'SliderIOS' - | 'SnapshotViewIOS' - | 'Switch' - | 'RecyclerViewBackedScrollView' - | 'RefreshControl' - | 'SafeAreaView' - | 'StatusBar' - | 'SwipeableListView' - | 'SwitchAndroid' - | 'SwitchIOS' - | 'TabBarIOS' - | 'Text' - | 'TextInput' - | 'ToastAndroid' - | 'ToolbarAndroid' - | 'Touchable' - | 'TouchableHighlight' - | 'TouchableNativeFeedback' - | 'TouchableOpacity' - | 'TouchableWithoutFeedback' - | 'View' - | 'ViewPagerAndroid' - | 'WebView' - | 'FlatList' - | 'SectionList' - | 'VirtualizedList'; - - type StyledComponentsForReactNative< - T extends keyof typeof ReactNative, - ExtraProps, - Theme - > = { - [K in T]: CreateStyledComponentExtrinsic< - typeof ReactNative[K], - ExtraProps, - Theme - >; - }; - - type MyTheme = typeof theme; - - export interface Styled - extends CreateStyled, - StyledComponentsForReactNative< - StyledReactNativeComponents, - ExtraProps, - Theme - > {} - - export {css}; - - const styled: Styled; - export default styled; -} \ No newline at end of file diff --git a/app/react-native/tsconfig.json b/app/react-native/tsconfig.json deleted file mode 100644 index 6a4f8e20e1b7..000000000000 --- a/app/react-native/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "./src", - "paths": { - "@emotion/native": ["src/typings.d.ts"] - } - }, - "include": ["src/**/*"], - "exclude": ["src/__tests__/**/*"] -} diff --git a/app/react/README.md b/app/react/README.md index 5576862408b1..c3958dd00321 100644 --- a/app/react/README.md +++ b/app/react/README.md @@ -35,7 +35,9 @@ This preset enables support for all Create React App features, including Sass/SC ## Typescript -If you are using Typescript, make sure you have the type definitions installed via `yarn add @types/node @types/react @types/storybook__react --dev`. +`@storybook/react` is now exporting its own types to use with Typescript. +You don't need to have `@types/storybook__react` installed anymore if it was your case. +But you probably also need to use types from `@types/node @types/react`. ## Docs diff --git a/app/react/package.json b/app/react/package.json index 6963b134ee96..6f936521b67a 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/react" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,40 +22,47 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/plugin-transform-react-constant-elements": "^7.2.0", + "@babel/plugin-transform-react-constant-elements": "^7.6.3", "@babel/preset-flow": "^7.0.0", "@babel/preset-react": "^7.0.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@storybook/node-logger": "5.3.0-rc.0", - "@svgr/webpack": "^4.0.3", - "@types/webpack-env": "^1.13.7", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@storybook/node-logger": "6.0.0-beta.21", + "@svgr/webpack": "^5.4.0", + "@types/webpack-env": "^1.15.2", "babel-plugin-add-react-displayname": "^0.0.5", "babel-plugin-named-asset-import": "^0.3.1", - "babel-plugin-react-docgen": "^4.0.0-beta.1", + "babel-plugin-react-docgen": "^4.1.0", "core-js": "^3.0.1", "global": "^4.3.2", "lodash": "^4.17.15", - "mini-css-extract-plugin": "^0.8.0", "prop-types": "^15.7.2", - "react-dev-utils": "^9.0.0", + "react-dev-utils": "^10.0.0", + "react-docgen-typescript-loader": "^3.7.2", "regenerator-runtime": "^0.13.3", - "semver": "^6.0.0", - "ts-dedent": "^1.1.0", - "webpack": "^4.33.0" + "@storybook/semver": "^7.3.2", + "ts-dedent": "^1.1.1", + "webpack": "^4.43.0" }, "devDependencies": { - "@types/mini-css-extract-plugin": "^0.8.0", - "@types/node": "^12.12.11", - "@types/webpack": "^4.41.0" + "@storybook/client-api": "6.0.0-beta.21", + "@types/node": "^14.0.10", + "@types/webpack": "^4.41.12" }, "peerDependencies": { "@babel/core": "^7.0.1", - "babel-loader": "^7.0.0 || ^8.0.0", "react": "*", "react-dom": "*" }, @@ -72,5 +72,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/react/src/client/preview/index.test.ts b/app/react/src/client/preview/index.test.ts index bd25fc3ca828..6bd053669e7e 100644 --- a/app/react/src/client/preview/index.test.ts +++ b/app/react/src/client/preview/index.test.ts @@ -18,7 +18,7 @@ describe('preview', () => { () => ({ document: undefined, - window: undefined, + window: {}, } as any) ); const api = require('.'); diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 8f4d897f9d4a..f7fe1ccc84c8 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -1,12 +1,13 @@ import { document } from 'global'; -import React, { Component, ReactElement, StrictMode } from 'react'; +import React, { Component, FunctionComponent, ReactElement, StrictMode } from 'react'; import ReactDOM from 'react-dom'; -import { RenderMainArgs } from './types'; + +import { RenderContext } from './types'; const rootEl = document ? document.getElementById('root') : null; const render = (node: ReactElement, el: Element) => - new Promise(resolve => { + new Promise((resolve) => { ReactDOM.render( process.env.STORYBOOK_EXAMPLE_APP ? {node} : node, el, @@ -47,11 +48,14 @@ class ErrorBoundary extends Component<{ } export default async function renderMain({ - storyFn: StoryFn, + storyFn, showMain, showException, forceRender, -}: RenderMainArgs) { +}: RenderContext) { + // storyFn has context bound in by now so can be treated as a function component with no args + const StoryFn = storyFn as FunctionComponent; + const element = ( diff --git a/app/react/src/client/preview/types.ts b/app/react/src/client/preview/types.ts index 1b85b49c44ec..77efa5ee92e5 100644 --- a/app/react/src/client/preview/types.ts +++ b/app/react/src/client/preview/types.ts @@ -1,20 +1,13 @@ -import { FunctionComponent, ReactElement } from 'react'; +import { ReactElement } from 'react'; + +// eslint-disable-next-line import/no-extraneous-dependencies +export { RenderContext } from '@storybook/client-api'; export interface ShowErrorArgs { title: string; description: string; } -export interface RenderMainArgs { - storyFn: FunctionComponent; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - showException: (err: Error) => void; - forceRender: boolean; -} - export type StoryFnReactReturnType = ReactElement; export interface IStorybookStory { diff --git a/app/react/src/demo/Welcome.tsx b/app/react/src/demo/Welcome.tsx index e941571d3240..2d5d35e5000a 100644 --- a/app/react/src/demo/Welcome.tsx +++ b/app/react/src/demo/Welcome.tsx @@ -5,10 +5,9 @@ import React, { FunctionComponent, HTMLAttributes, } from 'react'; -import PropTypes from 'prop-types'; type MainProps = Omit, HTMLElement>, 'style'>; -const Main: FunctionComponent = props => ( +const Main: FunctionComponent = (props) => (
, HTMLHead const Title: FunctionComponent = ({ children, ...props }) => (

{children}

); -Title.propTypes = { - children: PropTypes.node, -}; -Title.defaultProps = { - children: undefined, -}; type NoteProps = Omit< DetailedHTMLProps, HTMLParagraphElement>, 'style' >; -const Note: FunctionComponent = props => ( +const Note: FunctionComponent = (props) => (

= props => ( ); type InlineCodeProps = Omit, HTMLElement>, 'style'>; -const InlineCode: FunctionComponent = props => ( +const InlineCode: FunctionComponent = (props) => ( = ({ children, href, target, rel, ...pr {children} ); -Link.propTypes = { - href: PropTypes.string, - children: PropTypes.node, -}; -Link.defaultProps = { - href: undefined, - children: undefined, -}; type NavButtonProps = Omit< DetailedHTMLProps, HTMLButtonElement>, @@ -120,16 +105,10 @@ const NavButton: FunctionComponent = ({ children, onClick, ...pr {children} ); -NavButton.propTypes = { - children: PropTypes.node, -}; -NavButton.defaultProps = { - children: undefined, -}; -type WelcomeProps = { +interface WelcomeProps { showApp: () => void; -}; +} const Welcome: FunctionComponent = ({ showApp }) => (

Welcome to storybook @@ -151,7 +130,7 @@ const Welcome: FunctionComponent = ({ showApp }) => ( You can also edit those components and see changes right away.
(Try editing the Button stories located at  - src/stories/index.js + src/stories/1-Button.stories.js .)

@@ -176,8 +155,5 @@ const Welcome: FunctionComponent = ({ showApp }) => (

); Welcome.displayName = 'Welcome'; -Welcome.defaultProps = { - showApp: null, -}; export { Welcome as default }; diff --git a/app/react/src/server/__mocks__/mockConfig.ts b/app/react/src/server/__mocks__/mockConfig.ts deleted file mode 100644 index 34b2b5df9687..000000000000 --- a/app/react/src/server/__mocks__/mockConfig.ts +++ /dev/null @@ -1,25 +0,0 @@ -class BaseTestPlugin1 {} -class BaseTestPlugin2 {} - -export default { - devtool: 'cheap-eval-source-map', - resolve: { - extensions: ['.js', '.jsx'], - alias: { - baseAlias: 'base-alias', - }, - modules: [], - }, - module: { - noParse: /jquery/, - rules: [ - { - test: /\.js$/, - include: 'app/baseSrc', - loader: 'babel-loader', - options: {}, - }, - ], - }, - plugins: [new BaseTestPlugin1(), new BaseTestPlugin2()], -}; diff --git a/app/react/src/server/__mocks__/mockRules.ts b/app/react/src/server/__mocks__/mockRules.ts deleted file mode 100644 index 3c43a333672b..000000000000 --- a/app/react/src/server/__mocks__/mockRules.ts +++ /dev/null @@ -1,150 +0,0 @@ -export default [ - { parser: { requireEnsure: false } }, - { - test: /\.(js|mjs|jsx|ts|tsx)$/, - enforce: 'pre', - use: [ - { - options: { - formatter: '/mock_folder/node_modules/react-dev-utils/eslintFormatter.js', - eslintPath: '/mock_folder/node_modules/eslint/lib/api.js', - baseConfig: { - extends: ['/mock_folder/node_modules/eslint-config-react-app/index.js'], - }, - ignore: false, - useEslintrc: false, - }, - loader: '/mock_folder/node_modules/eslint-loader/index.js', - }, - ], - include: '/mock_folder/src', - }, - { - oneOf: [ - { - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: '/mock_folder/node_modules/url-loader/dist/cjs.js', - options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }, - }, - { - test: /\.(js|mjs|jsx|ts|tsx)$/, - include: '/mock_folder/src', - loader: '/mock_folder/node_modules/babel-loader/lib/index.js', - options: { - customize: '/mock_folder/node_modules/babel-preset-react-app/webpack-overrides.js', - babelrc: false, - configFile: false, - presets: ['/mock_folder/node_modules/babel-preset-react-app/index.js'], - cacheIdentifier: - 'development:babel-plugin-named-asset-import@0.2.3:babel-preset-react-app@6.1.0:react-dev-utils@6.1.1:react-scripts@', - plugins: [ - [ - '/mock_folder/node_modules/babel-plugin-named-asset-import/index.js', - { - loaderMap: { - svg: { - ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]', - }, - }, - }, - ], - ], - cacheDirectory: true, - cacheCompression: false, - compact: false, - }, - }, - { - test: /\.(js|mjs)$/, - exclude: {}, - loader: '/mock_folder/node_modules/babel-loader/lib/index.js', - options: { - babelrc: false, - configFile: false, - compact: false, - presets: [ - ['/mock_folder/node_modules/babel-preset-react-app/dependencies.js', { helpers: true }], - ], - cacheDirectory: true, - cacheCompression: false, - cacheIdentifier: - 'development:babel-plugin-named-asset-import@0.2.3:babel-preset-react-app@6.1.0:react-dev-utils@6.1.1:react-scripts@', - sourceMaps: false, - }, - }, - { - test: /\.css$/, - exclude: {}, - use: [ - '/mock_folder/node_modules/bmr-react-scripts/node_modules/style-loader/index.js', - { - loader: '/mock_folder/node_modules/bmr-react-scripts/node_modules/css-loader/index.js', - options: { importLoaders: 1, sourceMap: false }, - }, - { - loader: '/mock_folder/node_modules/postcss-loader/src/index.js', - options: { ident: 'postcss', sourceMap: false }, - }, - ], - sideEffects: true, - }, - { - test: /\.module\.css$/, - use: [ - '/mock_folder/node_modules/bmr-react-scripts/node_modules/style-loader/index.js', - { - loader: '/mock_folder/node_modules/bmr-react-scripts/node_modules/css-loader/index.js', - options: { importLoaders: 1, sourceMap: false, modules: true }, - }, - { - loader: '/mock_folder/node_modules/postcss-loader/src/index.js', - options: { ident: 'postcss', sourceMap: false }, - }, - ], - }, - { - test: /\.(scss|sass)$/, - exclude: {}, - use: [ - '/mock_folder/node_modules/bmr-react-scripts/node_modules/style-loader/index.js', - { - loader: '/mock_folder/node_modules/bmr-react-scripts/node_modules/css-loader/index.js', - options: { importLoaders: 2, sourceMap: false }, - }, - { - loader: '/mock_folder/node_modules/postcss-loader/src/index.js', - options: { ident: 'postcss', sourceMap: false }, - }, - { - loader: '/mock_folder/node_modules/sass-loader/lib/loader.js', - options: { sourceMap: false }, - }, - ], - sideEffects: true, - }, - { - test: /\.module\.(scss|sass)$/, - use: [ - '/mock_folder/node_modules/bmr-react-scripts/node_modules/style-loader/index.js', - { - loader: '/mock_folder/node_modules/bmr-react-scripts/node_modules/css-loader/index.js', - options: { importLoaders: 2, sourceMap: false, modules: true }, - }, - { - loader: '/mock_folder/node_modules/postcss-loader/src/index.js', - options: { ident: 'postcss', sourceMap: false }, - }, - { - loader: '/mock_folder/node_modules/sass-loader/lib/loader.js', - options: { sourceMap: false }, - }, - ], - }, - { - loader: '/mock_folder/node_modules/file-loader/dist/cjs.js', - exclude: [{}, {}, {}], - options: { name: 'static/media/[name].[hash:8].[ext]' }, - }, - ], - }, -]; diff --git a/app/react/src/server/__mocks__/react-scripts-2-0-0/config/webpack.config.dev.js b/app/react/src/server/__mocks__/react-scripts-2-0-0/config/webpack.config.dev.js deleted file mode 100644 index 072176e52878..000000000000 --- a/app/react/src/server/__mocks__/react-scripts-2-0-0/config/webpack.config.dev.js +++ /dev/null @@ -1,63 +0,0 @@ -const cssRegex = /\.css$/; -const cssModuleRegex = /\.module\.css$/; -const sassRegex = /\.(scss|sass)$/; -const sassModuleRegex = /\.module\.(scss|sass)$/; - -class CRATestPlugin1 {} -class CRATestPlugin2 {} - -module.exports = { - devtool: 'source-map', - resolve: { - extensions: [], - alias: { - 'react-native': 'react-native-web', - }, - }, - module: { - strictExportPresence: true, - rules: [ - { - test: /\.(js|mjs|jsx)$/, - enforce: 'pre', - use: [ - { - options: {}, - loader: 'eslint-loader', - }, - ], - include: 'app/src', - }, - { - test: /\.(js|mjs|jsx|ts|tsx)$/, - include: 'app/src', - loader: 'babel-loader', - options: {}, - }, - { - test: cssRegex, - exclude: cssModuleRegex, - use: 'style-loader', - sideEffects: true, - }, - { - test: cssModuleRegex, - use: 'style-loader', - }, - { - test: sassRegex, - exclude: sassModuleRegex, - use: 'sass-loader', - sideEffects: true, - }, - { - test: sassModuleRegex, - use: 'sass-loader', - }, - ], - }, - plugins: [new CRATestPlugin1(), new CRATestPlugin2()], - optimization: { - minimize: true, - }, -}; diff --git a/app/react/src/server/__mocks__/react-scripts-2-0-0/package.json b/app/react/src/server/__mocks__/react-scripts-2-0-0/package.json deleted file mode 100644 index c83272b54adf..000000000000 --- a/app/react/src/server/__mocks__/react-scripts-2-0-0/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "react-scripts", - "version": "2.0.0" -} diff --git a/app/react/src/server/__mocks__/react-scripts-2-1-0/config/webpack.config.dev.js b/app/react/src/server/__mocks__/react-scripts-2-1-0/config/webpack.config.dev.js deleted file mode 100644 index 072176e52878..000000000000 --- a/app/react/src/server/__mocks__/react-scripts-2-1-0/config/webpack.config.dev.js +++ /dev/null @@ -1,63 +0,0 @@ -const cssRegex = /\.css$/; -const cssModuleRegex = /\.module\.css$/; -const sassRegex = /\.(scss|sass)$/; -const sassModuleRegex = /\.module\.(scss|sass)$/; - -class CRATestPlugin1 {} -class CRATestPlugin2 {} - -module.exports = { - devtool: 'source-map', - resolve: { - extensions: [], - alias: { - 'react-native': 'react-native-web', - }, - }, - module: { - strictExportPresence: true, - rules: [ - { - test: /\.(js|mjs|jsx)$/, - enforce: 'pre', - use: [ - { - options: {}, - loader: 'eslint-loader', - }, - ], - include: 'app/src', - }, - { - test: /\.(js|mjs|jsx|ts|tsx)$/, - include: 'app/src', - loader: 'babel-loader', - options: {}, - }, - { - test: cssRegex, - exclude: cssModuleRegex, - use: 'style-loader', - sideEffects: true, - }, - { - test: cssModuleRegex, - use: 'style-loader', - }, - { - test: sassRegex, - exclude: sassModuleRegex, - use: 'sass-loader', - sideEffects: true, - }, - { - test: sassModuleRegex, - use: 'sass-loader', - }, - ], - }, - plugins: [new CRATestPlugin1(), new CRATestPlugin2()], - optimization: { - minimize: true, - }, -}; diff --git a/app/react/src/server/__mocks__/react-scripts-2-1-0/package.json b/app/react/src/server/__mocks__/react-scripts-2-1-0/package.json deleted file mode 100644 index 14f78409f092..000000000000 --- a/app/react/src/server/__mocks__/react-scripts-2-1-0/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "react-scripts", - "version": "2.1.0" -} diff --git a/app/react/src/server/__snapshots__/cra-config.test.ts.snap b/app/react/src/server/__snapshots__/cra-config.test.ts.snap deleted file mode 100644 index 73d95cb829ec..000000000000 --- a/app/react/src/server/__snapshots__/cra-config.test.ts.snap +++ /dev/null @@ -1,128 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`cra-config when used with react-scripts < 2.1.0 should apply styling webpack rules 1`] = ` -Object { - "devtool": "cheap-eval-source-map", - "module": Object { - "noParse": /jquery/, - "rules": Array [ - Object { - "include": "app/baseSrc", - "loader": "babel-loader", - "options": Object {}, - "test": /\\\\\\.js\\$/, - }, - Object { - "exclude": /\\\\\\.module\\\\\\.css\\$/, - "sideEffects": true, - "test": /\\\\\\.css\\$/, - "use": "style-loader", - }, - Object { - "test": /\\\\\\.module\\\\\\.css\\$/, - "use": "style-loader", - }, - Object { - "exclude": /\\\\\\.module\\\\\\.\\(scss\\|sass\\)\\$/, - "sideEffects": true, - "test": /\\\\\\.\\(scss\\|sass\\)\\$/, - "use": "sass-loader", - }, - Object { - "test": /\\\\\\.module\\\\\\.\\(scss\\|sass\\)\\$/, - "use": "sass-loader", - }, - ], - }, - "plugins": Array [ - BaseTestPlugin1 {}, - BaseTestPlugin2 {}, - ], - "resolve": Object { - "alias": Object { - "baseAlias": "base-alias", - }, - "extensions": Array [ - ".js", - ".jsx", - ], - "modules": Array [], - }, - "resolveLoader": Object { - "modules": Array [ - "node_modules", - "/app/react/src/server/__mocks__/react-scripts-2-0-0/node_modules", - ], - }, -} -`; - -exports[`cra-config when used with react-scripts >= 2.1.0 should apply Babel, styling rules and merge plugins 1`] = ` -Object { - "devtool": "cheap-eval-source-map", - "module": Object { - "noParse": /jquery/, - "rules": Array [ - Object { - "include": "app/baseSrc", - "loader": "babel-loader", - "options": Object {}, - "test": /\\\\\\.js\\$/, - }, - Object { - "exclude": /\\\\\\.module\\\\\\.css\\$/, - "sideEffects": true, - "test": /\\\\\\.css\\$/, - "use": "style-loader", - }, - Object { - "test": /\\\\\\.module\\\\\\.css\\$/, - "use": "style-loader", - }, - Object { - "exclude": /\\\\\\.module\\\\\\.\\(scss\\|sass\\)\\$/, - "sideEffects": true, - "test": /\\\\\\.\\(scss\\|sass\\)\\$/, - "use": "sass-loader", - }, - Object { - "test": /\\\\\\.module\\\\\\.\\(scss\\|sass\\)\\$/, - "use": "sass-loader", - }, - Object { - "include": Array [ - "app/src", - "/test-project", - ], - "loader": "babel-loader", - "options": Object {}, - "test": /\\\\\\.\\(js\\|mjs\\|jsx\\|ts\\|tsx\\)\\$/, - }, - ], - }, - "plugins": Array [ - BaseTestPlugin1 {}, - BaseTestPlugin2 {}, - CRATestPlugin1 {}, - CRATestPlugin2 {}, - ], - "resolve": Object { - "alias": Object { - "baseAlias": "base-alias", - }, - "extensions": Array [ - ".js", - ".jsx", - ".ts", - ".tsx", - ], - "modules": Array [], - }, - "resolveLoader": Object { - "modules": Array [ - "node_modules", - "/app/react/src/server/__mocks__/react-scripts-2-1-0/node_modules", - ], - }, -} -`; diff --git a/app/react/src/server/cra-config.test.ts b/app/react/src/server/cra-config.test.ts index 4c4425dd8482..c6b1a57c30ea 100644 --- a/app/react/src/server/cra-config.test.ts +++ b/app/react/src/server/cra-config.test.ts @@ -1,30 +1,18 @@ -/* eslint-disable jest/no-mocks-import */ import fs from 'fs'; -import path from 'path'; -import { - applyCRAWebpackConfig, - getModulePath, - getReactScriptsPath, - getTypeScriptRules, -} from './cra-config'; -import mockRules from './__mocks__/mockRules'; -import mockConfig from './__mocks__/mockConfig'; +import { getReactScriptsPath } from './cra-config'; jest.mock('fs', () => ({ realpathSync: jest.fn(() => '/test-project'), readFileSync: jest.fn(), existsSync: jest.fn(() => true), })); -jest.mock('mini-css-extract-plugin', () => {}); const SCRIPT_PATH = '.bin/react-scripts'; -const stripCwd = (loaderPath: string) => loaderPath.replace(process.cwd(), ''); - describe('cra-config', () => { describe('when used with the default react-scripts package', () => { beforeEach(() => { - fs.realpathSync.mockImplementationOnce(filePath => + ((fs.realpathSync as unknown) as jest.Mock).mockImplementationOnce((filePath) => filePath.replace(SCRIPT_PATH, `react-scripts/${SCRIPT_PATH}`) ); }); @@ -38,7 +26,7 @@ describe('cra-config', () => { describe('when used with a custom react-scripts package', () => { beforeEach(() => { - fs.realpathSync.mockImplementationOnce(filePath => + ((fs.realpathSync as unknown) as jest.Mock).mockImplementationOnce((filePath) => filePath.replace(SCRIPT_PATH, `custom-react-scripts/${SCRIPT_PATH}`) ); }); @@ -54,9 +42,9 @@ describe('cra-config', () => { beforeEach(() => { // In case of .bin/react-scripts is not symlink (like it happens on Windows), // realpathSync() method does not translate the path. - fs.realpathSync.mockImplementationOnce(filePath => filePath); + ((fs.realpathSync as unknown) as jest.Mock).mockImplementationOnce((filePath) => filePath); - fs.readFileSync.mockImplementationOnce( + ((fs.readFileSync as unknown) as jest.Mock).mockImplementationOnce( () => `#!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") @@ -81,66 +69,4 @@ exit $ret` ); }); }); - - describe('when used with TypeScript', () => { - it('should return the correct config', () => { - const rules = getTypeScriptRules(mockRules, './.storybook'); - expect(rules.length).toBe(2); - }); - - // Allows using TypeScript in the `.storybook` (or config) folder. - it('should add the Storybook config directory to `include` for all TS related rules', () => { - const rules = getTypeScriptRules(mockRules, './.storybook'); - expect( - rules.every(rule => rule.include.find(filePath => filePath.includes('.storybook'))) - ).toBe(true); - }); - - it('should get the baseUrl from a tsconfig.json', () => { - jest.spyOn(path, 'join').mockImplementation(() => 'project/tsconfig.json'); - jest.mock( - 'project/tsconfig.json', - () => ({ - compilerOptions: { - baseUrl: 'src', - }, - }), - { virtual: true } - ); - expect(getModulePath()).toEqual('src'); - path.join.mockRestore(); - }); - }); - - describe('when used with react-scripts < 2.1.0', () => { - beforeEach(() => { - fs.realpathSync.mockImplementationOnce(() => - path.join(__dirname, '__mocks__/react-scripts-2-0-0/sub1/sub2') - ); - getReactScriptsPath({ noCache: true }); - }); - - it('should apply styling webpack rules', () => { - const webpackConfig = applyCRAWebpackConfig(mockConfig, '/test-project'); - // We don't want full paths in snapshots. - webpackConfig.resolveLoader.modules = webpackConfig.resolveLoader.modules.map(stripCwd); - expect(webpackConfig).toMatchSnapshot(); - }); - }); - - describe('when used with react-scripts >= 2.1.0', () => { - beforeEach(() => { - fs.realpathSync.mockImplementationOnce(() => - path.join(__dirname, '__mocks__/react-scripts-2-1-0/sub1/sub2') - ); - getReactScriptsPath({ noCache: true }); - }); - - it('should apply Babel, styling rules and merge plugins', () => { - const webpackConfig = applyCRAWebpackConfig(mockConfig, '/test-project'); - // We don't want full paths in snapshots. - webpackConfig.resolveLoader.modules = webpackConfig.resolveLoader.modules.map(stripCwd); - expect(webpackConfig).toMatchSnapshot(); - }); - }); }); diff --git a/app/react/src/server/cra-config.ts b/app/react/src/server/cra-config.ts index a614d4a7f16e..ab6815c42382 100644 --- a/app/react/src/server/cra-config.ts +++ b/app/react/src/server/cra-config.ts @@ -1,18 +1,9 @@ import fs from 'fs'; import path from 'path'; -import semver from 'semver'; -import { Configuration, Plugin, RuleSetRule } from 'webpack'; -import MiniCssExtractPlugin from 'mini-css-extract-plugin'; -import { normalizeCondition } from 'webpack/lib/RuleSet'; +import semver from '@storybook/semver'; import { logger } from '@storybook/node-logger'; -const JSCONFIG = 'jsconfig.json'; -const TSCONFIG = 'tsconfig.json'; - const appDirectory = fs.realpathSync(process.cwd()); -const cssExtensions = ['.css', '.scss', '.sass']; -const cssModuleExtensions = ['.module.css', '.module.scss', '.module.sass']; -const typeScriptExtensions = ['.ts', '.tsx']; let reactScriptsPath: string; @@ -69,146 +60,3 @@ export function isReactScriptsInstalled(requiredVersion = '2.0.0') { return false; } } - -export const getRules = (extensions: string[]) => (rules: RuleSetRule[]) => - rules.reduce((craRules, rule) => { - // If at least one extension satisfies the rule test, the rule is one - // we want to extract - if (rule.test && extensions.some(normalizeCondition(rule.test))) { - // If the base test is for extensions, return early - return craRules.concat(rule); - } - - // Get any rules contained in rule.oneOf - if (!rule.test && rule.oneOf) { - craRules.push(...getRules(extensions)(rule.oneOf)); - } - - // Get any rules contained in rule.rules - if (!rule.test && rule.rules) { - craRules.push(...getRules(extensions)(rule.rules)); - } - - return craRules; - }, [] as RuleSetRule[]); - -const getStyleRules = getRules(cssExtensions.concat(cssModuleExtensions)); - -export const getTypeScriptRules = (webpackConfigRules: RuleSetRule[], configDir: string) => { - const rules = getRules(typeScriptExtensions)(webpackConfigRules); - - // Adds support for using TypeScript in the `.storybook` (or config) folder. - return rules.reduce((accRules, rule) => { - // Resolves an issue where this config is parsed twice (#4903). - if (typeof rule.include !== 'string') { - return [...accRules, rule]; - } - - return [ - ...accRules, - { - ...rule, - include: [rule.include, path.resolve(configDir)], - }, - ]; - }, [] as RuleSetRule[]); -}; - -export const getModulePath = () => { - // As with CRA, we only support `jsconfig.json` if `tsconfig.json` doesn't exist. - let configName; - if (fs.existsSync(path.join(appDirectory, TSCONFIG))) { - configName = TSCONFIG; - } else if (fs.existsSync(path.join(appDirectory, JSCONFIG))) { - configName = JSCONFIG; - } - - if (configName) { - // eslint-disable-next-line import/no-dynamic-require,global-require - const config = require(path.join(appDirectory, configName)); - return config.compilerOptions && config.compilerOptions.baseUrl; - } - return false; -}; - -function mergePlugins(basePlugins: Plugin[], additionalPlugins: Plugin[]) { - return [...basePlugins, ...additionalPlugins].reduce((plugins, plugin) => { - if ( - plugins.some(includedPlugin => includedPlugin.constructor.name === plugin.constructor.name) - ) { - return plugins; - } - return [...plugins, plugin]; - }, []); -} - -export function getCraWebpackConfig(mode: 'development' | 'production' | 'none') { - const pathToReactScripts = getReactScriptsPath(); - - const craWebpackConfig = - mode === 'production' ? 'config/webpack.config.prod.js' : 'config/webpack.config.dev.js'; - - let pathToWebpackConfig = path.join(pathToReactScripts, craWebpackConfig); - - if (!fs.existsSync(pathToWebpackConfig)) { - pathToWebpackConfig = path.join(pathToReactScripts, 'config/webpack.config.js'); - } - - // eslint-disable-next-line import/no-dynamic-require,global-require - const webpackConfig = require(pathToWebpackConfig); - - if (typeof webpackConfig === 'function') { - return webpackConfig(mode); - } - - return webpackConfig; -} - -export function applyCRAWebpackConfig(baseConfig: Configuration, configDir: string): Configuration { - // Check if the user can use TypeScript (react-scripts version 2.1+). - const hasTsSupport = isReactScriptsInstalled('2.1.0'); - - const tsExtensions = hasTsSupport ? typeScriptExtensions : []; - const extensions = [...cssExtensions, ...tsExtensions]; - - // Support for this was added in `react-scripts@3.0.0`. - // https://github.com/facebook/create-react-app/pull/6656 - const modulePath = isReactScriptsInstalled('3.0.0') && getModulePath(); - - // Remove any rules from baseConfig that test true for any one of the extensions - const filteredBaseRules = baseConfig.module.rules.filter( - rule => !rule.test || !extensions.some(normalizeCondition(rule.test)) - ); - - // Load create-react-app config - const craWebpackConfig = getCraWebpackConfig(baseConfig.mode); - - const craStyleRules = getStyleRules(craWebpackConfig.module.rules); - const craTypeScriptRules = hasTsSupport - ? getTypeScriptRules(craWebpackConfig.module.rules, configDir) - : []; - - // Add css minification for production - const plugins = [...baseConfig.plugins]; - if (baseConfig.mode === 'production') { - // @ts-ignore - plugins.push(new MiniCssExtractPlugin()); - } - - return { - ...baseConfig, - module: { - ...baseConfig.module, - rules: [...filteredBaseRules, ...craStyleRules, ...craTypeScriptRules], - }, - plugins: mergePlugins(plugins, hasTsSupport ? craWebpackConfig.plugins : []), - resolve: { - ...baseConfig.resolve, - extensions: [...baseConfig.resolve.extensions, ...tsExtensions], - modules: baseConfig.resolve.modules.concat(modulePath || []), - }, - resolveLoader: { - modules: ['node_modules', path.join(getReactScriptsPath(), 'node_modules')], - }, - }; -} diff --git a/app/react/src/server/framework-preset-cra.ts b/app/react/src/server/framework-preset-cra.ts index 6461659522b2..8239290acc53 100644 --- a/app/react/src/server/framework-preset-cra.ts +++ b/app/react/src/server/framework-preset-cra.ts @@ -1,11 +1,9 @@ -import path from 'path'; import { Configuration } from 'webpack'; import { logger } from '@storybook/node-logger'; -import { applyCRAWebpackConfig, getReactScriptsPath, isReactScriptsInstalled } from './cra-config'; +import { isReactScriptsInstalled } from './cra-config'; type Preset = string | { name: string }; -// Disable the built-in preset if the new preset is detected. const checkForNewPreset = (presetsList: Preset[]) => { const hasNewPreset = presetsList.some((preset: Preset) => { const presetName = typeof preset === 'string' ? preset : preset.name; @@ -15,63 +13,18 @@ const checkForNewPreset = (presetsList: Preset[]) => { if (!hasNewPreset) { logger.warn('Storybook support for Create React App is now a separate preset.'); logger.warn( - 'To get started with the new preset, simply add `@storybook/preset-create-react-app` to your project.' + 'To use the new preset, install `@storybook/preset-create-react-app` and add it to the list of `addons` in your `.storybook/main.js` config file.' ); - logger.warn('The built-in preset will be disabled in Storybook 6.0.'); - return false; + logger.warn('The built-in preset has been disabled in Storybook 6.0.'); } - - return true; }; export function webpackFinal( config: Configuration, { presetsList, configDir }: { presetsList: Preset[]; configDir: string } ) { - if (checkForNewPreset(presetsList)) { - return config; - } - if (!isReactScriptsInstalled()) { - logger.info('=> Using base config because react-scripts is not installed.'); - return config; - } - - logger.info('=> Loading create-react-app config.'); - return applyCRAWebpackConfig(config, configDir); -} - -export function managerWebpack(config: Configuration, { presetsList }: { presetsList: Preset[] }) { - if (!isReactScriptsInstalled() || checkForNewPreset(presetsList)) { - return config; + if (isReactScriptsInstalled()) { + checkForNewPreset(presetsList); } - - return { - ...config, - resolveLoader: { - modules: ['node_modules', path.join(getReactScriptsPath(), 'node_modules')], - }, - }; -} - -export function babelDefault(config: Configuration, { presetsList }: { presetsList: Preset[] }) { - if (!isReactScriptsInstalled() || checkForNewPreset(presetsList)) { - return config; - } - - return { - ...config, - presets: [require.resolve('babel-preset-react-app')], - plugins: [ - [ - require.resolve('babel-plugin-named-asset-import'), - { - loaderMap: { - svg: { - ReactComponent: '@svgr/webpack?-prettier,-svgo![path]', - }, - }, - }, - ], - ], - }; + return config; } diff --git a/app/react/src/server/framework-preset-react-docgen.test.ts b/app/react/src/server/framework-preset-react-docgen.test.ts index 1b1f0e27ca87..33bde8f2862e 100644 --- a/app/react/src/server/framework-preset-react-docgen.test.ts +++ b/app/react/src/server/framework-preset-react-docgen.test.ts @@ -1,76 +1,36 @@ -import { TransformOptions } from '@babel/core'; import * as preset from './framework-preset-react-docgen'; describe('framework-preset-react-docgen', () => { const babelPluginReactDocgenPath = require.resolve('babel-plugin-react-docgen'); - it('should return the config with the extra plugins when `plugins` is an array.', () => { + it('should return the config with the extra plugin', () => { const babelConfig = { babelrc: false, presets: ['env', 'foo-preset'], plugins: ['foo-plugin'], }; - const config = preset.babel(babelConfig); - - expect(config).toEqual({ - babelrc: false, - plugins: [ - 'foo-plugin', - [ - babelPluginReactDocgenPath, - { - DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', - }, - ], - ], - presets: ['env', 'foo-preset'], + const config = preset.babel(babelConfig, { + typescriptOptions: { check: false, reactDocgen: 'react-docgen' }, }); - }); - - it('should return the config with the extra plugins when `plugins` is not an array.', () => { - const babelConfig: TransformOptions = { - babelrc: false, - presets: ['env', 'foo-preset'], - plugins: ['bar-plugin'], - }; - - const config = preset.babel(babelConfig); expect(config).toEqual({ babelrc: false, - plugins: [ - 'bar-plugin', - [ - babelPluginReactDocgenPath, - { - DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', - }, - ], - ], - presets: ['env', 'foo-preset'], - }); - }); - - it('should return the config only with the extra plugins when `plugins` is not present.', () => { - const babelConfig = { - babelrc: false, + plugins: ['foo-plugin'], presets: ['env', 'foo-preset'], - }; - - const config = preset.babel(babelConfig); - - expect(config).toEqual({ - babelrc: false, - plugins: [ - [ - babelPluginReactDocgenPath, - { - DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', - }, - ], + overrides: [ + { + test: /\.(mjs|tsx?|jsx?)$/, + plugins: [ + [ + babelPluginReactDocgenPath, + { + DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', + }, + ], + ], + }, ], - presets: ['env', 'foo-preset'], }); }); }); diff --git a/app/react/src/server/framework-preset-react-docgen.ts b/app/react/src/server/framework-preset-react-docgen.ts index fae63672c360..a525c8d03f94 100644 --- a/app/react/src/server/framework-preset-react-docgen.ts +++ b/app/react/src/server/framework-preset-react-docgen.ts @@ -1,22 +1,50 @@ -import { TransformOptions } from '@babel/core'; +import type { TransformOptions } from '@babel/core'; +import type { Configuration } from 'webpack'; +import type { StorybookOptions } from '@storybook/core/types'; -export function babel(config: TransformOptions) { - // Ensure plugins are defined or fallback to an array to avoid empty values. - const babelConfigPlugins = config.plugins || []; - - const extraPlugins = [ - [ - require.resolve('babel-plugin-react-docgen'), +export function babel(config: TransformOptions, { typescriptOptions }: StorybookOptions) { + const { reactDocgen } = typescriptOptions; + if (!reactDocgen) { + return config; + } + return { + ...config, + overrides: [ { - DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', + test: reactDocgen === 'react-docgen' ? /\.(mjs|tsx?|jsx?)$/ : /\.(mjs|jsx?)$/, + plugins: [ + [ + require.resolve('babel-plugin-react-docgen'), + { + DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES', + }, + ], + ], }, ], - ]; + }; +} - // If `babelConfigPlugins` is not an `Array`, calling `concat` will inject it - // as a single value, if it is an `Array` it will spread. +export function webpackFinal(config: Configuration, { typescriptOptions }: StorybookOptions) { + const { reactDocgen, reactDocgenTypescriptOptions } = typescriptOptions; + if (reactDocgen !== 'react-docgen-typescript') return config; return { ...config, - plugins: [].concat(babelConfigPlugins, extraPlugins), + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /\.tsx?$/, + // include: path.resolve(__dirname, "../src"), + use: [ + { + loader: require.resolve('react-docgen-typescript-loader'), + options: reactDocgenTypescriptOptions, + }, + ], + }, + ], + }, }; } diff --git a/app/react/src/typings.d.ts b/app/react/src/typings.d.ts index 10c6c8e3f99c..fec12d9f09eb 100644 --- a/app/react/src/typings.d.ts +++ b/app/react/src/typings.d.ts @@ -1,4 +1,5 @@ declare module '@storybook/core/*'; +declare module '@storybook/semver'; declare module 'global'; // todo check for correct types declare module 'webpack/lib/RuleSet'; diff --git a/app/riot/package.json b/app/riot/package.json index 11f6e8f383b4..b8c6eafeb730 100644 --- a/app/riot/package.json +++ b/app/riot/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/riot", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for riot.js: View riot snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,43 +15,47 @@ "directory": "app/riot" }, "license": "MIT", + "main": "dist/client/index.js", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, "files": [ "bin/**/*", "dist/**/*", "README.md", "standalone.js", "*.js", - "*.d.ts" + "*.d.ts", + "ts3.5/**/*" ], - "main": "dist/client/index.js", - "bin": { - "build-storybook": "./bin/build.js", - "start-storybook": "./bin/index.js", - "storybook-server": "./bin/index.js" - }, "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-rc.0", + "@storybook/core": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", - "raw-loader": "^3.1.0", + "raw-loader": "^4.0.1", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/preset-env": "^7.4.1", + "@babel/plugin-transform-modules-commonjs": "^7.9.6", + "@babel/preset-env": "^7.9.6", "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.0.0" + "@babel/preset-react": "^7.7.0" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", + "@babel/core": "*", + "react": "*", + "react-dom": "*", "riot": "^3.0.0 || ^4.0.0", "riot-compiler": "^3.5.1 || ^4.0.0", "riot-hot-reload": "^1.0.0", - "riot-tag-loader": "^2.0.0 || ^3.0.0" + "riot-tag-loader": "^2.0.0 || ^3.0.0", + "webpack": "*" }, "engines": { "node": ">=8.0.0" @@ -59,5 +63,5 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/riot/src/client/preview/__snapshots__/render-riot.test.js.snap b/app/riot/src/client/preview/__snapshots__/render-riot.test.js.snap index 6e5c63342819..b67db362d53c 100644 --- a/app/riot/src/client/preview/__snapshots__/render-riot.test.js.snap +++ b/app/riot/src/client/preview/__snapshots__/render-riot.test.js.snap @@ -1,7 +1,72 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`render a riot element can accept a constructor 1`] = `"
HACKED : true ; simple test (with a parameter). Oh, by the way (value is mapped to riotValue)
"`; +exports[`render a riot element can accept a constructor 1`] = ` + +
+ HACKED : true ; simple test (with a parameter). Oh, by the way (value is mapped to riotValue) +
+
+`; -exports[`render a riot element can nest several tags 1`] = `"
Inside tag1:
  • Inside tag2:
    • Inside tag3:
      • Inside tag4:
        • Inside tag5:
          • Content
"`; +exports[`render a riot element can nest several tags 1`] = ` + +
+ +
+ Inside tag1: +
    +
  • + +
    + Inside tag2: +
      +
    • + +
      + Inside tag3: +
        +
      • + +
        + Inside tag4: +
          +
        • + +
          + Inside tag5: +
            +
          • + Content +
          • +
          +
          +
          +
        • +
        +
        +
        +
      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+
+
+
+`; -exports[`render a riot element can template some vars 1`] = `"
simple test (with a parameter). Oh, by the way (value is mapped to riotValue)
"`; +exports[`render a riot element can template some vars 1`] = ` + +
+ simple test (with a parameter). Oh, by the way (value is mapped to riotValue) +
+
+`; diff --git a/app/riot/src/client/preview/render.js b/app/riot/src/client/preview/render.js index 3138feb94063..f17ee2596383 100644 --- a/app/riot/src/client/preview/render.js +++ b/app/riot/src/client/preview/render.js @@ -5,8 +5,8 @@ import { render as renderRiot } from './rendering'; export default function renderMain({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain = () => {}, showError = () => {}, }) { @@ -19,7 +19,7 @@ export default function renderMain({ const rendered = renderRiot(element); if (!rendered) { showError({ - title: `Expecting a riot snippet or a riot component from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a riot snippet or a riot component from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the component snippet from the story? Use "() => " or when defining the story. diff --git a/app/riot/src/client/preview/rendering/stringified.js b/app/riot/src/client/preview/rendering/stringified.js index d6a888e69a39..4a7a732b34a9 100644 --- a/app/riot/src/client/preview/rendering/stringified.js +++ b/app/riot/src/client/preview/rendering/stringified.js @@ -1,4 +1,3 @@ -/* eslint-disable import/no-duplicates */ import { mount, unregister, tag2 as tag } from 'riot'; import * as riot from 'riot'; import compiler from 'riot-compiler'; @@ -26,10 +25,7 @@ function compileText(code, rootName) { code.substring(sourceCodeEndOfHtml); const sourceCode = rootName === 'root' ? `${sourceCodeReformatted}` : sourceCodeReformatted; - return compiler - .compile(sourceCode, {}) - .replace(alreadyCompiledMarker, '') - .trim(); + return compiler.compile(sourceCode, {}).replace(alreadyCompiledMarker, '').trim(); } export default function renderStringified({ @@ -38,10 +34,11 @@ export default function renderStringified({ tagConstructor, }) { const tag2 = tag; - tags.forEach(oneTag => { + tags.forEach((input) => { + const oneTag = input || {}; const rootName = oneTag.boundAs || guessRootName(oneTag); - const { content } = oneTag || {}; - const code = content ? content.trim() : oneTag || ''; + const { content } = oneTag; + const code = content ? content.trim() : input || ''; const compiled = code.includes(alreadyCompiledMarker) ? code : compileText(code, rootName); unregister(rootName); eval(getRidOfRiotNoise(`${compiled}`)); // eslint-disable-line no-eval diff --git a/app/server/README.md b/app/server/README.md new file mode 100644 index 000000000000..37bc6240d2e3 --- /dev/null +++ b/app/server/README.md @@ -0,0 +1,25 @@ +# Storybook for Server + +--- + +Storybook for Server is a UI development environment for your plain HTML snippets rendered by your server backend. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-app +npx -p @storybook/cli sb init -t server +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/server/bin/build.js b/app/server/bin/build.js new file mode 100755 index 000000000000..26142ec0af29 --- /dev/null +++ b/app/server/bin/build.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +require('../dist/server/build'); diff --git a/app/server/bin/index.js b/app/server/bin/index.js new file mode 100755 index 000000000000..2e96258ce63d --- /dev/null +++ b/app/server/bin/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../dist/server'); diff --git a/app/server/package.json b/app/server/package.json new file mode 100644 index 000000000000..112a6be7d2e4 --- /dev/null +++ b/app/server/package.json @@ -0,0 +1,57 @@ +{ + "name": "@storybook/server", + "version": "6.0.0-beta.21", + "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/app/server", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/server" + }, + "license": "MIT", + "main": "dist/client/index.js", + "types": "dist/client/index.d.ts", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@storybook/node-logger": "^5.2.8", + "@types/webpack-env": "^1.15.2", + "core-js": "^3.0.1", + "global": "^4.3.2", + "regenerator-runtime": "^0.13.3", + "safe-identifier": "^0.4.1", + "ts-dedent": "^1.1.1" + }, + "devDependencies": { + "fs-extra": "^9.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" +} diff --git a/app/polymer/src/client/index.js b/app/server/src/client/index.ts similarity index 100% rename from app/polymer/src/client/index.js rename to app/server/src/client/index.ts diff --git a/app/server/src/client/preview/globals.ts b/app/server/src/client/preview/globals.ts new file mode 100644 index 000000000000..90dca4aa3750 --- /dev/null +++ b/app/server/src/client/preview/globals.ts @@ -0,0 +1,3 @@ +import { window } from 'global'; + +window.STORYBOOK_ENV = 'SERVER'; diff --git a/app/server/src/client/preview/index.ts b/app/server/src/client/preview/index.ts new file mode 100644 index 000000000000..bccce6b8ac26 --- /dev/null +++ b/app/server/src/client/preview/index.ts @@ -0,0 +1,43 @@ +import { start } from '@storybook/core/client'; +import { ClientStoryApi, Loadable } from '@storybook/addons'; + +import './globals'; +import { renderMain as render, setFetchStoryHtml } from './render'; +import { StoryFnServerReturnType, IStorybookSection, ConfigureOptionsArgs } from './types'; + +const framework = 'server'; + +interface ClientApi extends ClientStoryApi { + setAddon(addon: any): void; + configure(loader: Loadable, module: NodeModule, options?: ConfigureOptionsArgs): void; + getStorybook(): IStorybookSection[]; + clearDecorators(): void; + forceReRender(): void; + raw: () => any; // todo add type +} + +const api = start(render); + +export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { + return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ + framework, + }); +}; + +const setRenderFetchAndConfigure: ClientApi['configure'] = (loader, module, options) => { + if (options && options.fetchStoryHtml) { + setFetchStoryHtml(options.fetchStoryHtml); + } + api.configure(loader, module, framework); +}; + +export const configure: ClientApi['configure'] = setRenderFetchAndConfigure; +export const { + addDecorator, + addParameters, + clearDecorators, + setAddon, + forceReRender, + getStorybook, + raw, +} = api.clientApi; diff --git a/app/server/src/client/preview/render.ts b/app/server/src/client/preview/render.ts new file mode 100644 index 000000000000..904ac552a39b --- /dev/null +++ b/app/server/src/client/preview/render.ts @@ -0,0 +1,61 @@ +import { document, fetch, Node } from 'global'; +import dedent from 'ts-dedent'; +import { RenderContext, FetchStoryHtmlType } from './types'; + +const rootElement = document.getElementById('root'); + +let fetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => { + const fetchUrl = new URL(`${url}/${path}`); + fetchUrl.search = new URLSearchParams(params).toString(); + + const response = await fetch(fetchUrl); + return response.text(); +}; + +export async function renderMain({ + storyFn, + id, + kind, + name, + showMain, + showError, + forceRender, + parameters, +}: RenderContext) { + const storyParams = storyFn(); + + const { + server: { url, id: storyId, params }, + } = parameters; + + const fetchId = storyId || id; + const fetchParams = { ...params, ...storyParams }; + const element = await fetchStoryHtml(url, fetchId, fetchParams); + + showMain(); + if (typeof element === 'string') { + rootElement.innerHTML = element; + } else if (element instanceof Node) { + // Don't re-mount the element if it didn't change and neither did the story + if (rootElement.firstChild === element && forceRender === true) { + return; + } + + rootElement.innerHTML = ''; + rootElement.appendChild(element); + } else { + showError({ + title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, + description: dedent` + Did you forget to return the HTML snippet from the story? + Use "() => " or when defining the story. + `, + }); + } +} + +export const setFetchStoryHtml: any = (fetchHtml: FetchStoryHtmlType) => { + if (fetchHtml !== undefined) { + fetchStoryHtml = fetchHtml; + } +}; diff --git a/app/server/src/client/preview/types.ts b/app/server/src/client/preview/types.ts new file mode 100644 index 000000000000..23bdf5b3a12c --- /dev/null +++ b/app/server/src/client/preview/types.ts @@ -0,0 +1,24 @@ +export { RenderContext } from '@storybook/core'; + +export type StoryFnServerReturnType = any; + +export type FetchStoryHtmlType = (url: string, id: string, params: any) => Promise; + +export interface IStorybookStory { + name: string; + render: () => any; +} + +export interface IStorybookSection { + kind: string; + stories: IStorybookStory[]; +} + +export interface ShowErrorArgs { + title: string; + description: string; +} + +export interface ConfigureOptionsArgs { + fetchStoryHtml: FetchStoryHtmlType; +} diff --git a/app/server/src/lib/compiler/__testfixtures__/a11y.json b/app/server/src/lib/compiler/__testfixtures__/a11y.json new file mode 100644 index 000000000000..c4496e6ab403 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/a11y.json @@ -0,0 +1,14 @@ +{ + "title": "Addons/a11y", + "parameters": { + "options": { "selectedPanel": "storybook/a11y/panel" } + }, + "stories": [ + { + "name": "Label", + "parameters": { + "server": { "id": "addons/a11y/label" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot new file mode 100644 index 000000000000..204ba40358b1 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler a11y.json 1`] = ` +" +export default { + title: 'Addons/a11y', + parameters: { + options: { + selectedPanel: 'storybook/a11y/panel' + } + } +}; + +export const Label = () => {}; +Label.story = { + name: 'Label', + parameters: { + server: { + id: 'addons/a11y/label' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/actions.json b/app/server/src/lib/compiler/__testfixtures__/actions.json new file mode 100644 index 000000000000..ac94c3527d1f --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/actions.json @@ -0,0 +1,15 @@ +{ + "title": "Addons/Actions", + "parameters": { + "options": { "selectedPanel": "storybook/actions/panel" } + }, + "stories": [ + { + "name": "Multiple actions + config", + "parameters": { + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }], + "server": { "id": "addons/actions/story3" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/actions.snapshot b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot new file mode 100644 index 000000000000..0df7e9216ed8 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler actions.json 1`] = ` +" +export default { + title: 'Addons/Actions', + parameters: { + options: { + selectedPanel: 'storybook/actions/panel' + } + } +}; + +export const Multiple_actions_config = () => {}; +Multiple_actions_config.story = { + name: 'Multiple actions + config', + parameters: { + actions: [ + 'click', + 'contextmenu', + { + clearOnStoryChange: false + } + ], + server: { + id: 'addons/actions/story3' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/backgrounds.json b/app/server/src/lib/compiler/__testfixtures__/backgrounds.json new file mode 100644 index 000000000000..4c91c4f4c51c --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/backgrounds.json @@ -0,0 +1,17 @@ +{ + "title": "Addons/Backgrounds", + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ] + }, + "stories": [ + { + "name": "Story 1", + "parameters": { + "server": { "id": "addons/backgrounds/story1" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot new file mode 100644 index 000000000000..c1408fd5dbc2 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler backgrounds.json 1`] = ` +" +export default { + title: 'Addons/Backgrounds', + parameters: { + backgrounds: [ + { + name: 'light', + value: '#eeeeee' + }, + { + name: 'dark', + value: '#222222', + default: true + } + ] + } +}; + +export const Story_1 = () => {}; +Story_1.story = { + name: 'Story 1', + parameters: { + server: { + id: 'addons/backgrounds/story1' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json new file mode 100644 index 000000000000..63909b27c77a --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json @@ -0,0 +1,40 @@ +{ + "title": "Kitchen Sink", + "addons": ["knobs"], + "parameters": { + "backgrounds": [ + { "name": "light", "value": "#eeeeee" }, + { "name": "dark", "value": "#222222", "default": true } + ], + "options": { "selectedPanel": "storybook/a11y/panel" }, + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Heading", + "parameters": { + "actions": ["click", "contextmenu", { "clearOnStoryChange": false }], + "notes": "My notes on some bold text", + "server": { + "id": "demo/heading", + "params": { + "color": "orange" + } + } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "number", "name": "Age", "value": 44, "param": "age"} + ] + }, + { + "name": "Button", + "parameters": { + "notes": "My notes on a button", + "server": { "id": "demo/button" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot new file mode 100644 index 000000000000..e3648ad5eecf --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler kitchen_sink.json 1`] = ` +"import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; + +export default { + title: 'Kitchen Sink', + decorators: [ + withKnobs + ], + parameters: { + backgrounds: [ + { + name: 'light', + value: '#eeeeee' + }, + { + name: 'dark', + value: '#222222', + default: true + } + ], + options: { + selectedPanel: 'storybook/a11y/panel' + }, + server: { + params: { + color: 'red' + } + } + } +}; + +export const Heading = () => { + return { + name: text('Name', 'John Doe'), + age: number('Age', 44, {}), + }; +}; +Heading.story = { + name: 'Heading', + parameters: { + actions: [ + 'click', + 'contextmenu', + { + clearOnStoryChange: false + } + ], + notes: 'My notes on some bold text', + server: { + id: 'demo/heading', + params: { + color: 'orange' + } + } + } +}; + +export const Button = () => {}; +Button.story = { + name: 'Button', + parameters: { + notes: 'My notes on a button', + server: { + id: 'demo/button' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.json b/app/server/src/lib/compiler/__testfixtures__/knobs.json new file mode 100644 index 000000000000..fc10ce3062e8 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/knobs.json @@ -0,0 +1,36 @@ +{ + "title": "Addons/Knobs", + "addons": ["knobs"], + "parameters": { + "options": { "selectedPanel": "storybook/knobs/panel" } + }, + "stories": [ + { + "name": "Simple", + "parameters": { + "server": { "id": "addons/knobs/simple" } + }, + "knobs": [ + { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, + { "type": "date", "name": "Birthday", "value": "1960-12-25T00:42:03.600Z", "param": "birthday"}, + { "type": "color", "name": "Favorite Color", "value": "red", "param": "favorite_color"}, + { "type": "boolean", "name": "Active", "value": true, "param": "active"}, + { "type": "number", "name": "Pets", "value": 2, "param": "pets"}, + { "type": "array", "name": "Sports", "value": ["football", "baseball"], "param": "sports"}, + { + "type": "select", + "name": "Favorite Food", + "value": "Ice Cream", + "options": { + "hot_dog": "Hot Dog", + "pizza": "Pizza", + "burgers": "Burgers", + "ice_cream": "Ice Cream" + }, + "param": "favorite_food" + }, + { "type": "object", "name": "Other Things", "value": {"hair": "Brown", "eyes": "Blue"}, "param": "other_thinkgs"} + ] + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot b/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot new file mode 100644 index 000000000000..e6d54bc510de --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler knobs.json 1`] = ` +"import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; + +export default { + title: 'Addons/Knobs', + decorators: [ + withKnobs + ], + parameters: { + options: { + selectedPanel: 'storybook/knobs/panel' + } + } +}; + +export const Simple = () => { + return { + name: text('Name', 'John Doe'), + birthday: new Date(date('Birthday', new Date('1960-12-25T00:42:03.600Z'))).toISOString(), + favorite_color: color('Favorite Color', 'red'), + active: boolean('Active', true), + pets: number('Pets', 2, {}), + sports: array('Sports', [ + 'football', + 'baseball' + ], ',').join(','), + favorite_food: select('Favorite Food', { + hot_dog: 'Hot Dog', + pizza: 'Pizza', + burgers: 'Burgers', + ice_cream: 'Ice Cream' + }, 'Ice Cream'), + other_thinkgs: JSON.stringify(object('Other Things', { + hair: 'Brown', + eyes: 'Blue' + })), + }; +}; +Simple.story = { + name: 'Simple', + parameters: { + server: { + id: 'addons/knobs/simple' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/links.json b/app/server/src/lib/compiler/__testfixtures__/links.json new file mode 100644 index 000000000000..ee192f0bec81 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/links.json @@ -0,0 +1,13 @@ +{ + "title": "Welcome", + "stories": [ + { + "name": "Welcome", + "parameters": { + "server": { + "id": "welcome/welcome" + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/links.snapshot b/app/server/src/lib/compiler/__testfixtures__/links.snapshot new file mode 100644 index 000000000000..70dda862c9a9 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/links.snapshot @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler links.json 1`] = ` +" +export default { + title: 'Welcome', +}; + +export const Welcome = () => {}; +Welcome.story = { + name: 'Welcome', + parameters: { + server: { + id: 'welcome/welcome' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json new file mode 100644 index 000000000000..207332142d29 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.json @@ -0,0 +1,23 @@ +{ + "title": "Demo", + "stories": [ + { + "name": "Heading", + "parameters": { + "server": { "id": "demo/heading" } + } + }, + { + "name": "Headings", + "parameters": { + "server": { "id": "demo/headings" } + } + }, + { + "name": "Button", + "parameters": { + "server": { "id": "demo/button" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot new file mode 100644 index 000000000000..4e1bc924e2fc --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler multiple_stories.json 1`] = ` +" +export default { + title: 'Demo', +}; + +export const Heading = () => {}; +Heading.story = { + name: 'Heading', + parameters: { + server: { + id: 'demo/heading' + } + } +}; + +export const Headings = () => {}; +Headings.story = { + name: 'Headings', + parameters: { + server: { + id: 'demo/headings' + } + } +}; + +export const Button = () => {}; +Button.story = { + name: 'Button', + parameters: { + server: { + id: 'demo/button' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.json b/app/server/src/lib/compiler/__testfixtures__/notes.json new file mode 100644 index 000000000000..9dc4e21c17d5 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/notes.json @@ -0,0 +1,12 @@ +{ + "title": "Addons/Notes", + "stories": [ + { + "name": "Simple note", + "parameters": { + "notes": "My notes on some bold text", + "server": { "id": "addons/notes/story1" } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.snapshot b/app/server/src/lib/compiler/__testfixtures__/notes.snapshot new file mode 100644 index 000000000000..95760047ea4b --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/notes.snapshot @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler notes.json 1`] = ` +" +export default { + title: 'Addons/Notes', +}; + +export const Simple_note = () => {}; +Simple_note.story = { + name: 'Simple note', + parameters: { + notes: 'My notes on some bold text', + server: { + id: 'addons/notes/story1' + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/params.json b/app/server/src/lib/compiler/__testfixtures__/params.json new file mode 100644 index 000000000000..60f6b720cf42 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params.json @@ -0,0 +1,19 @@ +{ + "title": "Params", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Story", + "parameters": { + "server": { + "id": "params/story", + "params": { "message": "Hello World" } + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/params.snapshot b/app/server/src/lib/compiler/__testfixtures__/params.snapshot new file mode 100644 index 000000000000..c0517efed521 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params.snapshot @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler params.json 1`] = ` +" +export default { + title: 'Params', + parameters: { + server: { + params: { + color: 'red' + } + } + } +}; + +export const Story = () => {}; +Story.story = { + name: 'Story', + parameters: { + server: { + id: 'params/story', + params: { + message: 'Hello World' + } + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/params_override.json b/app/server/src/lib/compiler/__testfixtures__/params_override.json new file mode 100644 index 000000000000..717be13a4ad3 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params_override.json @@ -0,0 +1,19 @@ +{ + "title": "Params", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Override", + "parameters": { + "server": { + "id": "params/override", + "params": { "message": "Hello World", "color": "green" } + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot new file mode 100644 index 000000000000..897da9eaf040 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler params_override.json 1`] = ` +" +export default { + title: 'Params', + parameters: { + server: { + params: { + color: 'red' + } + } + } +}; + +export const Override = () => {}; +Override.story = { + name: 'Override', + parameters: { + server: { + id: 'params/override', + params: { + message: 'Hello World', + color: 'green' + } + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/decorators/index.ts b/app/server/src/lib/compiler/decorators/index.ts new file mode 100644 index 000000000000..77d988f10d0d --- /dev/null +++ b/app/server/src/lib/compiler/decorators/index.ts @@ -0,0 +1,14 @@ +import { StorybookSection, Decorator } from '../types'; +import { knobsDecorator } from './knobs'; + +const allDecorators: Record = { + knobs: knobsDecorator, +}; + +export function decorateSection(section: StorybookSection, addons: string[]): StorybookSection { + const decorators = Object.keys(allDecorators) + .filter((addon) => addons.includes(addon)) + .map((addon) => allDecorators[addon]); + + return decorators.reduce((sec, decorator) => decorator(sec), section); +} diff --git a/app/server/src/lib/compiler/decorators/knobs.ts b/app/server/src/lib/compiler/decorators/knobs.ts new file mode 100644 index 000000000000..ac93b1842cc2 --- /dev/null +++ b/app/server/src/lib/compiler/decorators/knobs.ts @@ -0,0 +1,95 @@ +import dedent from 'ts-dedent'; +import { StorybookSection, StorybookStory } from '../types'; +import { decorateSimpleAddon, importMeta } from './utils'; +import { stringifyObject } from '../stringifier'; + +type KnobType = 'text' | 'number' | 'color' | 'array' | 'boolean' | 'object' | 'date' | 'select'; + +interface StoryKnob { + param: string; + type: KnobType; + name: string; + value: any; + [x: string]: any; +} + +function stringifyKnob(knob: StoryKnob) { + const { param, type, name, value, ...opts } = knob; + const knobParam = param || name; // Todo: can we do away with this? + const level = 2; + const stringifiedValue = stringifyObject(value, level); + // TODO: Add group + const knobFunction = ((t) => { + switch (t) { + case 'text': + return `text('${name}', ${stringifiedValue})`; + case 'number': + return `number('${name}', ${stringifiedValue}, ${stringifyObject(opts, level)})`; + case 'color': + return `color('${name}', ${stringifiedValue})`; + case 'array': { + const separator = opts.separator || ','; + return `array('${name}', ${stringifiedValue}, '${separator}').join('${separator}')`; + } + case 'boolean': + return `boolean('${name}', ${stringifiedValue})`; + case 'object': + return `JSON.stringify(object('${name}', ${stringifiedValue}))`; + case 'date': + return `new Date(date('${name}', new Date(${stringifiedValue}))).toISOString()`; + case 'select': + return `select('${name}', ${stringifyObject(opts.options, level)}, ${stringifiedValue})`; + default: + return ''; + } + })(type); + + return `${knobParam}: ${knobFunction}`; +} + +function stringifyStoryFunction(knobs: StoryKnob[], storyFn: string) { + if (!knobs || knobs.length === 0) return storyFn; + + return dedent` + () => { + return { + ${knobs.map((knob: any) => `${stringifyKnob(knob)},`).join('\n ')} + }; + } + `; +} + +function knobsStoryDecorator(story: StorybookStory): StorybookStory { + const { name, storyFn, knobs, ...options } = story; + + return { + name, + storyFn: stringifyStoryFunction(knobs, storyFn), + ...options, + }; +} + +export function knobsDecorator(section: StorybookSection): StorybookSection { + const { title, imports, decorators, stories, ...options } = decorateSimpleAddon(section, 'knobs'); + const { importName, moduleName } = importMeta('knobs'); + + const knobImports = [ + importName, + 'array', + 'boolean', + 'color', + 'date', + 'text', + 'number', + 'object', + 'select', + ]; + + return { + title, + imports: { ...imports, ...{ [moduleName]: knobImports } }, + decorators, + stories: stories.map((story) => knobsStoryDecorator(story)), + ...options, + }; +} diff --git a/app/server/src/lib/compiler/decorators/utils.ts b/app/server/src/lib/compiler/decorators/utils.ts new file mode 100644 index 000000000000..838df57a9d74 --- /dev/null +++ b/app/server/src/lib/compiler/decorators/utils.ts @@ -0,0 +1,21 @@ +import { StorybookSection } from '../types'; + +export function importMeta(addon: string) { + return { + importName: `with${addon.charAt(0).toUpperCase() + addon.slice(1)}`, + moduleName: `@storybook/addon-${addon}`, + }; +} + +export function decorateSimpleAddon(section: StorybookSection, addon: string) { + const { title, imports, decorators, stories, ...options } = section; + const { importName, moduleName } = importMeta(addon); + + return { + title, + imports: { ...imports, ...{ [moduleName]: [importName] } }, + decorators: [...decorators, importName], + stories, + ...options, + }; +} diff --git a/app/server/src/lib/compiler/index.ts b/app/server/src/lib/compiler/index.ts new file mode 100644 index 000000000000..8b329fb991ea --- /dev/null +++ b/app/server/src/lib/compiler/index.ts @@ -0,0 +1,38 @@ +import { + CompileCsfModuleArgs, + CompileStorybookSectionArgs, + CompileStorybookStoryArgs, + StorybookSection, + StorybookStory, +} from './types'; + +import { stringifySection } from './stringifier'; +import { decorateSection } from './decorators'; + +function createStory(storyArgs: CompileStorybookStoryArgs): StorybookStory { + const { name, ...options } = storyArgs; + + return { + name, + storyFn: '() => {}', + ...options, + }; +} + +function createSection(args: CompileStorybookSectionArgs): StorybookSection { + const { title, stories, ...options } = args; + return { + imports: {}, + decorators: [], + title, + stories: stories.map((storyArgs) => createStory(storyArgs)), + ...options, + }; +} + +export function compileCsfModule(args: CompileCsfModuleArgs): string { + const { addons = [], ...compileSectionArgs } = args; + const storybookSection = createSection(compileSectionArgs); + const decoratedSection = decorateSection(storybookSection, addons); + return stringifySection(decoratedSection); +} diff --git a/app/server/src/lib/compiler/json-to-csf-compiler.test.ts b/app/server/src/lib/compiler/json-to-csf-compiler.test.ts new file mode 100644 index 000000000000..503cf198a995 --- /dev/null +++ b/app/server/src/lib/compiler/json-to-csf-compiler.test.ts @@ -0,0 +1,24 @@ +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs-extra'; +import { compileCsfModule } from '.'; + +const inputRegExp = /\.json$/; + +async function generate(filePath: string) { + const content = await fs.readFile(filePath, 'utf8'); + return compileCsfModule(JSON.parse(content)); +} + +describe('json-to-csf-compiler', () => { + const transformFixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(transformFixturesDir) + .filter((fileName: string) => inputRegExp.test(fileName)) + .forEach((fixtureFile: string) => { + it(fixtureFile, async () => { + const inputPath = path.join(transformFixturesDir, fixtureFile); + const code = await generate(inputPath); + expect(code).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.snapshot')); + }); + }); +}); diff --git a/app/server/src/lib/compiler/stringifier.ts b/app/server/src/lib/compiler/stringifier.ts new file mode 100644 index 000000000000..7dff01084c54 --- /dev/null +++ b/app/server/src/lib/compiler/stringifier.ts @@ -0,0 +1,85 @@ +import dedent from 'ts-dedent'; +import { StorybookStory, StorybookSection } from './types'; + +const { identifier } = require('safe-identifier'); + +export function stringifyObject(object: any, level = 0, excludeOuterParams = false): string { + if (typeof object === 'string') { + return `'${object}'`; + } + const indent = ' '.repeat(level); + if (Array.isArray(object)) { + const arrayStrings: string[] = object.map((item: any) => stringifyObject(item, level + 1)); + const arrayString = arrayStrings.join(`,\n${indent} `); + if (excludeOuterParams) return arrayString; + return `[\n${indent} ${arrayString}\n${indent}]`; + } + if (typeof object === 'object') { + let objectString = ''; + if (Object.keys(object).length > 0) { + const objectStrings: string[] = Object.keys(object).map((key) => { + const value: string = stringifyObject(object[key], level + 1); + return `\n${indent} ${key}: ${value}`; + }); + objectString = objectStrings.join(','); + } + if (excludeOuterParams) return objectString; + if (objectString.length === 0) return '{}'; + return `{${objectString}\n${indent}}`; + } + + return object; +} + +export function stringifyImports(imports: Record): string { + if (Object.keys(imports).length === 0) return ''; + return Object.entries(imports) + .map(([module, names]) => `import { ${names.sort().join(', ')} } from '${module}';\n`) + .join(''); +} + +export function stringifyDecorators(decorators: string[]): string { + return decorators && decorators.length > 0 + ? `\n decorators: [\n ${decorators.join(',\n ')}\n ],` + : ''; +} + +export function stringifyDefault(section: StorybookSection): string { + const { title, imports, decorators, stories, ...options } = section; + + const decoratorsString = stringifyDecorators(decorators); + + const optionsString = stringifyObject(options, 0, true); + + return dedent` + export default { + title: '${title}',${decoratorsString}${optionsString} + }; + + `; +} + +export function stringifyStory(story: StorybookStory): string { + const { name, storyFn, decorators, ...options } = story; + const storyId = identifier(name); + + const decoratorsString = stringifyDecorators(decorators); + const optionsString = stringifyObject({ name, ...options }, 0, true); + + let storyString = ''; + if (decoratorsString.length > 0 || optionsString.length > 0) { + storyString = `${storyId}.story = {${decoratorsString}${optionsString}\n};\n`; + } + return `export const ${storyId} = ${storyFn};\n${storyString}`; +} + +export function stringifySection(section: StorybookSection): string { + const sectionString = [ + stringifyImports(section.imports), + stringifyDefault(section), + ...section.stories.map((story) => stringifyStory(story)), + ].join('\n'); + + // console.log('sectionString:\n', sectionString); + return sectionString; +} diff --git a/app/server/src/lib/compiler/types.ts b/app/server/src/lib/compiler/types.ts new file mode 100644 index 000000000000..0de8dbd14f2b --- /dev/null +++ b/app/server/src/lib/compiler/types.ts @@ -0,0 +1,31 @@ +export interface CompileStorybookStoryArgs { + name: string; + [x: string]: any; +} + +export interface CompileStorybookSectionArgs { + title: string; + stories: CompileStorybookStoryArgs[]; + [x: string]: any; +} + +export interface CompileCsfModuleArgs extends CompileStorybookSectionArgs { + addons?: string[]; +} + +export interface StorybookStory { + name: string; + storyFn: string; + decorators?: string[]; + [x: string]: any; +} + +export interface StorybookSection { + imports: Record; + decorators?: string[]; + title: string; + stories: StorybookStory[]; + [x: string]: any; +} + +export type Decorator = (section: StorybookSection) => StorybookSection; diff --git a/app/server/src/server/build.ts b/app/server/src/server/build.ts new file mode 100644 index 000000000000..d8abf06a4396 --- /dev/null +++ b/app/server/src/server/build.ts @@ -0,0 +1,4 @@ +import { buildStatic } from '@storybook/core/server'; +import options from './options'; + +buildStatic(options); diff --git a/app/server/src/server/framework-preset-server.ts b/app/server/src/server/framework-preset-server.ts new file mode 100644 index 000000000000..2604c82e8b0f --- /dev/null +++ b/app/server/src/server/framework-preset-server.ts @@ -0,0 +1,24 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Configuration } from 'webpack'; +import path from 'path'; + +export function webpack(config: Configuration) { + return { + ...config, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + type: 'javascript/auto', + test: /\.stories\.json$/, + use: [ + { + loader: path.resolve(__dirname, './loader.js'), + }, + ], + }, + ], + }, + }; +} diff --git a/app/server/src/server/index.ts b/app/server/src/server/index.ts new file mode 100644 index 000000000000..774d96025a84 --- /dev/null +++ b/app/server/src/server/index.ts @@ -0,0 +1,4 @@ +import { buildDev } from '@storybook/core/server'; +import options from './options'; + +buildDev(options); diff --git a/app/server/src/server/loader.ts b/app/server/src/server/loader.ts new file mode 100644 index 000000000000..67b8aab46e0c --- /dev/null +++ b/app/server/src/server/loader.ts @@ -0,0 +1,5 @@ +import { compileCsfModule } from '../lib/compiler'; + +export default (content: string) => { + return compileCsfModule(JSON.parse(content)); +}; diff --git a/app/server/src/server/options.ts b/app/server/src/server/options.ts new file mode 100644 index 000000000000..ee538806a875 --- /dev/null +++ b/app/server/src/server/options.ts @@ -0,0 +1,7 @@ +const packageJson = require('../../package.json'); + +export default { + packageJson, + framework: 'server', + frameworkPresets: [require.resolve('./framework-preset-server.js')], +}; diff --git a/app/server/src/typings.d.ts b/app/server/src/typings.d.ts new file mode 100644 index 000000000000..690e93343de2 --- /dev/null +++ b/app/server/src/typings.d.ts @@ -0,0 +1,5 @@ +declare module '@storybook/core/*'; +declare module 'global'; + +// will be provided by the webpack define plugin +declare var NODE_ENV: string | undefined; diff --git a/app/server/standalone.js b/app/server/standalone.js new file mode 100644 index 000000000000..1b1febe0d3bb --- /dev/null +++ b/app/server/standalone.js @@ -0,0 +1,8 @@ +const build = require('@storybook/core/standalone'); +const frameworkOptions = require('./dist/server/options').default; + +async function buildStandalone(options) { + return build(options, frameworkOptions); +} + +module.exports = buildStandalone; diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json new file mode 100644 index 000000000000..82ce44329cc6 --- /dev/null +++ b/app/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": ["src/**/*"], + "exclude": ["src/__tests__/**/*"] +} diff --git a/app/svelte/package.json b/app/svelte/package.json index 437c9e5aab2e..52316d7f4143 100644 --- a/app/svelte/package.json +++ b/app/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/svelte" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,23 +22,34 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { - "svelte": "^3.4.1", + "@types/webpack-env": "^1.15.2", + "svelte": "^3.18.1", "svelte-loader": "^2.13.4" }, "peerDependencies": { - "babel-loader": "^7.0.0 || ^8.0.0", + "@babel/core": "*", + "react": "*", + "react-dom": "*", "svelte": "^3.1.0", "svelte-loader": "^2.9.1" }, @@ -55,5 +59,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/svelte/src/client/preview/render.ts b/app/svelte/src/client/preview/render.ts index 76f8b005258a..cea239a2d152 100644 --- a/app/svelte/src/client/preview/render.ts +++ b/app/svelte/src/client/preview/render.ts @@ -1,6 +1,7 @@ +import { detach, insert, noop } from 'svelte/internal'; import { document } from 'global'; import dedent from 'ts-dedent'; -import { MountViewArgs, RenderMainArgs } from './types'; +import { MountViewArgs, RenderContext } from './types'; type Component = any; @@ -14,6 +15,32 @@ function cleanUpPreviousStory() { previousComponent = null; } +function createSlotFn(element: any) { + return [ + function createSlot() { + return { + c: noop, + m: function mount(target: any, anchor: any) { + insert(target, element, anchor); + }, + d: function destroy(detaching: boolean) { + if (detaching) { + detach(element); + } + }, + l: noop, + }; + }, + ]; +} + +function createSlots(slots: Record): Record { + return Object.entries(slots).reduce((acc, [slotName, element]) => { + acc[slotName] = createSlotFn(element); + return acc; + }, {} as Record); +} + function mountView({ Component, target, props, on, Wrapper, WrapperData }: MountViewArgs) { let component: Component; @@ -23,8 +50,11 @@ function mountView({ Component, target, props, on, Wrapper, WrapperData }: Mount const wrapper = new Wrapper({ target, - slots: { default: fragment }, - props: WrapperData || {}, + props: { + ...WrapperData, + $$slots: createSlots({ default: fragment }), + $$scope: {}, + }, }); component.$on('destroy', () => { wrapper.$destroy(true); @@ -35,7 +65,7 @@ function mountView({ Component, target, props, on, Wrapper, WrapperData }: Mount if (on) { // Attach svelte event listeners. - Object.keys(on).forEach(eventName => { + Object.keys(on).forEach((eventName) => { component.$on(eventName, on[eventName]); }); } @@ -43,13 +73,7 @@ function mountView({ Component, target, props, on, Wrapper, WrapperData }: Mount previousComponent = component; } -export default function render({ - storyFn, - selectedKind, - selectedStory, - showMain, - showError, -}: RenderMainArgs) { +export default function render({ storyFn, kind, name, showMain, showError }: RenderContext) { const { /** @type {SvelteComponent} */ Component, @@ -67,7 +91,7 @@ export default function render({ if (!DefaultCompatComponent) { showError({ - title: `Expecting a Svelte component from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a Svelte component from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the Svelte component configuration from the story? Use "() => ({ Component: YourComponent, data: {} })" diff --git a/app/svelte/src/client/preview/types.ts b/app/svelte/src/client/preview/types.ts index 31bf80ea6267..4382e18ca0ff 100644 --- a/app/svelte/src/client/preview/types.ts +++ b/app/svelte/src/client/preview/types.ts @@ -1,18 +1,10 @@ -import { StoryFn } from '@storybook/addons'; +export { RenderContext } from '@storybook/core'; export interface ShowErrorArgs { title: string; description: string; } -export interface RenderMainArgs { - storyFn: StoryFn; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; -} - export interface MountViewArgs { Component: any; target: any; diff --git a/app/svelte/tsconfig.json b/app/svelte/tsconfig.json index 9fc120ad0834..29fcd6ad6a26 100644 --- a/app/svelte/tsconfig.json +++ b/app/svelte/tsconfig.json @@ -11,4 +11,4 @@ "exclude": [ "src/**/*.test.*" ] -} \ No newline at end of file +} diff --git a/app/vue/README.md b/app/vue/README.md index d9fb66ea933f..57f4901cc329 100644 --- a/app/vue/README.md +++ b/app/vue/README.md @@ -19,7 +19,7 @@ For more information visit: [storybook.js.org](https://storybook.js.org) ## Starter Storybook-for-Vue Boilerplate project with [Vuetify](https://github.com/vuetifyjs/vuetify) Material Component Framework - + --- diff --git a/app/vue/package.json b/app/vue/package.json index f509c861627a..0b9add749319 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -15,13 +15,6 @@ "directory": "app/vue" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -29,30 +22,42 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0", - "webpack": "^4.33.0" + "ts-dedent": "^1.1.1", + "ts-loader": "^6.2.2", + "webpack": "^4.43.0" }, "devDependencies": { - "@types/mini-css-extract-plugin": "^0.8.0", - "@types/node": "^12.12.11", - "@types/webpack": "^4.41.0", - "babel-preset-vue": "^2.0.2", + "@types/node": "^14.0.10", + "@types/webpack": "^4.41.12", "vue": "^2.6.8", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.8" }, "peerDependencies": { + "@babel/core": "*", "babel-loader": "^7.0.0 || ^8.0.0", + "css-loader": "*", + "react": "*", + "react-dom": "*", + "ts-loader": "^6.2.2", "vue": "^2.6.8", "vue-loader": "^15.7.0", "vue-template-compiler": "^2.6.8" @@ -63,5 +68,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/vue/src/client/preview/index.ts b/app/vue/src/client/preview/index.ts index f00ff8338602..eeb0edf34644 100644 --- a/app/vue/src/client/preview/index.ts +++ b/app/vue/src/client/preview/index.ts @@ -68,6 +68,8 @@ const defaultContext: StoryContext = { name: 'unspecified', kind: 'unspecified', parameters: {}, + args: {}, + globalArgs: {}, }; function decorateStory( @@ -78,22 +80,13 @@ function decorateStory( (decorated: StoryFn, decorator) => (context: StoryContext = defaultContext) => { let story; - const decoratedStory = decorator(p => { - story = decorated( - p - ? { - ...context, - ...p, - parameters: { - ...context.parameters, - ...p.parameters, - }, - } - : context - ); - - return story; - }, context); + const decoratedStory = decorator( + ({ parameters, ...innerContext }: StoryContext = {} as StoryContext) => { + story = decorated({ ...context, ...innerContext }); + return story; + }, + context + ); if (!story) { story = decorated(context); @@ -105,7 +98,7 @@ function decorateStory( return prepare(decoratedStory, story); }, - context => prepare(storyFn(context)) + (context) => prepare(storyFn(context)) ); } const framework = 'vue'; diff --git a/app/vue/src/client/preview/render.ts b/app/vue/src/client/preview/render.ts index 03967e7fa4f7..a5cdd49fa5b5 100644 --- a/app/vue/src/client/preview/render.ts +++ b/app/vue/src/client/preview/render.ts @@ -1,6 +1,6 @@ import dedent from 'ts-dedent'; import Vue from 'vue'; -import { RenderMainArgs } from './types'; +import { RenderContext } from './types'; export const COMPONENT = 'STORYBOOK_COMPONENT'; export const VALUES = 'STORYBOOK_VALUES'; @@ -20,20 +20,20 @@ const root = new Vue({ export default function render({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain, showError, showException, forceRender, -}: RenderMainArgs) { +}: RenderContext) { Vue.config.errorHandler = showException; const element = storyFn(); if (!element) { showError({ - title: `Expecting a Vue component from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting a Vue component from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the Vue component from the story? Use "() => ({ template: '' })" or "() => ({ components: MyComp, template: '' })" when defining the story. diff --git a/app/vue/src/client/preview/types.ts b/app/vue/src/client/preview/types.ts index 7f89facb4e23..94442a01779c 100644 --- a/app/vue/src/client/preview/types.ts +++ b/app/vue/src/client/preview/types.ts @@ -1,22 +1,12 @@ -import { Component, VueConstructor } from 'vue'; -import { StoryFn } from '@storybook/addons'; -// TODO, 'any' should be what is actually expected from a storyFn +import { Component } from 'vue'; + +export { RenderContext } from '@storybook/core'; export interface ShowErrorArgs { title: string; description: string; } -export interface RenderMainArgs { - storyFn: StoryFn; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - showException: (...args: any[]) => void; - forceRender: boolean; -} - // TODO: some vue expert needs to look at this export type StoryFnVueReturnType = string | Component; diff --git a/app/vue/src/server/framework-preset-vue.ts b/app/vue/src/server/framework-preset-vue.ts index aa063f3d0a4a..bf6f79b53ad5 100644 --- a/app/vue/src/server/framework-preset-vue.ts +++ b/app/vue/src/server/framework-preset-vue.ts @@ -14,6 +14,18 @@ export function webpack(config: Configuration) { loader: require.resolve('vue-loader'), options: {}, }, + { + test: /\.tsx?$/, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + }, ], }, resolve: { @@ -26,10 +38,3 @@ export function webpack(config: Configuration) { }, }; } - -export function babelDefault(config: any) { - return { - ...config, - presets: [...config.presets, require.resolve('babel-preset-vue')], - }; -} diff --git a/app/web-components/README.md b/app/web-components/README.md index 6de3e192d2a0..b8f57d205f53 100644 --- a/app/web-components/README.md +++ b/app/web-components/README.md @@ -14,7 +14,7 @@ So you can develop UI components in isolation without worrying about app specifi ```sh cd my-app -npx -p @storybook/cli sb init -t web-components +npx -p @storybook/cli sb init -t web_components ``` For more information visit: [storybook.js.org](https://storybook.js.org) @@ -60,7 +60,7 @@ For example if you have a library called `my-library` which is in es7 then you c // .storybook/main.js module.exports = { - webpack: async config => { + webpackFinal: async config => { // find web-components rule for extra transpilation const webComponentsRule = config.module.rules.find( rule => rule.use && rule.use.options && rule.use.options.babelrc === false diff --git a/app/web-components/package.json b/app/web-components/package.json index 025e0aa8eb1f..70f0b4ee527a 100644 --- a/app/web-components/package.json +++ b/app/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit-html", @@ -17,13 +17,6 @@ "directory": "app/web-components" }, "license": "MIT", - "files": [ - "bin/**/*", - "dist/**/*", - "README.md", - "*.js", - "*.d.ts" - ], "main": "dist/client/index.js", "types": "dist/client/index.d.ts", "bin": { @@ -31,27 +24,38 @@ "start-storybook": "./bin/index.js", "storybook-server": "./bin/index.js" }, + "files": [ + "bin/**/*", + "dist/**/*", + "README.md", + "*.js", + "*.d.ts", + "ts3.5/**/*" + ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", - "@storybook/addons": "5.3.0-rc.0", - "@storybook/core": "5.3.0-rc.0", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/core": "6.0.0-beta.21", + "@types/webpack-env": "^1.15.2", "babel-plugin-bundled-import-meta": "^0.3.1", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "devDependencies": { "lit-html": "^1.0.0" }, "peerDependencies": { + "@babel/core": "*", "babel-loader": "^7.0.0 || ^8.0.0", - "lit-html": "^1.0.0" + "lit-html": "^1.0.0", + "react": "*", + "react-dom": "*" }, "engines": { "node": ">=8.0.0" @@ -59,5 +63,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/app/web-components/src/client/preview/render.ts b/app/web-components/src/client/preview/render.ts index 9f87fa36e607..77c5b87e36ba 100644 --- a/app/web-components/src/client/preview/render.ts +++ b/app/web-components/src/client/preview/render.ts @@ -1,18 +1,18 @@ import { document, Node } from 'global'; import dedent from 'ts-dedent'; import { render, TemplateResult } from 'lit-html'; -import { RenderMainArgs } from './types'; +import { RenderContext } from './types'; const rootElement = document.getElementById('root'); export default function renderMain({ storyFn, - selectedKind, - selectedStory, + kind, + name, showMain, showError, forceRender, -}: RenderMainArgs) { +}: RenderContext) { const element = storyFn(); showMain(); @@ -38,7 +38,7 @@ export default function renderMain({ rootElement.appendChild(element); } else { showError({ - title: `Expecting an HTML snippet or DOM node from the story: "${selectedStory}" of "${selectedKind}".`, + title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, description: dedent` Did you forget to return the HTML snippet from the story? Use "() => " or when defining the story. diff --git a/app/web-components/src/client/preview/types.ts b/app/web-components/src/client/preview/types.ts index 36432e1a4558..59574c428128 100644 --- a/app/web-components/src/client/preview/types.ts +++ b/app/web-components/src/client/preview/types.ts @@ -1,6 +1,9 @@ -import { StoryFn } from '@storybook/addons'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { TemplateResult, SVGTemplateResult } from 'lit-element'; -export type StoryFnHtmlReturnType = string | Node; +export { RenderContext } from '@storybook/core'; + +export type StoryFnHtmlReturnType = string | Node | TemplateResult | SVGTemplateResult; export interface IStorybookStory { name: string; @@ -16,12 +19,3 @@ export interface ShowErrorArgs { title: string; description: string; } - -export interface RenderMainArgs { - storyFn: () => StoryFn; - selectedKind: string; - selectedStory: string; - showMain: () => void; - showError: (args: ShowErrorArgs) => void; - forceRender: boolean; -} diff --git a/app/web-components/src/server/options.ts b/app/web-components/src/server/options.ts index 6dff94b2b6d2..ea9b2ee7aba4 100644 --- a/app/web-components/src/server/options.ts +++ b/app/web-components/src/server/options.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line: no-var-requires const packageJson = require('../../package.json'); export default { diff --git a/cypress.json b/cypress.json index 0967ef424bce..f098c972a496 100644 --- a/cypress.json +++ b/cypress.json @@ -1 +1,5 @@ -{} +{ + "defaultCommandTimeout": 20000, + "requestTimeout": 20000, + "responseTimeout": 60000 +} diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json index 373914272e56..ccb568d6c96d 100644 --- a/cypress/.eslintrc.json +++ b/cypress/.eslintrc.json @@ -1,5 +1,10 @@ { "extends": [ "plugin:cypress/recommended" - ] + ], + "rules": { + "import/no-extraneous-dependencies": [ + "error", { "devDependencies": true } + ] + } } diff --git a/cypress/generated/addon-action.spec.ts b/cypress/generated/addon-action.spec.ts new file mode 100644 index 000000000000..8f7a2dc858b3 --- /dev/null +++ b/cypress/generated/addon-action.spec.ts @@ -0,0 +1,31 @@ +import { visit, clickAddon } from '../helper'; + +describe('addon-action', () => { + before(() => { + visit(); + cy.get('#button').click(); + }); + + it('should trigger an action', () => { + // click on the button + cy.get('#button--text-with-action').click(); + + // assert url changes + cy.url().should('include', 'path=/story/button--text-with-action'); + + // check for selected element + cy.get('#button--text-with-action').should('have.class', 'selected'); + + // check for content + cy.getStoryElement().contains('Trigger Action').click(); + + // click on addon + clickAddon('Actions'); + + // TODO @yannbf improve tab identifier on addons + // get the logs + cy.get('#storybook-panel-root') + .contains(/This was clicked/) + .should('be.visible'); + }); +}); diff --git a/cypress/generated/addon-link.spec.ts b/cypress/generated/addon-link.spec.ts new file mode 100644 index 000000000000..d94420c496c6 --- /dev/null +++ b/cypress/generated/addon-link.spec.ts @@ -0,0 +1,25 @@ +import { visit, clickAddon } from '../helper'; + +describe('addon-link', () => { + before(() => { + visit(); + cy.get('#button').click(); + }); + + it('should redirect to another story', () => { + // click on the button + cy.get('#button--button-with-link-to-another-story').click(); + + // assert url changes + cy.url().should('include', 'path=/story/button--button-with-link-to-another-story'); + + // check for selected element + cy.get('#button--button-with-link-to-another-story').should('have.class', 'selected'); + + // check for content + cy.getStoryElement().contains('Go to Welcome Story').click(); + + // assert url changes + cy.url().should('include', 'path=/story/welcome--to-storybook'); + }); +}); diff --git a/cypress/generated/basic.spec.ts b/cypress/generated/basic.spec.ts new file mode 100644 index 000000000000..f141a86ccad0 --- /dev/null +++ b/cypress/generated/basic.spec.ts @@ -0,0 +1,35 @@ +import { visit } from '../helper'; + +describe('Basic Flow', () => { + before(() => { + visit(); + }); + + it('should load welcome flow', () => { + // assert url changes + cy.url().should('include', 'path=/story/welcome--to-storybook'); + + // check for selected element + cy.get('#welcome--to-storybook').should('have.class', 'selected'); + + // check for content + cy.getStoryElement().should('contain.text', 'Welcome to storybook'); + }); + + describe('Button story', () => { + before(() => { + cy.get('#button').click(); + }); + + it('should be visited succesfully', () => { + // assert url changes + cy.url().should('include', 'path=/story/button--text'); + + // check for selected element + cy.get('#button--text').should('have.class', 'selected'); + + // check for content + cy.getStoryElement().find('button').should('contain.text', 'Hello Button'); + }); + }); +}); diff --git a/cypress/helper.ts b/cypress/helper.ts index 61c9920125e4..d886439d90b0 100644 --- a/cypress/helper.ts +++ b/cypress/helper.ts @@ -1,18 +1,22 @@ /* eslint-disable no-unused-expressions */ /* eslint-disable jest/valid-expect */ -const baseUrl = 'http://localhost:8001'; - type StorybookApps = 'official-storybook'; -type Addons = 'Knobs'; +type Addons = 'Actions' | 'Knobs'; + +const getUrl = (route: string) => { + const host = Cypress.env('location') || 'http://localhost:8001'; -export const visitExample = (app: StorybookApps, route = '') => { + return `${host}/${route}`; +}; + +export const visit = (route = '') => { return cy .clearLocalStorage() - .visit(`${baseUrl}/${app}/${route}`) + .visit(getUrl(route)) .get(`#storybook-preview-iframe`) - .then({ timeout: 10000 }, iframe => { - return cy.wrap(iframe).should(() => { + .then({ timeout: 15000 }, (iframe) => { + return cy.wrap(iframe, { timeout: 10000 }).should(() => { const content: Document | null = (iframe[0] as HTMLIFrameElement).contentDocument; const element: HTMLElement | null = content !== null ? content.documentElement : null; @@ -26,14 +30,11 @@ export const visitExample = (app: StorybookApps, route = '') => { }; export const clickAddon = (addonName: Addons) => { - return cy - .get(`[role=tablist] button[role=tab]`) - .contains(addonName) - .click(); + return cy.get(`[role=tablist] button[role=tab]`).contains(addonName).click(); }; export const getStorybookPreview = () => { - return cy.get(`#storybook-preview-iframe`).then({ timeout: 10000 }, iframe => { + return cy.get(`#storybook-preview-iframe`).then({ timeout: 10000 }, (iframe) => { const content: Document | null = (iframe[0] as HTMLIFrameElement).contentDocument; const element: HTMLElement | null = content !== null ? content.documentElement : null; diff --git a/cypress/integration/knobs.spec.ts b/cypress/integration/knobs.spec.ts index a670ef486b22..c5f65dca5341 100644 --- a/cypress/integration/knobs.spec.ts +++ b/cypress/integration/knobs.spec.ts @@ -1,18 +1,16 @@ -import { clickAddon, visitExample } from '../helper'; +import { clickAddon, visit } from '../helper'; describe('Knobs', () => { beforeEach(() => { - visitExample('official-storybook', '?path=/story/addons-knobs-withknobs--tweaks-static-values'); + visit('official-storybook/?path=/story/addons-knobs-withknobs--tweaks-static-values'); }); it('[text] it should change a string value', () => { clickAddon('Knobs'); - cy.get('#Name') - .clear() - .type('John Doe'); + cy.get('#Name').clear().type('John Doe'); - cy.preview() + cy.getStoryElement() .console('info') .find('p') .eq(0) diff --git a/cypress/integration/navigation.spec.ts b/cypress/integration/navigation.spec.ts index 942cf47cc643..33f3ba29b7e1 100644 --- a/cypress/integration/navigation.spec.ts +++ b/cypress/integration/navigation.spec.ts @@ -1,14 +1,12 @@ -import { visitExample } from '../helper'; +import { visit } from '../helper'; describe('Navigation', () => { - beforeEach(() => { - visitExample('official-storybook'); + before(() => { + visit('official-storybook'); }); it('should search navigation item', () => { - cy.get('#storybook-explorer-searchfield') - .click() - .type('persisting the action logger'); + cy.get('#storybook-explorer-searchfield').click().clear().type('persisting the action logger'); cy.get('.sidebar-container a') .should('contain', 'Persisting the action logger') @@ -16,9 +14,7 @@ describe('Navigation', () => { }); it('should display no results after searching a non-existing navigation item', () => { - cy.get('#storybook-explorer-searchfield') - .click() - .type('zzzzzzzzzz'); + cy.get('#storybook-explorer-searchfield').click().clear().type('zzzzzzzzzz'); cy.get('.sidebar-container').should('contain', 'This filter resulted in 0 results'); }); @@ -26,15 +22,15 @@ describe('Navigation', () => { describe('Routing', () => { it('should navigate to story addons-a11y-basebutton--default', () => { - visitExample('official-storybook'); - cy.get('#exploreraddons-a11y-basebutton--label').click(); + visit('official-storybook'); + cy.get('#addons-a11y-basebutton--label').click(); cy.url().should('include', 'path=/story/addons-a11y-basebutton--label'); }); it('should directly visit a certain story and render correctly', () => { - visitExample('official-storybook', '?path=/story/addons-a11y-basebutton--label'); + visit('official-storybook/?path=/story/addons-a11y-basebutton--label'); - cy.preview().should('contain.text', 'Testing the a11y addon'); + cy.getStoryElement().should('contain.text', 'Testing the a11y addon'); }); }); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index e699dc68b670..6396b2eae7e9 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -14,7 +14,7 @@ const wp = require('@cypress/webpack-preprocessor'); const webpackConfig = require('./webpack.config'); -module.exports = on => { +module.exports = (on) => { const options = { webpackOptions: webpackConfig, }; diff --git a/cypress/report-teamcity-metadata.ts b/cypress/report-teamcity-metadata.ts new file mode 100644 index 000000000000..3a27544dddf0 --- /dev/null +++ b/cypress/report-teamcity-metadata.ts @@ -0,0 +1,73 @@ +import path from 'path'; +import fs from 'fs-extra'; + +import { testMetadata } from 'teamcity-service-messages'; +import { findSuitesAndTests } from 'mocha-list-tests'; + +const testsDir = path.join(__dirname, 'integration'); +const videosDir = path.join(__dirname, 'videos'); +const screensDir = path.join(__dirname, 'screenshots'); + +let prevFoundTests: string[] = []; +function getTests(fileName: string) { + const { tests } = findSuitesAndTests(path.join(testsDir, fileName)); + const newTests = tests.filter((test: string) => !prevFoundTests.includes(test)); + prevFoundTests = tests; + return newTests.map((test: string) => test.split(/\./)); +} + +const fullTestName = (suite: string, testName: string) => `${suite}: ${testName}`; + +async function report() { + const hookFailures: { [file: string]: [string, string][] } = {}; + const reports: any[] = []; + try { + const testFiles = await fs.readdir(screensDir); + await Promise.all( + testFiles.map(async (testFile) => { + const files = await fs.readdir(path.join(screensDir, testFile)); + files.forEach((file) => { + const match = file.match(/^(.*) \(failed\).png$/); + if (match == null) { + return; + } + + const [suite, test, hookPart] = match[1].split(' -- '); + let testName = test; + const hook = hookPart?.match(/^(.*) hook$/)?.[1]; + if (hook != null) { + testName = `"${hook}" hook for "${test}"`; + hookFailures[testFile] = hookFailures[testFile] || []; + hookFailures[testFile].push([suite, testName]); + } + reports.push({ + name: 'Screenshot', + testName: fullTestName(suite, testName), + type: 'image', + value: `screenshots.tar.gz!${testFile}/${file}`, + }); + }); + }) + ); + } catch (e) { + // ignore + } + + const videoFiles = await fs.readdir(videosDir); + videoFiles.forEach((videoFile) => { + const testFile = videoFile.replace(/\.mp4$/, ''); + const tests = [...getTests(testFile), ...(hookFailures[testFile] || [])]; + tests.forEach(([suite, testName]) => + reports.unshift({ + name: 'Video', + testName: fullTestName(suite, testName), + type: 'video', + value: `videos.tar.gz!${videoFile}`, + }) + ); + }); + + reports.forEach(testMetadata); +} + +report(); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ecad1e42571a..11ee583da256 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,4 +1,3 @@ -/* eslint-disable jest/no-standalone-expect, no-unused-expressions, jest/valid-expect */ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite @@ -37,22 +36,14 @@ Cypress.Commands.add( } ); -Cypress.Commands.add('preview', {}, () => { - return cy.get(`#storybook-preview-iframe`).then({ timeout: 10000 }, iframe => { - const content = iframe[0].contentDocument; - const element = content !== null ? content.documentElement : null; - - return cy - .wrap(iframe) - .should(() => { - expect(element).not.null; - - if (element !== null) { - expect(element.querySelector('#root > *')).not.null; - } - }) - .then(() => { - return element.querySelector('#root'); - }); - }); +Cypress.Commands.add('getStoryElement', {}, () => { + cy.log('getStoryElement'); + return cy + .get(`#storybook-preview-iframe`, { log: false }) + .its('0.contentDocument.body', { log: false }) + .should('not.be.empty') + .then((body) => cy.wrap(body, { log: false })) + .find('#root', { log: false }) + .should('not.be.empty') + .then((storyRoot) => cy.wrap(storyRoot, { log: false })); }); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts new file mode 100644 index 000000000000..133f544ad620 --- /dev/null +++ b/cypress/support/index.d.ts @@ -0,0 +1,17 @@ +/// + +type LoggerMethod = 'log' | 'info' | 'debug'; + +declare namespace Cypress { + interface Chainable { + /** + * Custom command to select the DOM element of a story in the canvas tab. + */ + getStoryElement(): Chainable; + + /** + * Returns the element while logging it. + */ + console(method: LoggerMethod): Chainable; + } +} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 6375e4c73dfa..cae4f81611a5 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -4,7 +4,8 @@ "baseUrl": "../node_modules", "target": "es5", "lib": ["es2017", "dom"], - "types": ["cypress"] + "types": ["cypress", "node"], + "esModuleInterop": true }, "include": ["**/*.ts"] } diff --git a/cypress/typings.d.ts b/cypress/typings.d.ts new file mode 100644 index 000000000000..9c9be08dc12b --- /dev/null +++ b/cypress/typings.d.ts @@ -0,0 +1,2 @@ +declare module 'mocha-list-tests'; +declare module 'teamcity-service-messages'; diff --git a/dev-kits/addon-decorator/package.json b/dev-kits/addon-decorator/package.json index 0b89a2b5cd75..37f236a8da04 100644 --- a/dev-kits/addon-decorator/package.json +++ b/dev-kits/addon-decorator/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-decorator", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "decorator addon for storybook", "keywords": [ "addon", @@ -24,13 +24,20 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.4.0" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/dev-kits/addon-parameter/package.json b/dev-kits/addon-parameter/package.json index 5e0dcbd343cb..86907410aa1e 100644 --- a/dev-kits/addon-parameter/package.json +++ b/dev-kits/addon-parameter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-parameter", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -24,19 +24,26 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/dev-kits/addon-parameter/src/register.tsx b/dev-kits/addon-parameter/src/register.tsx index 924fb3d1b1ba..3bd8f0d56258 100644 --- a/dev-kits/addon-parameter/src/register.tsx +++ b/dev-kits/addon-parameter/src/register.tsx @@ -14,8 +14,9 @@ export const Content = () => { () => results.length ? (
    - {results.map((i: string) => ( -
  1. {i}
  2. + {results.map((i, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  3. {i}
  4. ))}
) : null, diff --git a/dev-kits/addon-preview-wrapper/package.json b/dev-kits/addon-preview-wrapper/package.json index 6fbfe3e5515c..40e08d9126b8 100644 --- a/dev-kits/addon-preview-wrapper/package.json +++ b/dev-kits/addon-preview-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-preview-wrapper", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "preview wrapper addon for storybook", "keywords": [ "addon", @@ -24,11 +24,18 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", "react": "^16.8.3" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/dev-kits/addon-preview-wrapper/src/register.tsx b/dev-kits/addon-preview-wrapper/src/register.tsx index 6fe81015ff16..d7c3c86d3601 100644 --- a/dev-kits/addon-preview-wrapper/src/register.tsx +++ b/dev-kits/addon-preview-wrapper/src/register.tsx @@ -2,11 +2,15 @@ import React, { FunctionComponent } from 'react'; import { addons, types } from '@storybook/addons'; import { ADDON_ID } from './constants'; -const PreviewWrapper: FunctionComponent<{}> = p => ( -
- +const PreviewWrapper: FunctionComponent<{}> = (p) => ( +
{p.children}
); diff --git a/dev-kits/addon-roundtrip/package.json b/dev-kits/addon-roundtrip/package.json index a64a063bb83a..972af65ec386 100644 --- a/dev-kits/addon-roundtrip/package.json +++ b/dev-kits/addon-roundtrip/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-roundtrip", - "version": "5.3.0-rc.0", + "version": "6.0.0-beta.21", "description": "roundtrip addon for storybook", "keywords": [ "addon", @@ -24,20 +24,27 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-rc.0", - "@storybook/api": "5.3.0-rc.0", - "@storybook/client-api": "5.3.0-rc.0", - "@storybook/client-logger": "5.3.0-rc.0", - "@storybook/components": "5.3.0-rc.0", - "@storybook/core-events": "5.3.0-rc.0", - "@storybook/theming": "5.3.0-rc.0", + "@storybook/addons": "6.0.0-beta.21", + "@storybook/api": "6.0.0-beta.21", + "@storybook/client-api": "6.0.0-beta.21", + "@storybook/client-logger": "6.0.0-beta.21", + "@storybook/components": "6.0.0-beta.21", + "@storybook/core-events": "6.0.0-beta.21", + "@storybook/theming": "6.0.0-beta.21", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", - "ts-dedent": "^1.1.0" + "ts-dedent": "^1.1.1" }, "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "typesVersions": { + "<=3.5": { + "*": [ + "ts3.5/*" + ] + } + } } diff --git a/dev-kits/addon-roundtrip/src/panel.tsx b/dev-kits/addon-roundtrip/src/panel.tsx index a3349035fc46..52892f3bae0e 100644 --- a/dev-kits/addon-roundtrip/src/panel.tsx +++ b/dev-kits/addon-roundtrip/src/panel.tsx @@ -13,8 +13,9 @@ const Content = memo(({ results }: ContentProps) => ( {results.length ? (
    - {results.map((i: string) => ( -
  1. {i}
  2. + {results.map((i, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  3. {i}
  4. ))}
) : null} @@ -39,7 +40,7 @@ export const Panel = () => { title: 'setState with options', onClick: () => setState(['bar'], { persistence: 'session' }), }, - { title: 'setState with function', onClick: () => setState(s => [...s, 'baz']) }, + { title: 'setState with function', onClick: () => setState((s) => [...s, 'baz']) }, ]} />
diff --git a/docs/gatsby-browser.js b/docs/gatsby-browser.js index a77f7e3a887d..61a7ca8c9e4c 100644 --- a/docs/gatsby-browser.js +++ b/docs/gatsby-browser.js @@ -1,6 +1,6 @@ const { document } = require('global'); -exports.onRouteUpdate = location => { +exports.onRouteUpdate = (location) => { if (location.hash) { setTimeout(() => { document.querySelector(`${location.hash}`).scrollIntoView(); diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js index eb5ac5630a33..511197956edb 100644 --- a/docs/gatsby-config.js +++ b/docs/gatsby-config.js @@ -26,8 +26,11 @@ module.exports = { '/guides/guide-ember/', '/guides/guide-riot/', '/guides/guide-svelte/', + '/guides/guide-preact/', + '/guides/guide-web-components/', ], configurations: [ + '/configurations/overview/', '/configurations/options-parameter/', '/configurations/default-config/', '/configurations/custom-webpack-config/', @@ -38,6 +41,7 @@ module.exports = { '/configurations/serving-static-files/', '/configurations/env-vars/', '/configurations/theming/', + '/configurations/composition/', '/configurations/cli-options/', '/configurations/standalone-options/', ], diff --git a/docs/gatsby-node.js b/docs/gatsby-node.js index e9676a818b3b..cf022ae1cb63 100644 --- a/docs/gatsby-node.js +++ b/docs/gatsby-node.js @@ -6,8 +6,8 @@ const sm = require('sitemap'); function pagesToSitemap(pages) { return pages - .filter(p => !!p.path) - .map(p => ({ + .filter((p) => !!p.path) + .map((p) => ({ url: p.path, changefreq: 'daily', priority: 0.7, @@ -100,7 +100,7 @@ module.exports = { } // Create pages. - result.data.allMarkdownRemark.edges.forEach(edge => { + result.data.allMarkdownRemark.edges.forEach((edge) => { createPage({ path: edge.node.fields.slug, // required component: template, diff --git a/docs/src/components/Docs/Content/index.js b/docs/src/components/Docs/Content/index.js index 78e72085068a..80840bf3bfbb 100644 --- a/docs/src/components/Docs/Content/index.js +++ b/docs/src/components/Docs/Content/index.js @@ -20,11 +20,11 @@ const DocsContent = ({ title, content, editUrl, ...rest }) => (
{parse(content, { - replace: domNode => { + replace: (domNode) => { if ( domNode.name === 'pre' && domNode.children.find( - n => n.name === 'code' && n.attribs.class && n.attribs.class.match(/^language-/) + (n) => n.name === 'code' && n.attribs.class && n.attribs.class.match(/^language-/) ) ) { const language = domNode.children[0].attribs.class.replace('language-', ''); diff --git a/docs/src/components/Docs/Nav/dropdown.js b/docs/src/components/Docs/Nav/dropdown.js index 12b093b72037..c0baedc29236 100644 --- a/docs/src/components/Docs/Nav/dropdown.js +++ b/docs/src/components/Docs/Nav/dropdown.js @@ -12,7 +12,7 @@ class Nav extends React.Component { handleHeadingChange(event) { const { sections } = this.props; const selectedSectionId = event.target.value; - const section = sections.find(s => s.id === selectedSectionId); + const section = sections.find((s) => s.id === selectedSectionId); const itemId = section.items[0].id; this.changeRoute(selectedSectionId, itemId); } @@ -50,7 +50,7 @@ class Nav extends React.Component { const selectedSectionId = selectedSection || sections[0].id; const selectedItemId = selectedItem || sections[0].items[0].id; - const selectedSectionData = sections.find(section => section.id === selectedSectionId); + const selectedSectionData = sections.find((section) => section.id === selectedSectionId); const navs = selectedSectionData.items; return redirect ? ( @@ -61,9 +61,9 @@ class Nav extends React.Component {
@@ -71,9 +71,9 @@ class Nav extends React.Component {
diff --git a/docs/src/components/Docs/Nav/index.js b/docs/src/components/Docs/Nav/index.js index dd9bcf90c5d4..ecf34a14ca5d 100644 --- a/docs/src/components/Docs/Nav/index.js +++ b/docs/src/components/Docs/Nav/index.js @@ -5,11 +5,11 @@ import './style.css'; const Nav = ({ sections, selectedSectionId, selectedItemId }) => (