diff --git a/.babelrc.js b/.babelrc.js index c1e808c5983a..7e6d99f229fc 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -13,7 +13,10 @@ const withTests = { }; module.exports = { - ignore: ['./lib/codemod/src/transforms/__testfixtures__'], + ignore: [ + './lib/codemod/src/transforms/__testfixtures__', + './lib/postinstall/src/__testfixtures__', + ], presets: [ ['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }], '@babel/preset-typescript', @@ -45,13 +48,6 @@ module.exports = { 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: [ @@ -72,11 +68,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', diff --git a/.circleci/config.yml b/.circleci/config.yml index ed8d38cfe754..4b79060d74be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,7 +51,52 @@ jobs: 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" --exit-zero-on-changes --app-code="tl92yzsj6w" + 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 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" + - 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" packtracker: <<: *defaults @@ -146,11 +191,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: | @@ -181,24 +221,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: @@ -286,9 +308,6 @@ workflows: - packtracker: requires: - build - - native-smoke-tests: - requires: - - build - test: requires: - build diff --git a/.eslintignore b/.eslintignore index 4b9216815225..69e43252ce45 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,9 +15,11 @@ scripts/storage examples/ember-cli/.storybook/preview-head.html examples/official-storybook/tests/addon-jest.test.js examples/cra-ts-kitchen-sink/*.json -examples/cra-ts-kitchen-sink/public/*.json -examples/cra-ts-kitchen-sink/public/*.html - +examples/cra-ts-kitchen-sink/public/* +examples/cra-ts-essentials/*.json +examples/cra-ts-essentials/public/* +examples/rax-kitchen-sink/src/document/* +.yarn !.remarkrc.js !.babelrc.js !.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 37080fa43ba5..98dff193b3c1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,25 +1,22 @@ -const ignore = 0; -// const warn = 1; -const error = 2; - module.exports = { root: true, extends: ['@storybook/eslint-config-storybook'], rules: { 'import/extensions': [ - error, + 'error', 'never', { ignorePackages: true, md: 'always', svg: 'always', json: 'always', tag: 'always' }, ], - 'import/no-unresolved': [error, { ignore: ['@storybook'] }], - 'react/state-in-constructor': ignore, - 'react/static-property-placement': ignore, - 'react/jsx-props-no-spreading': ignore, - 'react/jsx-fragments': ignore, - '@typescript-eslint/ban-ts-ignore': ignore, - '@typescript-eslint/no-object-literal-type-assertion': ignore, + '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', + '@typescript-eslint/no-empty-function': 'off', 'react/sort-comp': [ - error, + 'error', { order: [ 'staticLifecycle', @@ -38,7 +35,7 @@ module.exports = { }, }, ], - 'max-classes-per-file': ignore, + 'max-classes-per-file': 'off', }, overrides: [ { @@ -51,30 +48,30 @@ module.exports = { 'docs/src/stories/**', ], rules: { - '@typescript-eslint/no-empty-function': ignore, - 'import/no-extraneous-dependencies': ignore, + '@typescript-eslint/no-empty-function': 'off', + 'import/no-extraneous-dependencies': 'off', }, }, - { files: '**/.storybook/config.js', rules: { 'global-require': ignore } }, + { files: '**/.storybook/config.js', rules: { 'global-require': 'off' } }, { files: ['**/*.stories.*'], rules: { - 'no-console': ignore, + 'no-console': 'off', }, }, { files: ['**/*.tsx', '**/*.ts'], rules: { - 'react/prop-types': ignore, // we should use types - 'no-dupe-class-members': ignore, // this is called overloads in typescript + 'react/prop-types': 'off', // we should use types + 'no-dupe-class-members': 'off', // this is called overloads in typescript }, }, { files: ['**/*.d.ts'], rules: { - 'vars-on-top': ignore, - 'no-var': ignore, // this is how typescript works - 'spaced-comment': ignore, + 'vars-on-top': 'off', + 'no-var': 'off', // this is how typescript works + 'spaced-comment': 'off', }, }, ], 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..70a20b7fbe5e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,9 +18,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 +28,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..a418cc267f82 100644 --- a/.github/autolabeler.yml +++ b/.github/autolabeler.yml @@ -13,9 +13,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 177c7086be99..fd0147cbc3aa 100644 --- a/.github/automention.yml +++ b/.github/automention.yml @@ -2,12 +2,10 @@ '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: rax': ['SoloJiang'] 'app: svelte': ['rixo', 'cam-stitt', 'plumpNation'] -'app: vue': ['backbone87', 'elevatebart', 'pksunkara', 'Aaron-Pool'] +'app: vue': ['backbone87', 'elevatebart', 'pksunkara', 'Aaron-Pool', 'pocka'] 'app: web-components': ['daKmoR'] 'api: addons': ['ndelangen'] 'addon: a11y': ['CodeByAlex', 'Armanio', 'jsomsanith'] diff --git a/.github/workflows/tests-puppeteer.yml b/.github/workflows/tests-puppeteer.yml new file mode 100644 index 000000000000..e7590c6e644d --- /dev/null +++ b/.github/workflows/tests-puppeteer.yml @@ -0,0 +1,32 @@ +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/.gitignore b/.gitignore index 48f61b11955e..1594748ce78d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ node_modules *.log .idea *.iml -.vscode +.vscode/* +!.vscode/launch.json *.sw* npm-shrinkwrap.json dist @@ -34,3 +35,4 @@ storybook-out built-storybooks cypress/videos cypress/screenshots +examples/ember-cli/ember-output diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..61c3bc75a05e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.yarn diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000000..daa7997bc064 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [{ + "type": "node", + "request": "launch", + "name": "official-storybook", + "runtimeExecutable": "npm", + "cwd": "${workspaceFolder}/examples/official-storybook", + "runtimeArgs": [ + "run-script", + "debug" + ], + "port": 9229, + "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/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index 167feddffcf0..540732862587 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -1,25 +1,25 @@ ## 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) | + | \* | + | + | + | + | + | + | + | + | + | + | +| [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 Native on device addon (addons/onDevice-\) +`*` - React Native on device addon (addons/onDevice-\) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0146a53dd8..f2a225f1d9d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,780 @@ +## 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)) + +### Bug Fixes + +* 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)) + +## 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)) + +### Bug Fixes + +* 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) + +Failed NPM publish + +## 5.3.0-beta.29 (December 16, 2019) + +Failed NPM publish + +## 5.3.0-beta.28 (December 16, 2019) + +Failed NPM publish + +## 5.3.0-beta.27 (December 16, 2019) + +Failed NPM publish + +## 5.3.0-beta.26 (December 16, 2019) + +Failed NPM publish + +## 5.3.0-beta.25 (December 15, 2019) + +### Bug Fixes + +* 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)) + +## 5.3.0-beta.24 (December 15, 2019) + +Failed NPM publish + +## 5.3.0-beta.23 (December 14, 2019) + +### Features + +* 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)) + +## 5.3.0-beta.22 (December 12, 2019) + +### Bug Fixes + +* 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)) + +### Dependency Upgrades + +* 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)) + +### 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)) + +### Maintenance + +* 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)) + +### 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)) + +### Maintenance + +* 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)) + +### Bug Fixes + +* 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)) + +### Bug Fixes + +* 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)) + +## 5.3.0-beta.17 (December 6, 2019) + +NPM publish failed + +## 5.3.0-beta.16 (December 5, 2019) + +### Features + +* 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)) + +### Dependency Upgrades + +* 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)) + +## 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)) + +## 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)) + +### 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)) + +## 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)) + +## 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)) + +### Maintenance + +* 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)) + +### 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)) + +### Dependency Upgrades + +* 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)) + +### 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)) + +## 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)) + +### 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)) + +### 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)) + +## 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)) + +### Bug Fixes + +* 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)) + +## 5.3.0-beta.8 (November 26, 2019) + +### Features + +* 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)) + +### Maintenance + +* 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)) + +## 5.3.0-beta.7 (November 26, 2019) + +Failed npm publish + +## 5.3.0-beta.6 (November 24, 2019) + +### Features + +* 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)) + +### 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)) + +### Dependency Upgrades + +* Miscellaneous upgrades ([#8912](https://github.com/storybookjs/storybook/pull/8912)) + +## 5.3.0-beta.5 (November 23, 2019) + +Failed npm publish + +## 5.3.0-beta.4 (November 23, 2019) + +Failed npm publish + +## 5.3.0-beta.3 (November 21, 2019) + +### 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)) + +### 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)) + +### 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)) + +## 5.3.0-beta.2 (November 19, 2019) + +### Features + +* 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)) + +### Maintenance + +* 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)) + +### 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)) + +## 5.3.0-beta.0 (November 16, 2019) + +Storybook 5.3 is in beta y'all 🔥🔥🔥 It includes: + +- 📝 Longform documentation in MDX +- 🎨 Multi-framework SB Docs (React, Vue, Angular, WC) +- 📦 Web-components framework support +- 🔼 Tri-config (experimental) + +See the [latest changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) for a full list of changes. + +## 5.3.0-alpha.47 (November 16, 2019) + +### 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)) + +### Bug Fixes + +* 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)) + +## 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)) + +### 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)) + +### Maintenance + +* 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)) + +## 5.3.0-alpha.45 (November 14, 2019) + +### Breaking Changes + +* 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)) + +### Bug Fixes + +* 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)) + +### 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)) + +### Maintenance + +* 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)) + +### 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)) + +## 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)) + +## 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)) + +### Maintenance + +* 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 638e1aade6f2..9788a94964bf 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 @@ -187,7 +187,7 @@ If you've made a change to storybook's codebase and would want this change to be ### Updating Tests -Before any contributes are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass. +Before any contributions are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass. When creating new unit test files, the tests should adhere to a particular folder structure and naming convention, as defined below. ```sh @@ -205,7 +205,7 @@ Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if ### Reviewing PRs -**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. +**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. **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. @@ -215,7 +215,7 @@ If you are looking for a way to help the project, triaging issues is a great pla ### Responding to issues -Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions). +Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone who has a similar question. Also in the future if anyone has that same question they can easily find it by searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions). ### Triaging issues @@ -273,7 +273,7 @@ If you run into trouble here, make sure your node, npm, and **_yarn_** are on th _This method is slow_ 1. `yarn bootstrap --all` -2. Have a beer 🍺 +2. Take a break 🍵 3. `yarn test` (to verify everything worked) ### Working with the kitchen sink apps @@ -283,9 +283,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 @@ -327,9 +328,7 @@ First we are going to install storybook, then we are going to link `@storybook/r You should now have a working storybook dev environment up and running. -> TODO: update this section (is already incorrect) - -Save and go to `http://localhost:9009` (or wherever storybook is running) +Save and go to `http://localhost:9011` (or wherever storybook is running) If you don't see the changes rerun `yarn storybook` again in your sandbox app diff --git a/HISTORY.md b/HISTORY.md deleted file mode 100644 index 29e854894381..000000000000 --- a/HISTORY.md +++ /dev/null @@ -1,25 +0,0 @@ -## v.Next - -- Deprecated `{ linkTo, action }` as built-in addons: . From 3.0 use them as you would [any other addon](https://storybook.js.org/addons/using-addons/). - -Before: - -```js -// .storybook/addons.js -import '@kadira/storybook/addons' - -// *.stories.js -import { linkTo, action } from '@kadira/storybook' -``` - -After: - -```js -// .storybook/addons.js -import '@storybook/addon-actions/register' -import '@storybook/addon-links/register' - -// *.stories.js -import { action } from '@storybook/addon-actions' -import { linkTo } from '@storybook/addon-links' -``` diff --git a/MIGRATION.md b/MIGRATION.md index ed9ccef19fba..6b573ada5970 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -2,9 +2,14 @@ - [Migration](#migration) - [From version 5.2.x to 5.3.x](#from-version-52x-to-53x) + - [To main.js configuration](#to-mainjs-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) + - [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) @@ -73,6 +78,82 @@ ## From version 5.2.x to 5.3.x +### To main.js configuration + +In storybook 5.3 3 new files for configuration were introduced, that replaced some previous files. + +These files are now soft-deprecated, (_they still work, but over time we will promote users to migrate_): + +- `presets.js` has been renamed to `main.js`. `main.js` is the main point of configuration for storybook. +- `config.js` has been renamed to `preview.js`. `preview.js` configures the "preview" iframe that renders your components. +- `addons.js` has been renamed to `manager.js`. `manager.js` configures Storybook's "manager" UI that wraps the preview, and also configures addons panel. + +#### Using main.js + +`main.js` is now the main point of configuration for Storybook. This is what a basic `main.js` looks like: + +```js +module.exports = { + stories: ['../**/*.stories.js'], + addons: ['@storybook/addon-knobs'], +}; +``` + +You remove all "register" import from `addons.js` and place them inside the array. You can also safely remove the `/register` suffix from these entries, for a cleaner, more readable configuration. If this means `addons.js` is now empty for you, it's safe to remove. + +Next you remove the code that imports/requires all your stories from `config.js`, and change it to a glob-pattern and place that glob in the `stories` array. If this means `config.js` is empty, it's safe to remove. + +If you had a `presets.js` file before you can add the array of presets to the main.js file and remove `presets.js` like so: + +```js +module.exports = { + stories: ['../**/*.stories.js'], + addons: [ + '@storybook/preset-create-react-app' + { + name: '@storybook/addon-docs', + options: { configureJSX: true } + } + ], +}; +``` + +By default, adding a package to the `addons` array will first try to load its `preset` entry, then its `register` entry, and finally, it will just assume the package itself is a preset. + +If you want to load a specific package entry, for example you want to use `@storybook/addon-docs/register`, you can also include that in the addons array and Storybook will do the right thing. + +#### Using preview.js + +If after migrating the imports/requires of your stories to `main.js` you're left with some code in `config.js` it's likely the usage of `addParameters` & `addDecorator`. + +This is fine, rename `config.js` to `preview.js`. + +This file can also be used to inject global stylesheets, fonts etc, into the preview bundle. + +#### Using manager.js + +If you are setting storybook options in `config.js`, especially `theme`, you should migrate it to `manager.js`: + +```js +import { addons } from '@storybook/addons'; +import { create } from '@storybook/theming/create'; + +const theme = create({ + base: 'light', + brandTitle: 'My custom title', +}); + +addons.setConfig({ + panelPosition: 'bottom', + theme, +}); +``` + +This makes storybook load and use the theme in the manager directly. +This allows for richer theming in the future, and has a much better performance! + +> If you're using addon-docs, you should probably not do this. Docs uses the theme as well, but this change makes the theme inaccessible to addon-docs. We'll address this in 6.0.0. + ### Create React App preset You can now move to the new preset for [Create React App](https://create-react-app.dev/). The in-built preset for Create React App will be disabled in Storybook 6.0. @@ -97,17 +178,62 @@ To avoid that now you have to manually pass asyncStorage to React Native Storybo Solution: -- Use `require('@react-native-community/async-storage')` for React Native v0.59 and above. +- Use `require('@react-native-community/async-storage').AsyncStorage` for React Native v0.59 and above. - Use `require('react-native').AsyncStorage` for React Native v0.58 or below. - Use `null` to disable Async Storage completely. ```javascript getStorybookUI({ ... - asyncStorage: require('@react-native-community/async-storage') || require('react-native').AsyncStorage || null + asyncStorage: require('@react-native-community/async-storage').AsyncStorage || require('react-native').AsyncStorage || null +}); +``` + +The benefit of using Async Storage is so that when users refresh the app, Storybook can open their last visited story. + +### Deprecate displayName parameter + +In 5.2, the story parameter `displayName` was introduced as a publicly visible (but internal) API. Storybook's Component Story Format (CSF) loader used it to modify a story's display name independent of the story's `name`/`id` (which were coupled). + +In 5.3, the CSF loader decouples the story's `name`/`id`, which means that `displayName` is no longer necessary. Unfortunately, this is a breaking change for any code that uses the story `name` field. Storyshots relies on story `name`, and the appropriate migration is to simply update your snapshots. Apologies for the inconvenience! + +### Unified docs preset + +Addon-docs configuration gets simpler in 5.3. In 5.2, each framework had its own preset, e.g. `@storybook/addon-docs/react/preset`. Starting in 5.3, everybody should use `@storybook/addon-docs/preset`. + +### Simplified hierarchy separators + +We've deprecated the ability to specify the hierarchy separators (how you control the grouping of story kinds in the sidebar). From Storybook 6.0 we will have a single separator `/`, which cannot be configured. + +If you are currently using using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, we provide a codemod, [`upgrade-hierarchy-separators`](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#upgrade-hierarchy-separators), that can be used to rename all your components. + +``` +yarn sb migrate upgrade-hierarchy-separators --glob="*.stories.js" +``` + +If you were using `|` and wish to keep the "root" behavior, use the `showRoots: true` option to re-enable roots: + +```js +addParameters({ + options: { + showRoots: true, + }, }); ``` +NOTE: it is no longer possible to have some stories with roots and others without. If you want to keep the old behavior, simply add a root called "Others" to all your previously unrooted stories. + +### Addon StoryShots Puppeteer uses external puppeteer + +To give you more control on the Chrome version used when running StoryShots Puppeteer, `puppeteer` is no more included in the addon dependencies. So you can now pick the version of `puppeteer` you want and set it in your project. + +If you want the latest version available just run: +```sh +yarn add puppeteer --dev +OR +npm install puppeteer --save-dev +``` + ## From version 5.1.x to 5.2.x ### Source-loader @@ -156,7 +282,7 @@ For example, here's how to sort by story ID using `storySort`: addParameters({ options: { storySort: (a, b) => - a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, { numeric: true }), + a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), }, }); ``` diff --git a/README.md b/README.md index 6b44d241b354..6e431acf9e3c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Storybook is a development environment for UI components. It allows you to browse a component library, view the different states of each component, and interactively develop and test components.
- +

@@ -91,7 +91,7 @@ If you'd rather set up your project manually, take a look at our [Slow Start Gui Once it's installed, you can `npm run storybook` and it will run the development server on your local machine, and give you a URL to browse some sample stories. -**Storybook v2.x migration note**: +**Storybook v2.x migration note**: If you're using Storybook v2.x and want to shift to 4.x version the easiest way is: ```sh @@ -115,7 +115,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl | [React Native](app/react-native) | - | [![React Native](https://img.shields.io/npm/dm/@storybook/react-native.svg)](app/react-native) | | [Vue](app/vue) | [v5.1.0](https://storybooks-vue.netlify.com/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) | | [Angular](app/angular) | [v5.1.0](https://storybooks-angular.netlify.com/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) | -| [Polymer](app/polymer) | [v5.1.0](https://storybooks-polymer.netlify.com/) | [![Polymer](https://img.shields.io/npm/dm/@storybook/polymer.svg)](app/polymer) | +| [Marionette.js](app/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) | | [Mithril](app/mithril) | [v5.1.0](https://storybooks-mithril.netlify.com/) | [![Mithril](https://img.shields.io/npm/dm/@storybook/mithril.svg)](app/mithril) | | [Marko](app/marko) | [v5.1.0](https://storybooks-marko.netlify.com/) | [![Marko](https://img.shields.io/npm/dm/@storybook/marko.svg)](app/marko) | | [HTML](app/html) | [v5.1.0](https://storybooks-html.netlify.com/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) | @@ -123,6 +123,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl | [Riot](app/riot) | [v5.1.0](https://storybooks-riot.netlify.com/) | [![Riot](https://img.shields.io/npm/dm/@storybook/riot.svg)](app/riot) | | [Ember](app/ember) | [v5.1.0](https://storybooks-ember.netlify.com/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember.svg)](app/ember) | | [Preact](app/preact) | [v5.1.0](https://storybooks-preact.netlify.com/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact.svg)](app/preact) | +| [Rax](app/rax) | [v5.1.0](https://storybooks-rax.netlify.com/) | [![Rax](https://img.shields.io/npm/dm/@storybook/rax.svg)](app/rax) | ### Sub Projects diff --git a/ROADMAP.md b/ROADMAP.md index 6a406bc2ccd7..506d84c86d2d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -9,7 +9,6 @@ + [Add a playground addon](#add-a-playground-addon) + [See multiple (or all) stories in 1 preview.](#see-multiple--or-all--stories-in-1-preview) * [Supporting other frameworks and libraries](#supporting-other-frameworks-and-libraries) - + [Polymer & Webcomponents](#polymer---webcomponents) + [Aurelia](#aurelia) * [Breaking changes](#breaking-changes) + [Addon API](#addon-api) @@ -60,10 +59,6 @@ Unfortunately, if you choose anything not from the list of [supported frameworks We want you to be able to use storybook with the framework / library of your choice. -### Polymer & Webcomponents - -Storybook for Polymer is currently in development and will support custom elements and plain HTML. - ### Aurelia We're reaching out to the Aurelia maintainers to cooperate on this. diff --git a/__mocks__/inject-decorator.ts.csf.txt b/__mocks__/inject-decorator.ts.csf.txt index 47e60534ac87..b3a3d900dccd 100644 --- a/__mocks__/inject-decorator.ts.csf.txt +++ b/__mocks__/inject-decorator.ts.csf.txt @@ -3,7 +3,9 @@ import { action } from "@storybook/addon-actions"; import { Button } from "@storybook/react/demo"; export default { - title: "Button" + title: "Button", + excludeStories: ["text"], + includeStories: /emoji.*/ }; export const text = () => ( @@ -17,3 +19,13 @@ export const emoji = () => ( ); + +export function emojiFn() { + return ( + + ) +}; diff --git a/addons/a11y/README.md b/addons/a11y/README.md index 584e49f67028..f4d086fb9f1d 100755 --- a/addons/a11y/README.md +++ b/addons/a11y/README.md @@ -14,37 +14,42 @@ First, install the addon. $ yarn add @storybook/addon-a11y --dev ``` -Add this line to your `addons.js` file (create this file inside your storybook config directory if needed). +Add this line to your `main.js` file (create this file inside your storybook config directory if needed). ```js -import '@storybook/addon-a11y/register'; +module.exports = { + addons: ['@storybook/addon-a11y/register'] +} ``` import the `withA11y` decorator to check your stories for violations within your components. ```js import React from 'react'; -import { storiesOf, addDecorator } from '@storybook/react'; + import { withA11y } from '@storybook/addon-a11y'; -// should only be added once -// best place is in config.js -addDecorator(withA11y) - -storiesOf('button', module) - .add('Accessible', () => ( - - )) - .add('Inaccessible', () => ( - - )); +export default { + title: 'button', + decorators: [withA11y], +}; + +export const accessible = () => ( + +); + +export const inaccessible = () => ( + +); ``` -For more customizability. Use the `addParameters` function to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). +## Parameters + +For more customizability use parameters to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). You can override these options [at story level too](https://storybook.js.org/docs/configurations/options-parameter/#per-story-options). ```js @@ -53,27 +58,34 @@ import { storiesOf, addDecorator, addParameters } from '@storybook/react'; import { withA11y } from '@storybook/addon-a11y'; -addDecorator(withA11y) -addParameters({ - a11y: { - // ... axe options - element: '#root', // optional selector which element to inspect - config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1) - options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) +export default { + title: 'button', + decorators: [withA11y], + parameters: { + a11y: { + // optional selector which element to inspect + element: '#root', + // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1) + 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, + }, }, -}); - -storiesOf('button', module) - .add('Accessible', () => ( - - )) - .add('Inaccessible', () => ( - - )); +}; + +export const accessible = () => ( + +); + +export const inaccessible = () => ( + +); ``` ## Roadmap diff --git a/addons/a11y/package.json b/addons/a11y/package.json index 7625a2132312..9dd357621f18 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "a11y addon for storybook", "keywords": [ "a11y", @@ -22,7 +22,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -33,12 +32,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "axe-core": "^3.3.2", "core-js": "^3.0.1", "global": "^4.3.2", @@ -51,9 +50,11 @@ "util-deprecate": "^1.0.2" }, "devDependencies": { - "@types/react-redux": "^7.0.6" + "@types/react-redux": "^7.0.6", + "@types/webpack-env": "^1.15.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/a11y/src/components/A11YPanel.test.js b/addons/a11y/src/components/A11YPanel.test.js index 5c57664c573c..9bc88673ab01 100644 --- a/addons/a11y/src/components/A11YPanel.test.js +++ b/addons/a11y/src/components/A11YPanel.test.js @@ -1,19 +1,18 @@ import React from 'react'; import { mount } from 'enzyme'; +import { EventEmitter } from 'events'; 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 emitter = new EventEmitter(); + jest.spyOn(emitter, 'emit'); + jest.spyOn(emitter, 'on'); + jest.spyOn(emitter, 'off'); + return emitter; } const axeResult = { @@ -63,7 +62,7 @@ function ThemedA11YPanel(props) { } describe('A11YPanel', () => { - it('should register STORY_RENDERED and RESULT updater on mount', () => { + it('should register event listener on mount', () => { // given const api = createApi(); expect(api.on).not.toHaveBeenCalled(); @@ -72,143 +71,92 @@ describe('A11YPanel', () => { mount(); // then - expect(api.on.mock.calls.length).toBe(2); - expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED); - expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT); + expect(api.on.mock.calls.length).toBe(3); + expect(api.on.mock.calls[0][0]).toBe(EVENTS.RESULT); + expect(api.on.mock.calls[1][0]).toBe(EVENTS.ERROR); + expect(api.on.mock.calls[2][0]).toBe(EVENTS.MANUAL); }); - it('should request a run on tab activation', () => { + it('should deregister event listener on unmount', () => { // 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 and RESULT updater on unmount', () => { - // given - const api = createApi(); - const wrapper = mount(); expect(api.off).not.toHaveBeenCalled(); // when + const wrapper = mount(); wrapper.unmount(); // then - expect(api.off.mock.calls.length).toBe(2); - expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED); - expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT); + expect(api.off.mock.calls.length).toBe(3); + expect(api.off.mock.calls[0][0]).toBe(EVENTS.RESULT); + expect(api.off.mock.calls[1][0]).toBe(EVENTS.ERROR); + expect(api.off.mock.calls[2][0]).toBe(EVENTS.MANUAL); }); - it('should update run result', () => { + it('should handle "initial" status', () => { // 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); + expect(api.emit).not.toHaveBeenCalled(); + expect(wrapper.text()).toMatch(/Initializing/); }); - it('should NOT request run on inactive tab', () => { + it('should handle "manual" status', () => { // given const api = createApi(); - mount(); - const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1]; - expect(api.emit).not.toHaveBeenCalled(); + const wrapper = mount(); // when - request(); + api.emit(EVENTS.MANUAL, true); + wrapper.update(); // then - expect(api.emit).not.toHaveBeenCalled(); + expect(wrapper.text()).toMatch(/Manually run the accessibility scan/); + expect(api.emit).not.toHaveBeenCalledWith(EVENTS.REQUEST); }); - it('should render report', () => { + it('should handle "running" status', () => { // given const api = createApi(); const wrapper = mount(); - const onUpdate = api.on.mock.calls.find(([event]) => event === EVENTS.RESULT)[1]; // when - onUpdate(axeResult); + api.emit(EVENTS.MANUAL, false); + wrapper.update(); // then - expect(wrapper.find(A11YPanel)).toMatchSnapshot(); + expect(wrapper.text()).toMatch(/Please wait while the accessibility scan is running/); + expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST); }); - it("should render loader when it's running", () => { + it('should handle "ran" status', () => { // given const api = createApi(); const wrapper = mount(); - const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1]; // when - request(); + api.emit(EVENTS.RESULT, axeResult); 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(); + expect( + wrapper + .find('button') + .last() + .text() + .trim() + ).toBe('Tests completed'); + expect(wrapper.find('Tabs').prop('tabs').length).toBe(3); + expect(wrapper.find('Tabs').prop('tabs')[0].label.props.children).toEqual([1, ' Violations']); + expect(wrapper.find('Tabs').prop('tabs')[1].label.props.children).toEqual([1, ' Passes']); + expect(wrapper.find('Tabs').prop('tabs')[2].label.props.children).toEqual([1, ' Incomplete']); }); - it('should NOT anything when tab is not active', () => { + it('should handle inactive state', () => { // given const api = createApi(); @@ -216,7 +164,7 @@ describe('A11YPanel', () => { const wrapper = mount(); // then - expect(wrapper.find('ScrollArea').length).toBe(0); - expect(wrapper.find('ActionBar').length).toBe(0); + expect(wrapper.text()).toBe(''); + expect(api.emit).not.toHaveBeenCalled(); }); }); diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index 5f0c1fffb61f..de8215a1b6ab 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -1,8 +1,8 @@ +/* eslint-disable react/destructuring-assignment,default-case,consistent-return,no-case-declarations */ import React, { Component, Fragment } from 'react'; import { styled } from '@storybook/theming'; -import { STORY_RENDERED } from '@storybook/core-events'; import { ActionBar, Icons, ScrollArea } from '@storybook/components'; import { AxeResults, Result } from 'axe-core'; @@ -20,19 +20,15 @@ export enum RuleType { INCOMPLETION, } -const Icon = styled(Icons)( - { - height: '12px', - width: '12px', - marginRight: '4px', - }, - ({ 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,26 +42,52 @@ const Incomplete = styled.span<{}>(({ theme }) => ({ color: theme.color.warning, })); -const Loader = styled(({ className }) => ( -

- Please wait while the accessibility scan is running - ... -
-))({ +const Centered = styled.span<{}>({ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', }); -Loader.displayName = 'Loader'; -interface A11YPanelState { - status: string; +interface InitialState { + status: 'initial'; +} + +interface ManualState { + status: 'manual'; +} + +interface RunningState { + status: 'running'; +} + +interface RanState { + status: 'ran'; passes: Result[]; violations: Result[]; incomplete: Result[]; } +interface ReadyState { + status: 'ready'; + passes: Result[]; + violations: Result[]; + incomplete: Result[]; +} + +interface ErrorState { + status: 'error'; + error: unknown; +} + +type A11YPanelState = + | InitialState + | ManualState + | RunningState + | RanState + | ReadyState + | ErrorState; + interface A11YPanelProps { active: boolean; api: API; @@ -73,17 +95,15 @@ interface A11YPanelProps { export class A11YPanel extends Component { state: A11YPanelState = { - status: 'ready', - passes: [], - violations: [], - incomplete: [], + status: 'initial', }; componentDidMount() { const { api } = this.props; - api.on(STORY_RENDERED, this.request); - api.on(EVENTS.RESULT, this.onUpdate); + api.on(EVENTS.RESULT, this.onResult); + api.on(EVENTS.ERROR, this.onError); + api.on(EVENTS.MANUAL, this.onManual); } componentDidUpdate(prevProps: A11YPanelProps) { @@ -93,17 +113,18 @@ export class A11YPanel extends Component { 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.RESULT, this.onResult); + api.off(EVENTS.ERROR, this.onError); + api.off(EVENTS.MANUAL, this.onManual); } - onUpdate = ({ passes, violations, incomplete }: AxeResults) => { + onResult = ({ passes, violations, incomplete }: AxeResults) => { this.setState( { status: 'ran', @@ -124,50 +145,74 @@ export class A11YPanel extends Component { ); }; - 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()); - } - ); + onError = (error: unknown) => { + this.setState({ + status: 'error', + error, + }); + }; + + onManual = (manual: boolean) => { + if (manual) { + this.setState({ + status: 'manual', + }); + } else { + this.request(); } }; + request = () => { + const { api } = this.props; + this.setState( + { + status: 'running', + }, + () => { + api.emit(EVENTS.REQUEST); + // removes all elements from the redux map in store from the previous panel + store.dispatch(clearElements()); + } + ); + }; + render() { - const { passes, violations, incomplete, status } = this.state; const { active } = this.props; + if (!active) return null; - let actionTitle; - if (status === 'ready') { - actionTitle = 'Rerun tests'; - } else if (status === 'running') { - actionTitle = ( - - Running test - - ); - } else if (status === 'ran') { - actionTitle = ( - - Tests completed - - ); - } - - return active ? ( - - - {status === 'running' ? ( - + switch (this.state.status) { + case 'initial': + return Initializing...; + case 'manual': + return ( + + Manually run the accessibility scan. + + + ); + case 'running': + return ( + + Please wait while the accessibility scan is running + ... + + ); + case 'ready': + case 'ran': + const { passes, violations, incomplete, status } = this.state; + const actionTitle = + status === 'ready' ? ( + 'Rerun tests' ) : ( + + Tests completed + + ); + return ( + { ]} /> - )} - - - - ) : null; + + + ); + case 'error': + const { error } = this.state; + return ( + + The accessibility scan encountered an error. +
+ {error} +
+ ); + } } } diff --git a/addons/a11y/src/components/ColorBlindness.tsx b/addons/a11y/src/components/ColorBlindness.tsx index 9d643a797f4a..87e98989513d 100644 --- a/addons/a11y/src/components/ColorBlindness.tsx +++ b/addons/a11y/src/components/ColorBlindness.tsx @@ -18,7 +18,7 @@ const getFilter = (filter: string | null) => { return `url('#${filter}')`; }; -const ColorIcon = styled.span( +const ColorIcon = styled.span<{ filter: string | null }>( { background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)', borderRadius: '1rem', @@ -26,7 +26,7 @@ const ColorIcon = styled.span( height: '1rem', width: '1rem', }, - ({ filter }: { filter: string | null }) => ({ + ({ filter }) => ({ filter: getFilter(filter), }), ({ theme }) => ({ @@ -80,16 +80,14 @@ const getColorList = (active: string | null, set: (i: string | null) => void): L ]; export const ColorBlindness: FunctionComponent = () => { - const [active, setActiveState] = useState(null); + const [active, setActiveState] = useState(null); const setActive = (activeState: string | null): void => { const iframe = getIframe(); if (iframe) { iframe.style.filter = getFilter(activeState); - setActiveState({ - active: activeState, - }); + setActiveState(activeState); } else { logger.error('Cannot find Storybook iframe'); } diff --git a/addons/a11y/src/components/Report/Elements.tsx b/addons/a11y/src/components/Report/Elements.tsx index ce713ec948a8..d2fc529b8d05 100644 --- a/addons/a11y/src/components/Report/Elements.tsx +++ b/addons/a11y/src/components/Report/Elements.tsx @@ -15,15 +15,15 @@ const ItemTitle = styled.span<{}>(({ theme }) => ({ borderBottom: `1px solid ${theme.appBorderColor}`, width: '100%', display: 'flex', - paddingBottom: '6px', - marginBottom: '6px', + paddingBottom: 6, + marginBottom: 6, justifyContent: 'space-between', })); const HighlightToggleElement = styled.span({ fontWeight: 'normal', alignSelf: 'center', - paddingRight: '15px', + paddingRight: 15, input: { margin: 0 }, }); diff --git a/addons/a11y/src/components/Report/HighlightToggle.tsx b/addons/a11y/src/components/Report/HighlightToggle.tsx index 5d075d8e075a..64c4d0cb7123 100644 --- a/addons/a11y/src/components/Report/HighlightToggle.tsx +++ b/addons/a11y/src/components/Report/HighlightToggle.tsx @@ -5,24 +5,24 @@ import { styled, themes, convert } from '@storybook/theming'; import memoize from 'memoizerific'; import { NodeResult } from 'axe-core'; +import { Dispatch } from 'redux'; import { RuleType } from '../A11YPanel'; import { addElement } from '../../redux-config'; import { IFRAME } from '../../constants'; -export class HighlightedElementData { +export interface HighlightedElementData { originalOutline: string; - isHighlighted: boolean; } interface ToggleProps { elementsToHighlight: NodeResult[]; type: RuleType; - addElement?: (data: any) => void; - highlightedElementsMap?: Map; + addElement: (data: any) => void; + highlightedElementsMap: Map; isToggledOn?: boolean; toggleId?: string; - indeterminate?: boolean; + indeterminate: boolean; } enum CheckBoxStates { @@ -48,7 +48,7 @@ function getElementBySelectorPath(elementPath: string): HTMLElement { if (iframe && iframe.contentDocument && elementPath) { return iframe.contentDocument.querySelector(elementPath); } - return null; + return (null as unknown) as HTMLElement; } function setElementOutlineStyle(targetElement: HTMLElement, outlineStyle: string): void { @@ -64,7 +64,7 @@ function areAllRequiredElementsHighlighted( const targetElement = getElementBySelectorPath(item.target[0]); return ( highlightedElementsMap.has(targetElement) && - highlightedElementsMap.get(targetElement).isHighlighted + (highlightedElementsMap.get(targetElement) as HighlightedElementData).isHighlighted ); }).length; @@ -76,7 +76,7 @@ function areAllRequiredElementsHighlighted( : CheckBoxStates.INDETERMINATE; } -function mapDispatchToProps(dispatch: any) { +function mapDispatchToProps(dispatch: Dispatch) { return { addElement: (data: { element: HTMLElement; data: HighlightedElementData }) => dispatch(addElement(data)), @@ -112,7 +112,7 @@ class HighlightToggle extends Component { }); } - componentDidUpdate(prevProps: Readonly): void { + componentDidUpdate(): void { const { indeterminate } = this.props; if (this.checkBoxRef.current) { this.checkBoxRef.current.indeterminate = indeterminate; @@ -126,8 +126,9 @@ class HighlightToggle extends Component { if (!highlightedElementsMap.has(targetElement)) { return; } - const { originalOutline } = highlightedElementsMap.get(targetElement); - const { isHighlighted } = highlightedElementsMap.get(targetElement); + const { originalOutline, isHighlighted } = highlightedElementsMap.get( + targetElement + ) as HighlightedElementData; const { isToggledOn } = this.props; if ((isToggledOn && isHighlighted) || (!isToggledOn && !isHighlighted)) { const addHighlight = !isToggledOn && !isHighlighted; @@ -151,7 +152,7 @@ class HighlightToggle extends Component { if (highlightedElementsMap.has(targetElement)) { setElementOutlineStyle( targetElement, - highlightedElementsMap.get(targetElement).originalOutline + highlightedElementsMap.get(targetElement)!.originalOutline ); } } @@ -162,9 +163,7 @@ class HighlightToggle extends Component { originalOutline: string ): void { const { addElement: localAddElement } = this.props; - const data: HighlightedElementData = new HighlightedElementData(); - data.isHighlighted = isHighlighted; - data.originalOutline = originalOutline; + const data: HighlightedElementData = { isHighlighted, originalOutline }; const payload = { element: targetElement, highlightedElementData: data }; localAddElement(payload); } @@ -185,7 +184,4 @@ class HighlightToggle extends Component { } } -export default connect( - mapStateToProps, - mapDispatchToProps -)(HighlightToggle); +export default connect(mapStateToProps, mapDispatchToProps)(HighlightToggle); diff --git a/addons/a11y/src/components/Report/Info.tsx b/addons/a11y/src/components/Report/Info.tsx index 5b1995fe4149..743a617c02f4 100644 --- a/addons/a11y/src/components/Report/Info.tsx +++ b/addons/a11y/src/components/Report/Info.tsx @@ -4,14 +4,14 @@ import { styled } from '@storybook/theming'; import { Result } from 'axe-core'; const Wrapper = styled.div({ - padding: '12px', - marginBottom: '10px', + padding: 12, + marginBottom: 10, }); const Help = styled.p({ margin: '0 0 12px', }); const Link = styled.a({ - marginTop: '12px', + marginTop: 12, textDecoration: 'underline', color: 'inherit', display: 'block', diff --git a/addons/a11y/src/components/Report/Item.tsx b/addons/a11y/src/components/Report/Item.tsx index 9122ff53c674..2ece16f74a15 100644 --- a/addons/a11y/src/components/Report/Item.tsx +++ b/addons/a11y/src/components/Report/Item.tsx @@ -24,7 +24,7 @@ const Icon = styled(Icons)(({ theme }) => ({ width: 10, minWidth: 10, color: theme.color.mediumdark, - marginRight: '10px', + marginRight: 10, transition: 'transform 0.1s ease-in-out', alignSelf: 'center', display: 'inline-flex', @@ -49,7 +49,7 @@ const HeaderBar = styled.div<{}>(({ theme }) => ({ const HighlightToggleElement = styled.span({ fontWeight: 'normal', float: 'right', - marginRight: '15px', + marginRight: 15, alignSelf: 'center', input: { margin: 0 }, }); diff --git a/addons/a11y/src/components/Report/Rules.tsx b/addons/a11y/src/components/Report/Rules.tsx index a7a61c3a6719..b1485ef4fa60 100644 --- a/addons/a11y/src/components/Report/Rules.tsx +++ b/addons/a11y/src/components/Report/Rules.tsx @@ -1,61 +1,41 @@ 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', flexDirection: 'column', - paddingBottom: '4px', - paddingRight: '4px', - paddingTop: '4px', + paddingBottom: 4, + paddingRight: 4, + paddingTop: 4, 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', - marginBottom: elementWidth > maxWidthBeforeBreak ? '6px' : '12px', + marginBottom: elementWidth > maxWidthBeforeBreak ? 6 : 12, display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block', }; }); -const StyledBadge = styled(Badge)(({ status }: { status: string }) => ({ +const StyledBadge = styled(Badge)({ padding: '2px 8px', - marginBottom: '3px', - minWidth: '65px', + marginBottom: 3, + minWidth: 65, maxWidth: 'fit-content', width: '100%', textAlign: 'center', -})); +}); const Message = styled.div({ - paddingLeft: '6px', - paddingRight: '23px', + 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: '16px', - width: '16px', - }, -})); - export enum ImpactValue { MINOR = 'minor', MODERATE = 'moderate', @@ -94,7 +74,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 62968af6aa31..e11245f55620 100644 --- a/addons/a11y/src/components/Report/Tags.tsx +++ b/addons/a11y/src/components/Report/Tags.tsx @@ -11,7 +11,7 @@ const Wrapper = styled.div({ const Item = styled.div<{}>(({ theme }) => ({ margin: '0 6px', - padding: '5px', + padding: 5, border: `1px solid ${theme.appBorderColor}`, borderRadius: theme.appBorderRadius, })); diff --git a/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap b/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap index bdef5cabcc02..e0c37bf518cd 100644 --- a/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap +++ b/addons/a11y/src/components/Report/__snapshots__/HighlightToggle.test.js.snap @@ -84,6 +84,7 @@ exports[`HighlightToggle component should match snapshot 1`] = ` "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 */", diff --git a/addons/a11y/src/components/Tabs.tsx b/addons/a11y/src/components/Tabs.tsx index 4ebeac7c4900..b16bd1745991 100644 --- a/addons/a11y/src/components/Tabs.tsx +++ b/addons/a11y/src/components/Tabs.tsx @@ -1,6 +1,6 @@ import React, { Component, SyntheticEvent } 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'; @@ -18,12 +18,12 @@ const Container = styled.div({ const HighlightToggleLabel = styled.label<{}>(({ theme }) => ({ cursor: 'pointer', userSelect: 'none', - marginBottom: '3px', - marginRight: '3px', + marginBottom: 3, + marginRight: 3, color: theme.color.dark, })); -const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => { +const GlobalToggle = styled.div<{ elementWidth: number }>(({ elementWidth }) => { const maxWidthBeforeBreak = 450; return { cursor: 'pointer', @@ -31,7 +31,7 @@ const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => padding: elementWidth > maxWidthBeforeBreak ? '12px 15px 10px 0' : '12px 0px 3px 12px', height: '40px', border: 'none', - marginTop: elementWidth > maxWidthBeforeBreak ? '-40px' : '0px', + marginTop: elementWidth > maxWidthBeforeBreak ? -40 : 0, float: elementWidth > maxWidthBeforeBreak ? 'right' : 'left', display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block', alignItems: 'center', @@ -39,15 +39,15 @@ const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => borderBottom: elementWidth > maxWidthBeforeBreak ? 'none' : '1px solid rgba(0,0,0,.1)', input: { - marginLeft: '10', - marginRight: '0', - marginTop: '0', - marginBottom: '0', + marginLeft: 10, + marginRight: 0, + marginTop: 0, + marginBottom: 0, }, }; }); -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, @@ -99,7 +99,7 @@ interface TabsState { } 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 { @@ -109,7 +109,7 @@ export class Tabs extends Component { onToggle = (event: SyntheticEvent) => { this.setState({ - active: parseInt(event.currentTarget.getAttribute('data-index'), 10), + active: parseInt(event.currentTarget.getAttribute('data-index') || '', 10), }); // removes all elements from the redux map in store from the previous panel store.dispatch(clearElements()); 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 24e12e704048..000000000000 --- a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap +++ /dev/null @@ -1,1112 +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 d5b153986461..79ef7446a159 100755 --- a/addons/a11y/src/constants.ts +++ b/addons/a11y/src/constants.ts @@ -6,5 +6,7 @@ export const ADD_ELEMENT = 'ADD_ELEMENT'; export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS'; const RESULT = `${ADDON_ID}/result`; const REQUEST = `${ADDON_ID}/request`; +const ERROR = `${ADDON_ID}/error`; +const MANUAL = `${ADDON_ID}/manual`; -export const EVENTS = { RESULT, REQUEST }; +export const EVENTS = { RESULT, REQUEST, ERROR, MANUAL }; diff --git a/addons/a11y/src/index.ts b/addons/a11y/src/index.ts index 20c8fc717267..f96d86cb0d07 100644 --- a/addons/a11y/src/index.ts +++ b/addons/a11y/src/index.ts @@ -11,8 +11,10 @@ interface Setup { element?: ElementContext; config: Spec; options: RunOptions; + manual: boolean; } -let setup: Setup = { element: null, config: {}, options: {} }; + +let setup: Setup = { element: undefined, config: {}, options: {}, manual: false }; const getElement = () => { const storyRoot = document.getElementById('story-root'); @@ -39,7 +41,8 @@ const run = (element: ElementContext, config: Spec, options: RunOptions) => { restoreScroll: true, } as RunOptions) // cast to RunOptions is necessary because axe types are not up to date ) - .then(report); + .then(report) + .catch(error => addons.getChannel().emit(EVENTS.ERROR, String(error))); }); }; @@ -47,14 +50,26 @@ 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) { - setup = parameters as Setup; + if (storedDefaultSetup === null) { + storedDefaultSetup = { ...setup }; + } + Object.assign(setup, parameters as Partial); + } else if (storedDefaultSetup !== null) { + Object.assign(setup, storedDefaultSetup); + storedDefaultSetup = null; } - addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options)); + + addons + .getChannel() + .on(EVENTS.REQUEST, () => run(setup.element as ElementContext, setup.config, setup.options)); + addons.getChannel().emit(EVENTS.MANUAL, setup.manual); return getStory(context); }, diff --git a/addons/a11y/tsconfig.json b/addons/a11y/tsconfig.json index 8876bb6737a1..99a91ffc487b 100644 --- a/addons/a11y/tsconfig.json +++ b/addons/a11y/tsconfig.json @@ -2,7 +2,13 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env"] + "types": ["webpack-env"], + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true }, "include": [ "src/**/*" diff --git a/addons/actions/README.md b/addons/actions/README.md index f1dc41ad1e00..d7cf0be887c3 100644 --- a/addons/actions/README.md +++ b/addons/actions/README.md @@ -14,10 +14,12 @@ Install: npm i -D @storybook/addon-actions ``` -Then, add following content to `.storybook/addons.js` +Then, add following content to `.storybook/main.js` ```js -import '@storybook/addon-actions/register'; +module.exports = { + addons: ['@storybook/addon-actions/register'] +} ``` Import the `action` function and use it to create actions handlers. When creating action handlers, provide a **name** to make it easier to identify. @@ -25,14 +27,17 @@ Import the `action` function and use it to create actions handlers. When creatin > _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybookjs/storybook-addon-actions/issues/29#issuecomment-288274794)_ ```js -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; - import Button from './button'; -storiesOf('Button', module).add('default view', () => ( +export default { + title: 'Button', + component: Button, +}; + +export const defaultView = () => ( -)); +); ``` ## Multiple actions @@ -40,22 +45,27 @@ storiesOf('Button', module).add('default view', () => ( If your story requires multiple actions, it may be convenient to use `actions` to create many at once: ```js -import { storiesOf } from '@storybook/react'; import { actions } from '@storybook/addon-actions'; - import Button from './button'; +export default { + title: 'Button', + component: Button, +}; + // This will lead to { onClick: action('onClick'), ... } const eventsFromNames = actions('onClick', 'onMouseOver'); // This will lead to { onClick: action('clicked'), ... } const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' }); -storiesOf('Button', module) - .add('default view', () => ) - .add('default view, different actions', () => ( - - )); +export const first = () => ( + +); + +export const second = () => ( + +); ``` ## Action Decorators @@ -66,14 +76,18 @@ If you wish to process action data before sending them over to the logger, you c ```js import { decorate } from '@storybook/addon-actions'; - import Button from './button'; +export default { + title: 'Button', + component: Button, +}; + const firstArg = decorate([args => args.slice(0, 1)]); -storiesOf('Button', module).add('default view', () => ( +export const first = () => ( -)); +); ``` ## Configuration @@ -88,7 +102,7 @@ The action logger, by default, will log all actions fired during the lifetime of 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 `config.js` file. +To apply the configuration globally use the `configureActions` function in your `preview.js` file. ```js import { configureActions } from '@storybook/addon-actions'; @@ -97,14 +111,14 @@ configureActions({ depth: 100, // Limit the number of items logged into the actions panel limit: 20, -}) +}); ``` To apply the configuration per action use: ```js action('my-action', { depth: 5, -}) +}); ``` ### Available Options @@ -121,15 +135,15 @@ You can define action handles in a declarative way using `withActions` decorator 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 { storiesOf } from '@storybook/html'; import { withActions } from '@storybook/addon-actions'; +import Button from './button'; + +export default { + title: 'Button', + decorators: [withActions('mouseover', 'click .btn')] +}; -storiesOf('button', module) - // Log mouseovers on entire story and clicks on .btn - .addDecorator(withActions('mouseover', 'click .btn')) - .add('with actions', () => ` -
- Clicks on this button will be logged: -
- `); +export const first = () => ( + +); ``` diff --git a/addons/actions/package.json b/addons/actions/package.json index ff035ada7ec5..c1e60dd0c374 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Action Logger addon for storybook", "keywords": [ "storybook" @@ -17,7 +17,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -28,12 +27,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "fast-deep-equal": "^2.0.1", "global": "^4.3.2", @@ -44,10 +43,12 @@ "uuid": "^3.3.2" }, "devDependencies": { - "@types/lodash": "^4.14.129", - "@types/uuid": "^3.4.4" + "@types/lodash": "^4.14.149", + "@types/uuid": "^3.4.4", + "@types/webpack-env": "^1.15.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/actions/src/components/ActionLogger/style.tsx b/addons/actions/src/components/ActionLogger/style.tsx index 54abfbec1566..450e407a697c 100644 --- a/addons/actions/src/components/ActionLogger/style.tsx +++ b/addons/actions/src/components/ActionLogger/style.tsx @@ -3,7 +3,7 @@ import { opacify } from 'polished'; export const Action = styled.div({ display: 'flex', - padding: '0', + padding: 0, borderLeft: '5px solid transparent', borderBottom: '1px solid transparent', transition: 'all 0.1s', @@ -17,7 +17,7 @@ export const Counter = styled.div<{}>(({ theme }) => ({ fontWeight: theme.typography.weight.bold, lineHeight: 1, padding: '1px 5px', - borderRadius: '20px', + borderRadius: 20, margin: '2px 0px', })); diff --git a/addons/backgrounds/README.md b/addons/backgrounds/README.md index e55d1a0c2a2f..fe8554429f64 100644 --- a/addons/backgrounds/README.md +++ b/addons/backgrounds/README.md @@ -14,12 +14,14 @@ npm i -D @storybook/addon-backgrounds ## Configuration -Then create a file called `addons.js` in your storybook config. +Then create a file called `main.js` in your storybook config. Add following content to it: ```js -import '@storybook/addon-backgrounds/register'; +module.exports = { + addons: ['@storybook/addon-backgrounds/register'] +} ``` ## Usage @@ -28,19 +30,23 @@ Then write your stories like this: ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; -storiesOf('Button', module) - .addParameters({ +export default { + title: 'Button', + parameters: { backgrounds: [ { name: 'twitter', value: '#00aced', default: true }, { name: 'facebook', value: '#3b5998' }, - ], - }) - .add('with text', () => ); + ] + }, +}; + +export const defaultView = () => ( + +); ``` -You can add the backgrounds to all stories with `addParameters` in `.storybook/config.js`: +You can add the backgrounds to all stories with `addParameters` in `.storybook/preview.js`: ```js import { addParameters } from '@storybook/react'; // <- or your storybook framework @@ -51,48 +57,53 @@ addParameters({ { name: 'facebook', value: '#3b5998' }, ], }); - -// should be before configure() ``` If you want to override backgrounds for a single story or group of stories, pass the `backgrounds` parameter: ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; - -storiesOf('Button', module) - .add('with text', () => , { - backgrounds: [{ - name: 'red', value: 'rgba(255, 0, 0)', - }] - }); + +export default { + title: 'Button', +} + +export const defaultView = () => ( + +); +defaultView.story = { + parameters: { + backgrounds: [ + { 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: ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; -storiesOf('Button', module) - .add('example 1', () => , { - backgrounds: [], - }); +export default { + title: 'Button', +} -storiesOf('Button', module) - .add('example 2', () => , { - backgrounds: { disable: true }, - }); -``` - -## Events - -If you want to react to a background change—for instance to implement some custom logic in your Storybook—you can subscribe to the `storybook/background/update` event. It will be emitted when the user changes the background. - -```js -addonAPI.getChannel().on('storybook/background/update', (bg) => { - console.log('Background color', bg.selected); - console.log('Background name', bg.name); -}); +export const noBackgrounds = () => ( + +); +noBackgrounds.story = { + parameters: { + backgrounds: [], + }, +}; + +export const disabledBackgrounds = () => ( + +); +disabledBackgrounds.story = { + parameters: { + backgrounds: { disabled: true }, + }, +}; ``` diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index 552703b630ea..16ac69bb7c7d 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "A storybook addon to show different backgrounds for your preview", "keywords": [ "addon", @@ -21,7 +21,6 @@ "author": "jbaxleyiii", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -32,21 +31,23 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "memoizerific": "^1.11.3", "react": "^16.8.3", "util-deprecate": "^1.0.2" }, "devDependencies": { - "@types/util-deprecate": "^1.0.0" + "@types/util-deprecate": "^1.0.0", + "@types/webpack-env": "^1.15.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/centered/package.json b/addons/centered/package.json index b59c6b932894..ca536816cc3f 100644 --- a/addons/centered/package.json +++ b/addons/centered/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-centered", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook decorator to center components", "keywords": [ "addon", @@ -29,15 +29,17 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "util-deprecate": "^1.0.2" }, "devDependencies": { "@types/mithril": "^1.1.16", + "@types/webpack-env": "^1.15.0", "mithril": "*", "preact": "*", "react": "*" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/contexts/README.md b/addons/contexts/README.md index c781dacbe0e9..7a3f55afb418 100644 --- a/addons/contexts/README.md +++ b/addons/contexts/README.md @@ -47,14 +47,15 @@ To get it started, add this package into your project: yarn add -D @storybook/addon-contexts ``` -Then, register the addon by adding the following line into your `addon.js` file (you should be able to find the file -under the storybook config directory of your project): +within `.storybook/main.js`: ```js -import '@storybook/addon-contexts/register'; +module.exports = { + addons: ['@storybook/addon-contexts/register'] +} ``` -To load your contextual setups for your stories globally, add the following lines into `config.js` file (you should +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 @@ -68,28 +69,24 @@ addDecorator(withContexts(contexts)); Alternatively, like other addons, you can use this addon only for a given set of stories: ```js -import { storiesOf } from '@storybook/[framework]'; import { withContexts } from '@storybook/addon-contexts/[framework]'; import { contexts } from './configs/contexts'; -const story = storiesOf('Component With Contexts', module).addDecorator(withContexts(contexts)); // use this addon with a default contextual environment setups +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 -story.add( - () => { - /* some stories */ - }, - { - contexts: [ - { - /* the modified setup goes here, sharing the same API signatures */ - }, - ], +export const defaultView = () =>
; +defaultView.story = { + parameters: { + context: [{}] } -); +}; ``` ## ⚙️ Setups @@ -112,8 +109,8 @@ export const contexts = [ ], 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 }, + { name: 'Light Theme', props: { theme /* : your light theme */ } }, + { name: 'Dark Theme', props: { theme /* : your dark theme */ }, default: true }, ], options: { deep: true, // pass the `props` deeply into all wrapping components diff --git a/addons/contexts/package.json b/addons/contexts/package.json index b548e52492fb..840ceb726697 100644 --- a/addons/contexts/package.json +++ b/addons/contexts/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-contexts", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook Addon Contexts", "keywords": [ "preact", @@ -27,10 +27,10 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "qs": "^6.6.0" @@ -39,10 +39,12 @@ "global": "*", "preact": "*", "qs": "*", + "rax": "*", "react": "*", "vue": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/contexts/rax.js b/addons/contexts/rax.js new file mode 100644 index 000000000000..06abd7467f83 --- /dev/null +++ b/addons/contexts/rax.js @@ -0,0 +1,4 @@ +import { withContexts } from './dist/preview/frameworks/rax'; + +export { withContexts }; +export default withContexts; diff --git a/addons/contexts/src/manager/components/ToolBar.test.tsx b/addons/contexts/src/manager/components/ToolBar.test.tsx index b5a09c187e19..35caba83f2c6 100644 --- a/addons/contexts/src/manager/components/ToolBar.test.tsx +++ b/addons/contexts/src/manager/components/ToolBar.test.tsx @@ -27,7 +27,10 @@ describe('Tests on addon-contexts component: ToolBar', () => { 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: {} }], + params: [ + { name: 'Some Param X', props: {} }, + { name: 'Some Param Y', props: {} }, + ], title: 'Some Context B', }, ]; diff --git a/addons/contexts/src/manager/components/ToolBarControl.test.tsx b/addons/contexts/src/manager/components/ToolBarControl.test.tsx index c0131fd4a47f..975f420dc27f 100644 --- a/addons/contexts/src/manager/components/ToolBarControl.test.tsx +++ b/addons/contexts/src/manager/components/ToolBarControl.test.tsx @@ -9,7 +9,10 @@ describe('Tests on addon-contexts component: ToolBarControl', () => { icon: 'box' as const, nodeId: 'Some Context', options: { cancelable: true, deep: false, disable: false }, - params: [{ name: 'A', props: {} }, { name: 'B', props: {} }], + params: [ + { name: 'A', props: {} }, + { name: 'B', props: {} }, + ], title: 'Some Context', selected: '', setSelected: jest.fn, diff --git a/addons/contexts/src/preview/frameworks/preact.ts b/addons/contexts/src/preview/frameworks/preact.ts index 659fd6940693..ece1de938e6b 100644 --- a/addons/contexts/src/preview/frameworks/preact.ts +++ b/addons/contexts/src/preview/frameworks/preact.ts @@ -1,4 +1,4 @@ -import Preact from 'preact'; +import { h, VNode } from 'preact'; import { createAddonDecorator, Render } from '../../index'; import { ContextsPreviewAPI } from '../ContextsPreviewAPI'; @@ -6,9 +6,9 @@ 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) => { +export const renderPreact: Render = (contextNodes, propsMap, getStoryVNode) => { const { getRendererFrom } = ContextsPreviewAPI(); - return getRendererFrom(Preact.h)(contextNodes, propsMap, getStoryVNode); + return getRendererFrom(h)(contextNodes, propsMap, getStoryVNode); }; export const withContexts = createAddonDecorator(renderPreact); diff --git a/addons/contexts/src/preview/frameworks/rax.ts b/addons/contexts/src/preview/frameworks/rax.ts new file mode 100644 index 000000000000..0748a7d7f7cf --- /dev/null +++ b/addons/contexts/src/preview/frameworks/rax.ts @@ -0,0 +1,14 @@ +import { createElement, RaxElement } from 'rax'; +import { createAddonDecorator, Render } from '../../index'; +import { ContextsPreviewAPI } from '../ContextsPreviewAPI'; + +/** + * This is the framework specific bindings for Rax. + * '@storybook/rax' expects the returning object from a decorator to be a 'Rax Element' (vNode). + */ +export const renderRax: Render> = (contextNodes, propsMap, getStoryVNode) => { + const { getRendererFrom } = ContextsPreviewAPI(); + return getRendererFrom(createElement)(contextNodes, propsMap, getStoryVNode); +}; + +export const withContexts = createAddonDecorator(renderRax); diff --git a/addons/contexts/src/preview/libs/getContextNodes.test.ts b/addons/contexts/src/preview/libs/getContextNodes.test.ts index cf6b1c2e20f8..d6bbb23c24e6 100644 --- a/addons/contexts/src/preview/libs/getContextNodes.test.ts +++ b/addons/contexts/src/preview/libs/getContextNodes.test.ts @@ -22,7 +22,10 @@ describe('Test on the merging result of a pair of settings', () => { icon: 'box' as const, title: 'Some Context', components: ['div'], - params: [{ name: 'T1', props: {} }, { name: 'T2', props: {} }], + params: [ + { name: 'T1', props: {} }, + { name: 'T2', props: {} }, + ], options: { cancelable: true, disable: true, @@ -32,7 +35,10 @@ describe('Test on the merging result of a pair of settings', () => { icon: 'box' as const, title: 'Some Context', components: ['span'], - params: [{ name: 'S1', props: {} }, { name: 'S2', props: {} }], + params: [ + { name: 'S1', props: {} }, + { name: 'S2', props: {} }, + ], options: { deep: true, disable: false, @@ -106,7 +112,10 @@ describe('Test on reconciliation of settings', () => { icon: 'box', nodeId: 'Some Context', options: { cancelable: false, deep: false, disable: false }, - params: [{ name: 'T1', props: {} }, { name: 'S2', props: {}, default: true }], + params: [ + { name: 'T1', props: {} }, + { name: 'S2', props: {}, default: true }, + ], title: 'Some Context', }, { diff --git a/addons/contexts/src/preview/libs/getPropsMap.test.ts b/addons/contexts/src/preview/libs/getPropsMap.test.ts index 130cba8df63f..321d926e38b8 100644 --- a/addons/contexts/src/preview/libs/getPropsMap.test.ts +++ b/addons/contexts/src/preview/libs/getPropsMap.test.ts @@ -2,7 +2,10 @@ 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: {} }]; + const someParams = [ + { name: 'A', props: {} }, + { name: 'B', props: {} }, + ]; it('should return "null" when params in 0 length', () => { const result = _getPropsByParamName([]); @@ -44,7 +47,10 @@ describe('Test on the integrity of the method to get the propMaps', () => { 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 }], + params: [ + { name: 'A1', props: { a: 1 } }, + { name: 'A2', props: { a: 2 }, default: true }, + ], title: 'Some Context', }, { diff --git a/addons/cssresources/README.md b/addons/cssresources/README.md index 1d3cd90d7dc2..bf15532f593e 100644 --- a/addons/cssresources/README.md +++ b/addons/cssresources/README.md @@ -14,49 +14,37 @@ yarn add -D @storybook/addon-cssresources ## Configuration -Then create a file called `addons.js` in your storybook config. +Then create a file called `main.js` in your storybook config. Add following content to it: ```js -import '@storybook/addon-cssresources/register'; +module.exports = { + addons: ['@storybook/addon-cssresources/register'], +}; ``` ## Usage -You need add the all the css resources at compile time using the `withCssResources` decorator. They can be added globally or per story. You can then choose which ones to load from the cssresources addon ui: +You need add the all the css resources at compile time using the `withCssResources` decorator. They can be added globally or per story. You can then choose which ones to load from the cssresources addon UI: ```js -// Import from @storybook/X where X is your framework -import { configure, addDecorator, addParameters, storiesOf } from '@storybook/react'; import { withCssResources } from '@storybook/addon-cssresources'; -// global -addDecorator(withCssResources) -addParameters({ - cssresources: [{ - id: `bluetheme`, - code: ``, - picked: false, - }, - ], -}); - -You can use the `cssresources` parameter to override resources on each story individually: - -// per story -storiesOf('Addons|Cssresources', module) - .add('Camera Icon', () => Camera Icon, { - cssresources: [ - { - id: `fontawesome`, - code: ``, - picked: true, - }, { - id: `whitetheme`, - code: ``, - picked: true, - }, - ], - }); +export default { + title: 'CssResources', + parameters: { + cssresources: [ + { + id: `bluetheme`, + code: ``, + picked: false, + hideCode: false, // Defaults to false, this enables you to hide the code snippet and only displays the style selector + }, + ], + }, + decorators: [withCssResources], +}; + +export const defaultView = () =>
; ``` diff --git a/addons/cssresources/package.json b/addons/cssresources/package.json index 766c45e78230..b1f21d225e10 100644 --- a/addons/cssresources/package.json +++ b/addons/cssresources/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-cssresources", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "A storybook addon to switch between css resources at runtime for your story", "keywords": [ "addon", @@ -21,7 +21,6 @@ "author": "nm123github", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -32,18 +31,23 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/cssresources/src/CssResource.ts b/addons/cssresources/src/CssResource.ts index 0067ca677488..ee67b93e2a7c 100644 --- a/addons/cssresources/src/CssResource.ts +++ b/addons/cssresources/src/CssResource.ts @@ -2,4 +2,5 @@ export interface CssResource { id: string; code: string; picked: boolean; + hideCode: boolean; } diff --git a/addons/cssresources/src/css-resource-panel.test.js b/addons/cssresources/src/css-resource-panel.test.js index cd0f47303045..ef75ae2ba376 100644 --- a/addons/cssresources/src/css-resource-panel.test.js +++ b/addons/cssresources/src/css-resource-panel.test.js @@ -300,5 +300,32 @@ describe('CSSResourcePanel', () => { node.instance().onStoryChange('fake-story-id'); expect(node.find(SyntaxHighlighter).length).toEqual(1); }); + + it('should not render code for items /w the `hideCode` flag', () => { + const apiGetParameters = jest.fn(() => [ + { + id: 'local-fake-id-1', + code: 'local-fake-code-1', + picked: true, + hideCode: true, + }, + { + id: 'local-fake-id-2', + code: 'local-fake-code-2', + picked: false, + }, + ]); + + const node = shallowNode({ + ...defaultProps, + api: { + ...defaultProps.api, + getParameters: apiGetParameters, + }, + }); + + node.instance().onStoryChange('fake-story-id'); + expect(node.find(SyntaxHighlighter).length).toEqual(1); + }); }); }); diff --git a/addons/cssresources/src/css-resource-panel.tsx b/addons/cssresources/src/css-resource-panel.tsx index ef0be6e19bb2..5155edb71c31 100644 --- a/addons/cssresources/src/css-resource-panel.tsx +++ b/addons/cssresources/src/css-resource-panel.tsx @@ -1,7 +1,8 @@ import React, { Component, Fragment } from 'react'; -import { SyntaxHighlighter } from '@storybook/components'; +import { SyntaxHighlighter, Placeholder, Spaced, Icons } from '@storybook/components'; import { STORY_RENDERED } from '@storybook/core-events'; import { API } from '@storybook/api'; +import { styled } from '@storybook/theming'; import { EVENTS, PARAM_KEY } from './constants'; import { CssResource } from './CssResource'; @@ -20,6 +21,27 @@ interface CssResourceLookup { [key: string]: CssResource; } +const maxLimitToUseSyntaxHighlighter = 100000; + +const PlainCode = styled.pre({ + textAlign: 'left', + fontWeight: 'normal', +}); + +const Warning = styled.div({ + display: 'flex', + padding: '10px', + justifyContent: 'center', + alignItems: 'center', + background: '#fff3cd', + fontSize: 12, + '& svg': { + marginRight: 10, + width: 24, + height: 24, + }, +}); + export class CssResourcePanel extends Component { constructor(props: Props) { super(props); @@ -90,13 +112,26 @@ export class CssResourcePanel extends Component { return (
{list && - list.map(({ id, code, picked }) => ( + list.map(({ id, code, picked, hideCode = false }) => (
- {code ? {code} : null} + {code && !hideCode && code.length < maxLimitToUseSyntaxHighlighter && ( + {code} + )} + {code && !hideCode && code.length >= maxLimitToUseSyntaxHighlighter && ( + + + {code.substring(0, maxLimitToUseSyntaxHighlighter)} ... + + + Rest of the content cannot be displayed + + + + )}
))}
diff --git a/addons/design-assets/README.md b/addons/design-assets/README.md index 390da484bb68..06f7b69545b1 100644 --- a/addons/design-assets/README.md +++ b/addons/design-assets/README.md @@ -11,45 +11,32 @@ npm install @storybook/addon-design-assets ``` ## Usage -within `addons.js`: +within `.storybook/main.js`: ```js -import '@storybook/addon-design-assets/register'; +module.exports = { + addons: ['@storybook/addon-design-assets/register'] +} ``` within your stories: ```js -import { storiesOf } from '@storybook/react'; +import React from 'react'; import imageUrl from './images/my-image.jpg'; -storiesOf('root|group/component', module) - .addParameters({ +export default { + title: 'Design Assets', + parameters: { assets: [ imageUrl, // link to a file imported 'https://via.placeholder.com/300/09f/fff.png', // link to an external image 'https://www.example.com', // link to a webpage 'https://www.example.com?id={id}', // link to a webpage with the current story's id in the url ], - }) - .add('variant', () =>
your story here
); -``` - -If you have a set of different assets on 1 story, you might want to name then: -```js -import { storiesOf } from '@storybook/react'; + }, +}; -import imageUrl from './images/my-image.jpg'; - -storiesOf('root|group/component', module) - .addParameters({ - assets: [{ - url: 'https://via.placeholder.com/300/09f/fff.png', // link to an external image - name: 'blue', - }, { - url: 'https://via.placeholder.com/300/f90/fff.png', // link to an external image - name: 'orange', - }], - }) - .add('variant', () =>
your story here
); -``` +export const defaultView = () => ( +
your story here
+); diff --git a/addons/design-assets/package.json b/addons/design-assets/package.json index dd57c2b07268..cc084cbbb76c 100644 --- a/addons/design-assets/package.json +++ b/addons/design-assets/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-design-assets", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Design asset preview for storybook", "keywords": [ "addon", @@ -23,7 +23,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -34,12 +33,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -48,5 +47,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/docs/README.md b/addons/docs/README.md index 6008ed2cffb0..1276d5f2c299 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -1,9 +1,11 @@
- +
# Storybook Docs +> 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. **DocsPage.** Out of the box, all your stories get a `DocsPage`. `DocsPage` is a zero-config aggregation of your component stories, text descriptions, docgen comments, props tables, and code examples into clean, readable pages. @@ -32,7 +34,7 @@ When you [install Docs](#installation), every story gets a `DocsPage`. `DocsPage Click on the `Docs` tab to see it:
- +
For more information on how it works, see the [`DocsPage` reference](./docs/docspage.md). @@ -47,7 +49,7 @@ Here's an example file: import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; import { Checkbox } from './Checkbox'; - + # Checkbox @@ -68,7 +70,7 @@ markdown documentation. And here's how that's rendered in Storybook:
- +
For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md). @@ -77,16 +79,16 @@ For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md). Storybook Docs supports all view layers that Storybook supports except for React Native (currently). There are some framework-specific features as well, such as props tables and inline story rendering. This chart captures the current state of support: -| | React | Vue | Angular | HTML | [Web Components](./web-components) | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact | -| ----------------- | :---: | :---: | :-----: | :---: | :--------------------------------: | :----: | :-----: | :---: | :-----: | :---: | :---: | :----: | -| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + | -| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + | -| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + | -| Source | + | + | + | + | + | + | + | + | + | + | + | + | -| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | -| Props table | + | + | # | | + | | | | | | | | -| Description | + | + | # | | + | | | | | | | | -| Inline stories | + | + | | | + | | | | | | | | +| | React | Vue | Angular | Ember | Web Components | HTML | Svelte | Preact | Riot | Mithril | Marko | +| ----------------- | :---: | :-: | :-----: | :---: | :------------: | :--: | :----: | :----: | :--: | :-----: | :---: | +| MDX stories | + | + | + | + | + | + | + | + | + | + | + | +| CSF stories | + | + | + | + | + | + | + | + | + | + | + | +| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | +| Source | + | + | + | + | + | + | + | + | + | + | + | +| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | +| Props table | + | + | + | + | + | | | | | | | +| Description | + | + | + | + | + | | | | | | | +| Inline stories | + | + | | | + | | | | | | | **Note:** `#` = WIP support @@ -106,24 +108,15 @@ Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you wa yarn add -D react react-is babel-loader ``` -Then add the following to your `.storybook/presets.js` exports: - -```js -module.exports = ['@storybook/addon-docs/react/preset']; -``` - -If you're not using `react`, replace it with your framework of choice corresponding to the Storybook package name, e.g. `angular` for `@storybook/angular` etc. - -**Configure.** If you're migrating from an earlier version of Storybook and want to use `MDX`, you need to upgrade your Storybook config: +Then add the following to your `.storybook/main.js`: ```js -import { configure } from '@storybook/react'; - -configure(require.context('../src', true, /\.stories\.(js|mdx)$/), module); +module.exports = { + stories: ['../src/**/*.stories.(js|mdx)'], + addons: ['@storybook/addon-docs'], +}; ``` -For more information on the new `configure`, see ["Loading stories"](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/basics/writing-stories/index.md#loading-stories) in the Storybook documentation. - If using in conjunction with the [storyshots add-on](../storyshots/storyshots-core/README.md), you will need to configure Jest to transform MDX stories into something Storyshots can understand: @@ -140,8 +133,10 @@ Add the following to your Jest configuration: ### Be sure to check framework specific installation needs -- [Angular](./angular) +- [React](./react) (covered here) - [Vue](./vue) +- [Angular](./angular) +- [Ember](./ember) - [Web Components](./web-components) ## Preset options @@ -149,65 +144,71 @@ Add the following to your Jest configuration: The `addon-docs` preset has a few configuration options that can be used to configure its babel/webpack loading behavior. Here's an example of how to use the preset with options: ```js -module.exports = [ - { - name: '@storybook/addon-docs/react/preset', - options: { - configureJSX: true, - babelOptions: {}, - sourceLoaderOptions: null, +module.exports = { + addons: [ + { + name: '@storybook/addon-docs', + options: { + configureJSX: true, + babelOptions: {}, + sourceLoaderOptions: null, + }, }, - }, -]; + ], +}; ``` -The `configureJsx` option is useful when you're writing your docs in MDX and your project's babel config isn't already set up to handle JSX files. `babelOptions` is a way to further configure the babel processor when you're using `configureJSX`. +The `configureJSX` option is useful when you're writing your docs in MDX and your project's babel config isn't already set up to handle JSX files. `babelOptions` is a way to further configure the babel processor when you're using `configureJSX`. -`sourceLoaderOptions` is an object for configuring `@storybook/source-loader`. When set to `null` it tells docs not to run the `source-loader` at all, which can be used as an optimization, or if you're already using `source-loader` in your `webpack.config.js`. +`sourceLoaderOptions` is an object for configuring `@storybook/source-loader`. When set to `null` it tells docs not to run the `source-loader` at all, which can be used as an optimization, or if you're already using `source-loader` in your `main.js`. ## Manual configuration -If you don't want to use the preset, and prefer to configure "the long way", first register the addon in `.storybook/addons.js`: - -```js -import '@storybook/addon-docs/register'; -``` - -Then configure Storybook's webpack loader in `.storybook/webpack.config.js` to understand MDX story files and annotate TS/JS story files with source code using `source-loader`: +If you don't want to use the preset, and prefer to configure "the long way" add the following configuration to `.storybook/main.js` (see comments inline for explanation): ```js const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin'); -module.exports = async ({ config }) => { - config.module.rules.push({ - test: /\.(stories|story)\.mdx$/, - use: [ - { - loader: 'babel-loader', - // may or may not need this line depending on your app's setup - options: { - plugins: ['@babel/plugin-transform-react-jsx'], +module.exports = { + // 1. register the docs panel (as opposed to '@storybook/addon-docs' which + // will configure everything with a preset) + addons: ['@storybook/addon-docs/register'], + // 2. manually configure webpack, since you're not using the preset + webpackFinal: async config => { + config.module.rules.push({ + // 2a. Load `.stories.mdx` / `.story.mdx` files as CSF and generate + // the docs page from the markdown + test: /\.(stories|story)\.mdx$/, + use: [ + { + loader: 'babel-loader', + // may or may not need this line depending on your app's setup + options: { + plugins: ['@babel/plugin-transform-react-jsx'], + }, }, - }, - { - loader: '@mdx-js/loader', - options: { - compilers: [createCompiler({})], + { + loader: '@mdx-js/loader', + options: { + compilers: [createCompiler({})], + }, }, - }, - ], - }); - config.module.rules.push({ - test: /\.(stories|story)\.[tj]sx?$/, - loader: require.resolve('@storybook/source-loader'), - exclude: [/node_modules/], - enforce: 'pre', - }); - return config; + ], + }); + // 2b. Run `source-loader` on story files to show their source code + // automatically in `DocsPage` or the `Source` doc block. + config.module.rules.push({ + test: /\.(stories|story)\.[tj]sx?$/, + loader: require.resolve('@storybook/source-loader'), + exclude: [/node_modules/], + enforce: 'pre', + }); + return config; + }, }; ``` -Finally, you'll need to set up DocsPage in `.storybook/config.js`: +Finally, you'll need to set up DocsPage in `.storybook/preview.js`: ```js import { addParameters } from '@storybook/react'; diff --git a/addons/docs/angular/README.md b/addons/docs/angular/README.md index 6a8de67eae48..5783d26dfcaa 100644 --- a/addons/docs/angular/README.md +++ b/addons/docs/angular/README.md @@ -4,6 +4,8 @@ # Storybook Docs for Angular +> 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 Angular 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 Angular specifics, read on! @@ -22,10 +24,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/presets.js` exports: +Then add the following to your `.storybook/main.js` exports: ```js -module.exports = ['@storybook/addon-docs/angular/preset']; +module.exports = { + addons: ['@storybook/addon-docs'], +}; ``` ## DocsPage @@ -55,7 +59,7 @@ Then you'll need to configure Compodoc to generate a `documentation.json` file. Unfortunately, it's not currently possible to update this dynamically as you edit your components, but [there's an open issue](https://github.com/storybookjs/storybook/issues/8672) to support this with improvements to Compodoc. -Next, add the following to your `.storybook/config.json` to load the Compodoc-generated file: +Next, add the following to `.storybook/preview.ts` to load the Compodoc-generated file: ```js import { setCompodocJson } from '@storybook/addon-docs/angular'; @@ -95,10 +99,12 @@ Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you wa yarn add -D react react-is babel-loader ``` -Then update your `.storybook/config.ts` to make sure you load MDX files: +Then update your `.storybook/main.js` to make sure you load MDX files: ```ts -configure(require.context('../src/stories', true, /\.stories\.(ts|mdx)$/), module); +module.exports = { + stories: ['../src/stories/**/*.stories.(js|mdx)'], +}; ``` Finally, you can create MDX files like this: @@ -131,7 +137,7 @@ Also, to use the `Props` doc block, you need to set up Compodoc, [as described a Storybook Docs renders all Angular stories inside IFrames, with a default height of `60px`. You can update this default globally, or modify the IFrame height locally per story in `DocsPage` and `MDX`. -To update the global default, modify `.storybook/config.ts`: +To update the global default, modify `.storybook/preview.ts`: ```ts import { addParameters } from '@storybook/angular'; diff --git a/addons/docs/angular/config.js b/addons/docs/angular/config.js deleted file mode 100644 index 52f6248698b5..000000000000 --- a/addons/docs/angular/config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/frameworks/angular/config'); diff --git a/addons/docs/docs/docspage.md b/addons/docs/docs/docspage.md index 5e286be9ee44..048bfa9de52e 100644 --- a/addons/docs/docs/docspage.md +++ b/addons/docs/docs/docspage.md @@ -1,17 +1,28 @@
- +
# Storybook DocsPage When you install [Storybook Docs](../README.md), `DocsPage` is the zero-config default documentation that all stories get out of the box. It aggregates your stories, text descriptions, docgen comments, props tables, and code examples into a single page for each component. -- [Motivation](#motivation) -- [Component parameter](#component-parameter) -- [DocsPage slots](#docspage-slots) -- [Replacing DocsPage](#replacing-docspage) -- [Story file names](#story-file-names) -- [More resources](#more-resources) +- [Storybook DocsPage](#storybook-docspage) + - [Motivation](#motivation) + - [Component parameter](#component-parameter) + - [Subcomponents parameter](#subcomponents-parameter) + - [DocsPage slots](#docspage-slots) + - [Slot values](#slot-values) + - [Title](#title) + - [Subtitle](#subtitle) + - [Description](#description) + - [Primary](#primary) + - [Props](#props) + - [Stories](#stories) + - [Slot functions](#slot-functions) + - [Replacing DocsPage](#replacing-docspage) + - [Story file names](#story-file-names) + - [Inline stories vs. Iframe stories](#inline-stories-vs-iframe-stories) + - [More resources](#more-resources) ## Motivation @@ -54,12 +65,32 @@ storiesOf('Path/to/Badge', module).addParameters({ component: Badge }); If you're coming from the `storiesOf` format, there's [a codemod that adds it for you](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#add-component-parameters). +## Subcomponents parameter + +Sometimes it's useful to document multiple components on the same page. For example, suppose your component library contains `List` and `ListItem` components that don't make sense without one another. `DocsPage` has the concept of a "primary" component with the [`component` parameter](#component-parameter), and can also accept one or more "subcomponents": + +```js +import { List, ListHeading, ListItem } from './List'; + +export default { + title: 'Path/to/List', + component: List, + subcomponents: { ListHeading, ListItem }, +}; +``` + +Subcomponent prop tables will show up in a tabbed interface along with the primary component, and the tab titles will correspond to the keys of the `subcomponents` object. + + + +If you want organize your documentation differently for groups of components, we recommend trying [MDX](./mdx.md) which is completely flexible to support any configuration. + ## DocsPage slots `DocsPage` is organized into a series of "slots" including Title, Subtitle, Description, Props, and Story. Each of these slots pulls information from your project and formats it for the screen.
- +
## Slot values @@ -204,7 +235,7 @@ You can replace DocsPage at any level by overriding the `docs.page` parameter: - [With MDX](./recipes.md#csf-stories-with-mdx-docs) docs - With a custom React component -**Globally (config.js)** +**Globally (preview.js)** ```js import { addParameters } from '@storybook/react'; diff --git a/addons/docs/docs/faq.md b/addons/docs/docs/faq.md index 871d80643a01..e7592c3e2aa0 100644 --- a/addons/docs/docs/faq.md +++ b/addons/docs/docs/faq.md @@ -10,7 +10,7 @@ You've read the [Storybook Docs README](../README.md). You're already familiar w ## 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 +19,7 @@ Currently we hide the addons panel when docs is visible. It's tricky because all ## How do I debug my MDX story?
- +
> "My story renders in docs, but doesn’t show up the way I’d expect in the Canvas” @@ -37,7 +37,7 @@ For example, the following MDX story: Shows up in the dev tools as follows:
- +
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. diff --git a/addons/docs/docs/mdx.md b/addons/docs/docs/mdx.md index 8eb3ec708d99..0d65a34044c3 100644 --- a/addons/docs/docs/mdx.md +++ b/addons/docs/docs/mdx.md @@ -1,21 +1,20 @@
- +
# 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! - `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. -- [Basic example](#basic-example) -- [MDX-Flavored CSF](#mdx-flavored-csf) -- [Writing stories](#writing-stories) -- [Embedding stories](#embedding-stories) -- [Decorators and parameters](#decorators-and-parameters) -- [Documentation-only MDX](#documentation-only-mdx) -- [MDX file names](#mdx-file-names) -- [More resources](#more-resources) +- [Storybook Docs MDX](#storybook-docs-mdx) + - [Basic example](#basic-example) + - [MDX-Flavored CSF](#mdx-flavored-csf) + - [Writing stories](#writing-stories) + - [Embedding stories](#embedding-stories) + - [Decorators and parameters](#decorators-and-parameters) + - [Documentation-only MDX](#documentation-only-mdx) + - [MDX file names](#mdx-file-names) + - [More resources](#more-resources) ## Basic example @@ -25,7 +24,7 @@ Let's get started with an example that combines markdown with a single story: import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; import { Checkbox } from './Checkbox'; - + # Checkbox @@ -46,7 +45,7 @@ markdown documentation. And here's how that's rendered in Storybook:
- +
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. @@ -64,7 +63,7 @@ For example, here's the story from `Checkbox` example above, rewritten in CSF: ```js import React from 'react'; import { Checkbox } from './Checkbox'; -export default { title: "MDX|Checkbox" component: Checkbox }; +export default { title: "MDX/Checkbox" component: Checkbox }; export const allCheckboxes = () => (
@@ -86,7 +85,7 @@ import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; import { Badge } from './Badge'; import { Icon } from './Icon'; - + # Badge @@ -130,7 +129,7 @@ with unique URLs and isolated snapshot tests. And here's how that gets rendered in Storybook:
- +
## Embedding stories @@ -165,7 +164,7 @@ To add [decorators](https://github.com/storybookjs/storybook/blob/next/docs/src/ ``` -In addition, global decorators work just like before, e.g. adding the following to your `.storybook/config.js`: +In addition, global decorators work just like before, e.g. adding the following to your `.storybook/preview.js`: ```js import { addDecorator, addParameters } from '@storybook/react'; @@ -185,7 +184,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 `` as you normally would, but don't define any stories. It will show up in your UI as a documentation node:
- +
## MDX file names diff --git a/addons/docs/docs/media/docspage-subcomponents.png b/addons/docs/docs/media/docspage-subcomponents.png new file mode 100644 index 000000000000..86d001556ffa Binary files /dev/null and b/addons/docs/docs/media/docspage-subcomponents.png differ diff --git a/addons/docs/docs/multiframework.md b/addons/docs/docs/multiframework.md index e5994b7c14a7..074aac0b1106 100644 --- a/addons/docs/docs/multiframework.md +++ b/addons/docs/docs/multiframework.md @@ -1,51 +1,42 @@ # Storybook Docs framework dev guide -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 set up a new framework in docs. +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. -- [Adding a preset](#adding-a-preset) +- [Framework-specific configuration](#framework-specific-configuration) - [Props tables](#props-tables) - [Component descriptions](#component-descriptions) - [Inline story rendering](#inline-story-rendering) -## Adding a preset +## Framework-specific configuration -To get basic support, you need to add a [preset](https://storybook.js.org/docs/presets/introduction). By default this doesn't need to do much. +Your framework might need framework-specific configuration. This could include adding extra webpack loaders or global decorators/story parameters. -Here's a basic preset for `@storybook/html` in `addons/docs/html/preset.js`: +Addon-docs handles this kind of customization by file naming convention. Its [common preset](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/common/preset.ts) does this by looking for files `..//{preset,config}.[tj]sx?`, where `` is the framework identifier, e.g. `vue`, `angular`, `react`, etc. -```js -module.exports = require('../dist/frameworks/common/makePreset').default('html'); -``` - -This automatically adds [DocsPage](./docspage.md) for each story, as well as webpack/babel settings for MDX support. - -There is also a little hoop-jumping that will hopefully be unnecessary soon. +For example, consider Storybook Docs for Vue, which needs `vue-docgen-loader` in its webpack config, and also has custom extraction functions for [props tables](#props-tables) and [component descriptions](#component-descriptions). -`addons/docs/src/frameworks/html/config.js` +For webpack configuration, Docs for Vue defines [preset.ts](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/preset.ts), which follows the [preset](https://storybook.js.org/docs/presets/introduction) file structure: -```js -import { addParameters } from '@storybook/html'; -import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks'; - -addParameters({ - docs: { - container: DocsContainer, - page: DocsPage, - }, -}); +``` +export function webpack(webpackConfig: any = {}, options: any = {}) { + webpackConfig.module.rules.push({ + test: /\.vue$/, + loader: 'vue-docgen-loader', + enforce: 'post', + }); + return webpackConfig; +} ``` -`addons/docs/html/config.js` +This appends `vue-docgen-loader` to the existing configuration, which at this point will also include modifications made by the common preset. -```js -module.exports = require('../dist/frameworks/html/config'); -``` +For props tables and descriptions, both of which are described in more detail below, it defines a file [config.tsx](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/config.tsx). ## Props tables Props tables are enabled by the framework-specific `docs.extractProps` parameter, which extracts a component's props into a common data structure. -Here's how it's done in Vue's framework-specific `config.js`: +Here's how it's done in Vue's framework-specific `preview.js`: ```js import { extractProps } from './extractProps'; @@ -58,7 +49,7 @@ addParameters({ }); ``` -The `extractProps`function receives a component as an argument, and returns an object of type [`PropsTableProps`](https://github.com/storybookjs/storybook/blob/next/lib/components/src/blocks/PropsTable/PropsTable.tsx#L147), which can either be a array of `PropDef` rows (React), or a mapping of section name to an array of `PropDef` rows (e.g. `Props`/`Events`/`Slots` in Vue). +The `extractProps`function receives a component as an argument, and returns an object of type [`PropsTableProps`](https://github.com/storybookjs/storybook/blob/next/lib/components/src/blocks/PropsTable/PropsTable.tsx#L147), which can either be an array of `PropDef` rows (React), or a mapping of section name to an array of `PropDef` rows (e.g. `Props`/`Events`/`Slots` in Vue). ```ts export interface PropDef { @@ -88,7 +79,7 @@ It follows the pattern of [Props tables](#props-tables) above, only it's even si Inline story rendering is another framework specific optimization, made possible by the `docs.prepareForInline` parameter. -Again let's look at Vue's framework-specific `config.js`: +Again let's look at Vue's framework-specific `preview.js`: ```js import toReact from '@egoist/vue-to-react'; diff --git a/addons/docs/docs/recipes.md b/addons/docs/docs/recipes.md index d8074274392a..2b1d4841194e 100644 --- a/addons/docs/docs/recipes.md +++ b/addons/docs/docs/recipes.md @@ -66,7 +66,7 @@ import { SomeComponent } from 'path/to/SomeComponent'; I can define a story with the function imported from CSF: -{stories.basic} +{stories.basic()} And I can also embed arbitrary markdown & JSX in this file. @@ -76,9 +76,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 @@ -157,7 +155,7 @@ We made this error explicit to make sure you know what you're doing when you mix If you're currently using the notes/info addons, you can upgrade to DocsPage by providing a custom `docs.extractComponentDescription` parameter. There are different ways to use each addon, so you can adapt this recipe according to your use case. -Suppose you've added a `notes` parameter to each component in your library, containing markdown text, and you want that to show up at the top of the page in the `Description` slot. You could do that by adding the following snippet to `.storybook/config.js`: +Suppose you've added a `notes` parameter to each component in your library, containing markdown text, and you want that to show up at the top of the page in the `Description` slot. You could do that by adding the following snippet to `.storybook/preview.js`: ```js import { addParameters } from '@storybook/client-api'; @@ -211,6 +209,31 @@ User writes documentation & stories side-by-side in a single MDX file, and wants ``` +## 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 = () => ; +Foo.story = { + 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', +}); +``` + ## More resources Want to learn more? Here are some more articles on Storybook Docs: diff --git a/addons/docs/docs/theming.md b/addons/docs/docs/theming.md index 924f81ef1052..73ee65dced5f 100644 --- a/addons/docs/docs/theming.md +++ b/addons/docs/docs/theming.md @@ -10,7 +10,11 @@ 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. -For example, here's how to change your docs (and Storybook) to the dark theme, by modifying `.storybook/config.js`: +> In the documentation it will say you can theme storybook in `manager.js`, That's a newer more optimal way to theme storybook, but it's currently incompatible with docs. +> +> We're working on making it compatible in 6.0.0, so for now use the method described below. + +Here's how to change your docs (and Storybook) to the dark theme, by modifying `.storybook/preview.js`: ```js import { addParameters } from '@storybook/react'; @@ -45,7 +49,7 @@ You can style these classes in `.storybook/preview-head.html`. For example, here If you're using MDX, there's one more level of themability. MDX allows you to [completely override the components](https://mdxjs.com/advanced/components) that are rendered from markdown using a `components` parameter. This is an advanced usage that we don't officially support in Storybook, but it's a powerful mechanism if you need it. -Here's how you might insert a custom code renderer for `code` blocks on the page, in `.storybook/config.js`: +Here's how you might insert a custom code renderer for `code` blocks on the page, in `.storybook/preview.js`: ```js import { addParameters } from '@storybook/react'; @@ -60,6 +64,22 @@ addParameters({ }); ``` +You can even override a Storybook *block* component. + +Here's how you might insert a custom `` block: + +```js +import { MyPreview } from './MyPreview'; + +addParameters({ + docs: { + components: { + Preview: MyPreview, + }, + }, +}); +``` + ## More resources Want to learn more? Here are some more articles on Storybook Docs: diff --git a/addons/docs/ember/README.md b/addons/docs/ember/README.md new file mode 100644 index 000000000000..375b8b0de3e6 --- /dev/null +++ b/addons/docs/ember/README.md @@ -0,0 +1,152 @@ +# Storybook Docs for Ember + +> 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. + +To learn more about Storybook Docs, read the [general documentation](../README.md). To learn the Ember specifics, read on! + +- [Installation](#installation) +- [DocsPage](#docspage) +- [MDX](#mdx) +- [IFrame height](#iframe-height) +- [More resources](#more-resources) + +## Installation + +First add the package. Make sure that the versions for your `@storybook/*` packages match: + +```sh +yarn add -D @storybook/addon-docs@next +``` + +Then add the following to your `.storybook/main.js` addons: + +```js +module.exports = { + addons: ['@storybook/addon-docs'], +}; +``` + +## 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 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, { + 'ember-cli-storybook': { + enableAddonDocsIntegration: true, + }, +}); +``` + +Now, running the ember-cli server will generate a JSON documentation file at `/storybook-docgen/index.json`. Since generation of this file is tied into the ember-cli build, it will get regenerated everytime component files are saved. For details on documenting your components, check out the examples in the addon that powers the generation [ember-cli-addon-docs-yuidoc](https://github.com/ember-learn/ember-cli-addon-docs-yuidoc#documenting-components). + +Next, add the following to your `.storybook/preview.js` to load the generated json file: + +```js +import { setJSONDoc } from '@storybook/addon-docs/ember'; +import docJson from '../storybook-docgen/index.json'; +setJSONDoc(docJson); +``` + +Finally, be sure to fill in the `component` field in your story metadata. This should be a string that matches the name of the `@class` used in your souce comments: + +```ts +export default { + title: 'App Component', + component: 'AppComponent', +}; +``` + +If you haven't upgraded from `storiesOf`, you can use a parameter to do the same thing: + +```ts +import { storiesOf } from '@storybook/angular'; + +storiesOf('App Component', module) + .addParameters({ component: 'AppComponent' }) + .add( ... ); +``` + +## MDX + +[MDX](../docs/mdx.md) is a convenient way to document your components in Markdown and embed documentation components, such as stories and props tables, inline. + +Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you'll need to add these dependencies as well: + +```sh +yarn add -D react react-is babel-loader +``` + +Then update your `.storybook/main.js` to make sure you load MDX files: + +```js +module.exports = { + stories: ['../src/stories/**/*.stories.(js|mdx)'], +}; +``` + +Finally, you can create MDX files like this: + +```md +import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; +import { hbs } from 'ember-cli-htmlbars'; + + + +# App Component + +Some **markdown** description, or whatever you want. + +{{ + template: hbs``, +context: { title: "Title" }, +}} + +## Props + + +``` + +Yes, it's redundant to declare `component` twice. [Coming soon](https://github.com/storybookjs/storybook/issues/8673). + +Also, to use the `Props` doc block, you need to set up documentation generation, [as described above](#docspage). + +## IFrame height + +Storybook Docs renders all Ember stories inside `iframe`s, with a default height of `60px`. You can update this default globally, or modify the `iframe` height locally per story in `DocsPage` and `MDX`. + +To update the global default, modify `.storybook/preview.js`: + +```ts +import { addParameters } from '@storybook/ember'; + +addParameters({ docs: { iframeHeight: 400 } }); +``` + +For `DocsPage`, you need to update the parameter locally in a story: + +```ts +export const basic = () => ... +basic.story = { + parameters: { docs: { iframeHeight: 400 } } +} +``` + +And for `MDX` you can modify it as an attribute on the `Story` element: + +```md +{...} +``` + +## 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) +- Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/ember/index.js b/addons/docs/ember/index.js new file mode 100644 index 000000000000..1ef21f6687ba --- /dev/null +++ b/addons/docs/ember/index.js @@ -0,0 +1 @@ +module.exports = require('../dist/frameworks/ember'); diff --git a/addons/docs/package.json b/addons/docs/package.json index 07565121ce21..cb735d29f20a 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Superior documentation for your components", "keywords": [ "addon", @@ -19,10 +19,11 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "angular/**/*", "common/**/*", + "ember/**/*", "html/**/*", + "postinstall/**/*", "react/**/*", "vue/**/*", "web-components/**/*", @@ -41,23 +42,35 @@ "@babel/plugin-transform-react-jsx": "^7.3.0", "@egoist/vue-to-react": "^1.1.0", "@jest/transform": "^24.9.0", - "@mdx-js/loader": "^1.1.0", - "@mdx-js/mdx": "^1.1.0", - "@mdx-js/react": "^1.0.27", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@mdx-js/loader": "^1.5.1", + "@mdx-js/mdx": "^1.5.1", + "@mdx-js/react": "^1.5.1", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/csf": "0.0.1", + "@storybook/postinstall": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "acorn-walk": "^7.0.0", "core-js": "^3.0.1", "doctrine": "^3.0.0", + "escodegen": "^1.12.0", "global": "^4.3.2", + "html-tags": "^3.1.0", "js-string-escape": "^1.0.1", "lodash": "^4.17.15", "prop-types": "^15.7.2", - "storybook-addon-vue-info": "^1.2.1", - "ts-dedent": "^1.1.0" + "react-element-to-jsx-string": "^14.1.0", + "remark-external-links": "^5.0.0", + "remark-slug": "^5.1.2", + "ts-dedent": "^1.1.0", + "util-deprecate": "^1.0.2", + "vue-docgen-api": "^4.1.0", + "vue-docgen-loader": "^1.3.0-beta.0" }, "devDependencies": { "@types/doctrine": "^0.0.3", @@ -65,7 +78,7 @@ "@types/jest": "^24.0.11", "@types/prop-types": "^15.5.9", "@types/util-deprecate": "^1.0.0", - "@types/webpack-env": "^1.14.0" + "jest-specific-snapshot": "^2.0.0" }, "peerDependencies": { "babel-loader": "^8.0.0", @@ -74,5 +87,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/docs/postinstall/presets.js b/addons/docs/postinstall/presets.js new file mode 100644 index 000000000000..a8feae9101e4 --- /dev/null +++ b/addons/docs/postinstall/presets.js @@ -0,0 +1,39 @@ +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) { + const packageJson = JSON.parse(fs.readFileSync('./package.json')); + const frameworks = getFrameworks(packageJson); + + let err = null; + let framework = null; + let presetOptions = null; + if (frameworks.length !== 1) { + err = `${frameworks.length === 0 ? 'No' : 'Multiple'} frameworks found: ${frameworks}`; + logger.error(`${err}, please configure '@storybook/addon-docs' manually.`); + return file.source; + } + + // eslint-disable-next-line prefer-destructuring + framework = frameworks[0]; + + const { dependencies, devDependencies } = packageJson; + if ( + framework === 'react' && + ((dependencies && dependencies['react-scripts']) || + (devDependencies && devDependencies['react-scripts'])) + ) { + presetOptions = { + configureJSX: true, + }; + } + + const j = api.jscodeshift; + const root = j(file.source); + + presetsAddPreset(`@storybook/addon-docs/preset`, presetOptions, { root, api }); + + return root.toSource({ quote: 'single' }); +} diff --git a/addons/docs/preset.js b/addons/docs/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/docs/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/docs/react/README.md b/addons/docs/react/README.md new file mode 100644 index 000000000000..55d74e526abf --- /dev/null +++ b/addons/docs/react/README.md @@ -0,0 +1,122 @@ +
+ +
+ +# Storybook Docs for React + +> 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. + +To learn more about Storybook Docs, read the [general documentation](../README.md). To learn the React specifics, read on! + +- [Installation](#installation) +- [DocsPage](#docspage) +- [MDX](#mdx) +- [Inline stories](#inline-stories) +- [More resources](#more-resources) + +## Installation + +First add the package. Make sure that the versions for your `@storybook/*` packages match: + +```sh +yarn add -D @storybook/addon-docs@next +``` + +Then add the following to your `.storybook/main.js` list of `addons`: + +```js +module.exports = { + // other settings + addons: ['@storybook/addon-docs']; +} +``` + +## 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. + +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'; + +export default { + title: 'Button', + component: Button, +}; +``` + +If you haven't upgraded from `storiesOf`, you can use a parameter to do the same thing: + +```ts +import { storiesOf } from '@storybook/react'; +import { Button } from './Button'; + +storiesOf('InfoButton', module) + .addParameters({ component: Button }) + .add( ... ); +``` + +## MDX + +[MDX](../docs/mdx.md) is a convenient way to document your components in Markdown and embed documentation components, such as stories and props tables, inline. + +Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well: + +```sh +yarn add -D react react-is babel-loader +``` + +Then update your `.storybook/main.js` to make sure you load MDX files: + +```js +module.exports = { + stories: ['../src/stories/**/*.stories.(js|mdx)'], +}; +``` + +Finally, you can create MDX files like this: + +```md +import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; +import { Button } from './Button'; + + + +# Button + +Some **markdown** description, or whatever you want. + + + + + +## Props + + +``` + +## 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`: + +```js +import { addParameters } from '@storybook/react'; + +addParameters({ + docs: { + inlineStories: 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) +- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a) +- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf) +- Example: [Storybook Design System](https://github.com/storybookjs/design-system) diff --git a/addons/docs/src/blocks/Anchor.tsx b/addons/docs/src/blocks/Anchor.tsx index 912f20258eeb..7ed1160cfd74 100644 --- a/addons/docs/src/blocks/Anchor.tsx +++ b/addons/docs/src/blocks/Anchor.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent } from 'react'; +import React, { FC } from 'react'; export const anchorBlockIdFromId = (storyId: string) => `anchor--${storyId}`; @@ -6,6 +6,6 @@ export interface AnchorProps { storyId: string; } -export const Anchor: FunctionComponent = ({ storyId, children }) => ( +export const Anchor: FC = ({ storyId, children }) => (
{children}
); diff --git a/addons/docs/src/blocks/Description.tsx b/addons/docs/src/blocks/Description.tsx index 5858bf8eba93..ce34756634c3 100644 --- a/addons/docs/src/blocks/Description.tsx +++ b/addons/docs/src/blocks/Description.tsx @@ -1,8 +1,8 @@ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useContext } from 'react'; import { Description, DescriptionProps as PureDescriptionProps } from '@storybook/components'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { Component, CURRENT_SELECTION } from './shared'; -import { str } from '../lib/docgenUtils'; +import { Component, CURRENT_SELECTION, DescriptionSlot } from './shared'; +import { str } from '../lib/docgen'; export enum DescriptionType { INFO = 'info', @@ -16,24 +16,26 @@ type Notes = string | any; type Info = string | any; interface DescriptionProps { + slot?: DescriptionSlot; of?: '.' | Component; type?: DescriptionType; markdown?: string; + children?: string; } -export const getNotes = (notes?: Notes) => +const getNotes = (notes?: Notes) => notes && (typeof notes === 'string' ? notes : str(notes.markdown) || str(notes.text)); -export const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text)); +const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text)); const noDescription = (component?: Component): string | null => null; export const getDescriptionProps = ( - { of, type, markdown }: DescriptionProps, + { of, type, markdown, children }: DescriptionProps, { parameters }: DocsContextProps ): PureDescriptionProps => { - if (markdown) { - return { markdown }; + if (children || markdown) { + return { markdown: children || markdown }; } const { component, notes, info, docs } = parameters; const { extractComponentDescription = noDescription } = docs || {}; @@ -59,13 +61,19 @@ ${extractComponentDescription(target) || ''} } }; -const DescriptionContainer: FunctionComponent = props => ( - - {context => { - const { markdown } = getDescriptionProps(props, context); - return markdown && ; - }} - -); +const DescriptionContainer: FunctionComponent = props => { + const context = useContext(DocsContext); + const { slot } = props; + let { markdown } = getDescriptionProps(props, context); + if (slot) { + markdown = slot(markdown, context); + } + return markdown ? : null; +}; + +// since we are in the docs blocks, assume default description if for primary component story +DescriptionContainer.defaultProps = { + of: '.', +}; export { DescriptionContainer as Description }; diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index a415bac0b7d1..bc536a2361c2 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -1,44 +1,24 @@ import React, { FunctionComponent, useEffect } from 'react'; -import { document } from 'global'; +import { document, window } from 'global'; import { MDXProvider } from '@mdx-js/react'; import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming'; -import { DocsWrapper, DocsContent, Source } from '@storybook/components'; -import { components as htmlComponents, Code } from '@storybook/components/html'; +import { DocsWrapper, DocsContent } from '@storybook/components'; +import { components as htmlComponents } from '@storybook/components/html'; import { DocsContextProps, DocsContext } from './DocsContext'; import { anchorBlockIdFromId } from './Anchor'; import { storyBlockIdFromId } from './Story'; +import { CodeOrSourceMdx, AnchorMdx, HeadersMdx } from './mdx'; +import { scrollToElement } from './utils'; interface DocsContainerProps { context: DocsContextProps; } -interface CodeOrSourceProps { - className?: string; -} -export const CodeOrSource: FunctionComponent = props => { - const { className, children, ...rest } = props; - // markdown-to-jsx does not add className to inline code - if ( - typeof className !== 'string' && - (typeof children !== 'string' || !(children as string).match(/[\n\r]/g)) - ) { - return {children}; - } - // className: "lang-jsx" - const language = className && className.split('-'); - return ( - - ); -}; - const defaultComponents = { ...htmlComponents, - code: CodeOrSource, + code: CodeOrSourceMdx, + a: AnchorMdx, + ...HeadersMdx, }; export const DocsContainer: FunctionComponent = ({ context, children }) => { @@ -46,30 +26,45 @@ export const DocsContainer: FunctionComponent = ({ context, const options = parameters.options || {}; const theme = ensureTheme(options.theme); const { components: userComponents = null } = parameters.docs || {}; - const components = { ...defaultComponents, ...userComponents }; + const allComponents = { ...defaultComponents, ...userComponents }; useEffect(() => { - let element = document.getElementById(anchorBlockIdFromId(storyId)); - if (!element) { - element = document.getElementById(storyBlockIdFromId(storyId)); + let url; + try { + url = new URL(window.parent.location); + } catch (err) { + return; } - if (element) { - const allStories = element.parentElement.querySelectorAll('[id|="anchor-"]'); - let block = 'start'; - if (allStories && allStories[0] === element) { - block = 'end'; // first story should be shown with the intro content above + if (url.hash) { + const element = document.getElementById(url.hash.substring(1)); + if (element) { + // Introducing a delay to ensure scrolling works when it's a full refresh. + setTimeout(() => { + scrollToElement(element); + }, 200); + } + } else { + const element = + document.getElementById(anchorBlockIdFromId(storyId)) || + document.getElementById(storyBlockIdFromId(storyId)); + if (element) { + const allStories = element.parentElement.querySelectorAll('[id|="anchor-"]'); + let block = 'start'; + if (allStories && allStories[0] === element) { + block = 'end'; // first story should be shown with the intro content above + } + // Introducing a delay to ensure scrolling works when it's a full refresh. + setTimeout(() => { + scrollToElement(element, block); + }, 200); } - element.scrollIntoView({ - behavior: 'smooth', - block, - inline: 'nearest', - }); } }, [storyId]); + return ( - + {children} diff --git a/addons/docs/src/blocks/DocsContext.ts b/addons/docs/src/blocks/DocsContext.ts index a900ed260773..1babaafdddd6 100644 --- a/addons/docs/src/blocks/DocsContext.ts +++ b/addons/docs/src/blocks/DocsContext.ts @@ -6,11 +6,12 @@ export interface DocsContextProps { selectedStory?: string; /** - * mdxStoryNameToId is an MDX-compiler-generated mapping of an MDX story's - * display name to its storyId. It's used internally by the `` - * 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 `` + * and `Preview` doc blocks. */ - mdxStoryNameToId?: Record; + mdxStoryNameToKey?: Record; + 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 new file mode 100644 index 000000000000..92e7264a40cf --- /dev/null +++ b/addons/docs/src/blocks/DocsPage.test.ts @@ -0,0 +1,24 @@ +import { defaultTitleSlot } 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'); + }); + 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'); + }); + 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'); + }); +}); diff --git a/addons/docs/src/blocks/DocsPage.tsx b/addons/docs/src/blocks/DocsPage.tsx index 1187288d9626..a19a7038867a 100644 --- a/addons/docs/src/blocks/DocsPage.tsx +++ b/addons/docs/src/blocks/DocsPage.tsx @@ -1,140 +1,26 @@ import React, { FunctionComponent } from 'react'; - -import { parseKind } from '@storybook/router'; -import { DocsPage as PureDocsPage, PropsTable, PropsTableProps } from '@storybook/components'; -import { H2, H3 } from '@storybook/components/html'; -import { DocsContext } from './DocsContext'; +import { DocsPageProps } from './shared'; +import { Title } from './Title'; +import { Subtitle } from './Subtitle'; import { Description } from './Description'; -import { Story } from './Story'; -import { Preview } from './Preview'; -import { Anchor } from './Anchor'; -import { getPropsTableProps } from './Props'; - -export interface SlotContext { - id?: string; - selectedKind?: string; - selectedStory?: string; - parameters?: any; - storyStore?: any; -} - -export type StringSlot = (context: SlotContext) => string | void; -export type PropsSlot = (context: SlotContext) => PropsTableProps | void; -export type StorySlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps | void; -export type StoriesSlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps[] | void; - -export interface DocsPageProps { - titleSlot: StringSlot; - subtitleSlot: StringSlot; - descriptionSlot: StringSlot; - primarySlot: StorySlot; - propsSlot: PropsSlot; - storiesSlot: StoriesSlot; -} - -interface DocsStoryProps { - id: string; - name: string; - expanded?: boolean; - withToolbar?: boolean; - parameters?: any; -} - -interface StoryData { - id: string; - kind: string; - name: string; - parameters?: any; -} - -const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => { - const { - hierarchyRootSeparator: rootSeparator, - hierarchySeparator: groupSeparator, - } = (parameters && parameters.options) || { - hierarchyRootSeparator: '|', - hierarchySeparator: /\/|\./, - }; - const { groups } = parseKind(selectedKind, { rootSeparator, groupSeparator }); - return (groups && groups[groups.length - 1]) || selectedKind; -}; - -const defaultSubtitleSlot: StringSlot = ({ parameters }) => - parameters && parameters.componentSubtitle; - -const defaultPropsSlot: PropsSlot = context => getPropsTableProps({ of: '.' }, context); - -const defaultDescriptionSlot: StringSlot = ({ parameters }) => { - const { component, docs } = parameters; - if (!component) { - return null; - } - const { extractComponentDescription } = docs || {}; - return extractComponentDescription && extractComponentDescription(component, parameters); -}; - -const defaultPrimarySlot: StorySlot = stories => stories && stories[0]; -const defaultStoriesSlot: StoriesSlot = stories => { - if (stories && stories.length > 1) { - const [first, ...rest] = stories; - return rest; - } - return null; -}; - -const StoriesHeading = H2; -const StoryHeading = H3; - -const DocsStory: FunctionComponent = ({ - id, - name, - expanded = true, - withToolbar = false, - parameters, -}) => ( - - {expanded && {(parameters && parameters.displayName) || name}} - {expanded && parameters && parameters.docs && parameters.docs.storyDescription && ( - - )} - - - - -); +import { Primary } from './Primary'; +import { Props } from './Props'; +import { Stories } from './Stories'; export const DocsPage: FunctionComponent = ({ - titleSlot = defaultTitleSlot, - subtitleSlot = defaultSubtitleSlot, - descriptionSlot = defaultDescriptionSlot, - primarySlot = defaultPrimarySlot, - propsSlot = defaultPropsSlot, - storiesSlot = defaultStoriesSlot, + titleSlot, + subtitleSlot, + descriptionSlot, + primarySlot, + propsSlot, + storiesSlot, }) => ( - - {context => { - const title = titleSlot(context) || ''; - const subtitle = subtitleSlot(context) || ''; - const description = descriptionSlot(context) || ''; - const propsTableProps = propsSlot(context); - - const { selectedKind, storyStore } = context; - const componentStories = storyStore - .getStoriesForKind(selectedKind) - .filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable)); - const primary = primarySlot(componentStories, context); - const stories = storiesSlot(componentStories, context); - - return ( - - - {primary && } - {propsTableProps && } - {stories && stories.length > 0 && Stories} - {stories && - stories.map(story => story && )} - - ); - }} - + <> + + <Subtitle slot={subtitleSlot} /> + <Description slot={descriptionSlot} /> + <Primary slot={primarySlot} /> + <Props slot={propsSlot} /> + <Stories slot={storiesSlot} /> + </> ); diff --git a/addons/docs/src/blocks/DocsStory.tsx b/addons/docs/src/blocks/DocsStory.tsx new file mode 100644 index 000000000000..ca6640ad1832 --- /dev/null +++ b/addons/docs/src/blocks/DocsStory.tsx @@ -0,0 +1,25 @@ +import React, { FunctionComponent } from 'react'; +import { Subheading } from './Subheading'; +import { DocsStoryProps } from './shared'; +import { Anchor } from './Anchor'; +import { Description } from './Description'; +import { Story } from './Story'; +import { Preview } from './Preview'; + +export const DocsStory: FunctionComponent<DocsStoryProps> = ({ + id, + name, + expanded = true, + withToolbar = false, + parameters, +}) => ( + <Anchor storyId={id}> + {expanded && <Subheading>{name}</Subheading>} + {expanded && parameters && parameters.docs && parameters.docs.storyDescription && ( + <Description markdown={parameters.docs.storyDescription} /> + )} + <Preview withToolbar={withToolbar}> + <Story id={id} /> + </Preview> + </Anchor> +); diff --git a/addons/docs/src/blocks/Heading.tsx b/addons/docs/src/blocks/Heading.tsx new file mode 100644 index 000000000000..0b3445ce85b1 --- /dev/null +++ b/addons/docs/src/blocks/Heading.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent } from 'react'; +import { H2 } from '@storybook/components/html'; +import { HeaderMdx } from './mdx'; + +export interface HeadingProps { + children: JSX.Element | string; + disableAnchor?: boolean; +} + +export const Heading: FunctionComponent<HeadingProps> = ({ children, disableAnchor }) => { + if (disableAnchor || typeof children !== 'string') { + return <H2>{children}</H2>; + } + const tagID = children.toLowerCase().replace(/[^a-z0-9]/gi, '-'); + return ( + <HeaderMdx as="h2" id={tagID}> + {children} + </HeaderMdx> + ); +}; diff --git a/addons/docs/src/blocks/Meta.tsx b/addons/docs/src/blocks/Meta.tsx index 49d43ae83b0a..b4416cf4cb9f 100644 --- a/addons/docs/src/blocks/Meta.tsx +++ b/addons/docs/src/blocks/Meta.tsx @@ -1,4 +1,8 @@ -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'; type Decorator = (...args: any) => any; @@ -9,9 +13,27 @@ interface MetaProps { 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..05f4cada390b 100644 --- a/addons/docs/src/blocks/Preview.tsx +++ b/addons/docs/src/blocks/Preview.tsx @@ -1,4 +1,5 @@ import React, { FunctionComponent, ReactElement, ReactNode, ReactNodeArray } from 'react'; +import { toId, storyNameFromExport } from '@storybook/csf'; import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components'; import { getSourceProps } from './Source'; import { DocsContext, DocsContextProps } from './DocsContext'; @@ -21,7 +22,7 @@ const getPreviewProps = ( children, ...props }: PreviewProps & { children?: ReactNode }, - { mdxStoryNameToId, storyStore }: DocsContextProps + { mdxStoryNameToKey, mdxComponentMeta, storyStore }: DocsContextProps ): PurePreviewProps => { if (withSource === SourceState.NONE) { return props; @@ -36,7 +37,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. diff --git a/addons/docs/src/blocks/Primary.tsx b/addons/docs/src/blocks/Primary.tsx new file mode 100644 index 000000000000..fe2b764fcaa6 --- /dev/null +++ b/addons/docs/src/blocks/Primary.tsx @@ -0,0 +1,16 @@ +import React, { useContext, FunctionComponent } from 'react'; +import { DocsContext } from './DocsContext'; +import { DocsStory } from './DocsStory'; +import { getDocsStories } from './utils'; +import { StorySlot } from './shared'; + +interface PrimaryProps { + slot?: StorySlot; +} + +export const Primary: FunctionComponent<PrimaryProps> = ({ slot }) => { + const context = useContext(DocsContext); + const componentStories = getDocsStories(context); + const story = slot ? slot(componentStories, context) : componentStories && 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 1656af599041..21a4d573852a 100644 --- a/addons/docs/src/blocks/Props.tsx +++ b/addons/docs/src/blocks/Props.tsx @@ -1,15 +1,30 @@ -import React, { FunctionComponent } from 'react'; -import { PropsTable, PropsTableError, PropsTableProps } from '@storybook/components'; +import React, { FunctionComponent, useContext } from 'react'; +import { isNil } from 'lodash'; + +import { + PropsTable, + PropsTableError, + PropsTableProps, + PropsTableRowsProps, + PropsTableSectionsProps, + PropDef, + TabsState, +} from '@storybook/components'; import { DocsContext, DocsContextProps } from './DocsContext'; -import { Component, CURRENT_SELECTION } from './shared'; +import { Component, PropsSlot, CURRENT_SELECTION } from './shared'; +import { getComponentName } from './utils'; -import { PropsExtractor } from '../lib/docgenUtils'; +import { PropsExtractor } from '../lib/docgen/types'; import { extractProps as reactExtractProps } from '../frameworks/react/extractProps'; import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps'; interface PropsProps { exclude?: string[]; - of: '.' | Component; + of?: '.' | Component; + components?: { + [label: string]: Component; + }; + slot?: PropsSlot; } // FIXME: remove in SB6.0 & require config @@ -24,36 +39,109 @@ const inferPropsExtractor = (framework: string): PropsExtractor | null => { } }; -export const getPropsTableProps = ( - { exclude, of }: PropsProps, +const filterRows = (rows: PropDef[], exclude: string[]) => + rows && rows.filter((row: PropDef) => !exclude.includes(row.name)); + +export const getComponentProps = ( + component: Component, + { exclude }: PropsProps, { parameters }: DocsContextProps ): PropsTableProps => { + if (!component) { + return null; + } try { const params = parameters || {}; - const { component, framework = null } = params; + const { framework = null } = params; - const target = of === CURRENT_SELECTION ? component : of; - if (!target) { - throw new Error(PropsTableError.NO_COMPONENT); - } - - const { extractProps = inferPropsExtractor(framework) } = params.docs || {}; + const { extractProps = inferPropsExtractor(framework) }: { extractProps: PropsExtractor } = + params.docs || {}; if (!extractProps) { throw new Error(PropsTableError.PROPS_UNSUPPORTED); } - return extractProps(target, { exclude }); + 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); + }); + } + } + + return props; } catch (err) { return { error: err.message }; } }; -const PropsContainer: FunctionComponent<PropsProps> = props => ( - <DocsContext.Consumer> - {context => { - const propsTableProps = getPropsTableProps(props, context); - return <PropsTable {...propsTableProps} />; - }} - </DocsContext.Consumer> -); +export const getComponent = (props: PropsProps = {}, context: DocsContextProps): Component => { + const { of } = props; + const { parameters = {} } = context; + const { component } = parameters; + + const target = of === CURRENT_SELECTION ? component : of; + if (!target) { + if (of === CURRENT_SELECTION) { + return null; + } + throw new Error(PropsTableError.NO_COMPONENT); + } + return target; +}; + +const PropsContainer: FunctionComponent<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); + + if (!subcomponents || typeof subcomponents !== 'object') { + return mainProps && <PropsTable {...mainProps} />; + } + + 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), + }); + }); + + 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> + ); +}; + +PropsContainer.defaultProps = { + of: '.', +}; export { PropsContainer as Props }; diff --git a/addons/docs/src/blocks/Source.tsx b/addons/docs/src/blocks/Source.tsx index dbeecd969059..fcd13ceac9cd 100644 --- a/addons/docs/src/blocks/Source.tsx +++ b/addons/docs/src/blocks/Source.tsx @@ -5,6 +5,7 @@ import { CURRENT_SELECTION } from './shared'; interface CommonProps { language?: string; + dark?: boolean; } type SingleSourceProps = { @@ -34,17 +35,23 @@ interface StorySource { } 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) { + 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), @@ -76,7 +83,7 @@ export const getSourceProps = ( .join('\n\n'); } return source - ? { code: source, language: props.language || 'jsx' } + ? { code: source, language: props.language || 'jsx', dark: props.dark || false } : { error: SourceError.SOURCE_UNAVAILABLE }; }; diff --git a/addons/docs/src/blocks/Stories.tsx b/addons/docs/src/blocks/Stories.tsx new file mode 100644 index 000000000000..77b299035a1e --- /dev/null +++ b/addons/docs/src/blocks/Stories.tsx @@ -0,0 +1,33 @@ +import React, { useContext, FunctionComponent } from 'react'; +import { DocsContext } from './DocsContext'; +import { DocsStory } from './DocsStory'; +import { Heading } from './Heading'; +import { getDocsStories } from './utils'; +import { StoriesSlot, DocsStoryProps } from './shared'; + +interface StoriesProps { + slot?: StoriesSlot; + title?: JSX.Element | string; +} + +export const Stories: FunctionComponent<StoriesProps> = ({ slot, title }) => { + const context = useContext(DocsContext); + const componentStories = getDocsStories(context); + + const stories: DocsStoryProps[] = slot + ? slot(componentStories, context) + : componentStories && componentStories.slice(1); + if (!stories || stories.length === 0) { + return null; + } + return ( + <> + <Heading>{title}</Heading> + {stories.map(story => story && <DocsStory key={story.id} {...story} expanded />)} + </> + ); +}; + +Stories.defaultProps = { + title: 'Stories', +}; diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index f9a4902b08f4..71bb693dd36e 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -2,6 +2,7 @@ import React, { createElement, ElementType, FunctionComponent, ReactNode } from import { MDXProvider } from '@mdx-js/react'; import { components as docsComponents } from '@storybook/components/html'; import { Story, StoryProps as PureStoryProps } from '@storybook/components'; +import { toId, storyNameFromExport } from '@storybook/csf'; import { CURRENT_SELECTION } from './shared'; import { DocsContext, DocsContextProps } from './DocsContext'; @@ -40,12 +41,17 @@ const inferInlineStories = (framework: string): boolean => { export const getStoryProps = ( props: StoryProps, - { id: currentId, storyStore, parameters, mdxStoryNameToId }: DocsContextProps | null + { id: currentId, storyStore, mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps | null ): PureStoryProps => { const { id } = props as StoryRefProps; const { name } = props as StoryDefProps; const inputId = id === CURRENT_SELECTION ? currentId : id; - const previewId = inputId || mdxStoryNameToId[name]; + const previewId = + inputId || + toId( + mdxComponentMeta.id || mdxComponentMeta.title, + storyNameFromExport(mdxStoryNameToKey[name]) + ); const { height, inline } = props; const data = storyStore.fromId(previewId); diff --git a/addons/docs/src/blocks/Subheading.tsx b/addons/docs/src/blocks/Subheading.tsx new file mode 100644 index 000000000000..244b7430dc01 --- /dev/null +++ b/addons/docs/src/blocks/Subheading.tsx @@ -0,0 +1,16 @@ +import React, { FunctionComponent } from 'react'; +import { H3 } from '@storybook/components/html'; +import { HeaderMdx } from './mdx'; +import { HeadingProps } from './Heading'; + +export const Subheading: FunctionComponent<HeadingProps> = ({ children, disableAnchor }) => { + if (disableAnchor || typeof children !== 'string') { + return <H3>{children}</H3>; + } + const tagID = children.toLowerCase().replace(/[^a-z0-9]/gi, '-'); + return ( + <HeaderMdx as="h3" id={tagID}> + {children} + </HeaderMdx> + ); +}; diff --git a/addons/docs/src/blocks/Subtitle.tsx b/addons/docs/src/blocks/Subtitle.tsx new file mode 100644 index 000000000000..595bac6452f2 --- /dev/null +++ b/addons/docs/src/blocks/Subtitle.tsx @@ -0,0 +1,19 @@ +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 }) => { + const context = useContext(DocsContext); + const { parameters } = context; + let text: JSX.Element | string = children; + if (!text) { + text = slot ? slot(context) : parameters && 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 new file mode 100644 index 000000000000..bbc4533bb61e --- /dev/null +++ b/addons/docs/src/blocks/Title.tsx @@ -0,0 +1,44 @@ +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'; + +interface TitleProps { + slot?: StringSlot; + children?: JSX.Element | string; +} +export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => { + const { + showRoots, + hierarchyRootSeparator: rootSeparator = '|', + hierarchySeparator: groupSeparator = /\/|\./, + } = (parameters && parameters.options) || {}; + + let groups; + if (typeof showRoots !== 'undefined') { + groups = selectedKind.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 })); + } + + return (groups && groups[groups.length - 1]) || selectedKind; +}; + +export const Title: FunctionComponent<TitleProps> = ({ slot, 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 }); + } + } + return text ? <PureTitle className="sbdocs-title">{text}</PureTitle> : null; +}; diff --git a/addons/docs/src/blocks/index.ts b/addons/docs/src/blocks/index.ts index 4e68cf50276c..f4f5def5d237 100644 --- a/addons/docs/src/blocks/index.ts +++ b/addons/docs/src/blocks/index.ts @@ -5,12 +5,19 @@ export * from './Description'; export * from './DocsContext'; export * from './DocsPage'; export * from './DocsContainer'; +export * from './DocsStory'; +export * from './Heading'; export * from './Meta'; export * from './Preview'; +export * from './Primary'; export * from './Props'; export * from './Source'; +export * from './Stories'; export * from './Story'; +export * from './Subheading'; +export * from './Subtitle'; +export * from './Title'; export * from './Wrapper'; -// helper function for MDX -export const makeStoryFn = (val: any) => (typeof val === 'function' ? val : () => val); +export * from './shared'; +export * from './mdx'; diff --git a/addons/docs/src/blocks/mdx.tsx b/addons/docs/src/blocks/mdx.tsx new file mode 100644 index 000000000000..473ee8a57ee8 --- /dev/null +++ b/addons/docs/src/blocks/mdx.tsx @@ -0,0 +1,211 @@ +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 } from 'global'; +import { styled } from '@storybook/theming'; +import { DocsContext, DocsContextProps } from './DocsContext'; + +// Hacky utility for asserting identifiers in MDX Story elements +export const assertIsFn = (val: any) => { + if (typeof val !== 'function') { + throw new Error(`Expected story function, got: ${val}`); + } + return val; +}; + +// Hacky utilty for adding mdxStoryToId to the default context +export const AddContext: FC<DocsContextProps> = props => { + const { children, ...rest } = props; + const parentContext = React.useContext(DocsContext); + return ( + <DocsContext.Provider value={{ ...parentContext, ...rest }}>{children}</DocsContext.Provider> + ); +}; + +interface CodeOrSourceMdxProps { + className?: string; +} + +export const CodeOrSourceMdx: FC<CodeOrSourceMdxProps> = ({ className, children, ...rest }) => { + // markdown-to-jsx does not add className to inline code + if ( + typeof className !== 'string' && + (typeof children !== 'string' || !(children as string).match(/[\n\r]/g)) + ) { + return <Code>{children}</Code>; + } + // className: "lang-jsx" + const language = className && className.split('-'); + return ( + <Source + language={(language && language[1]) || 'plaintext'} + format={false} + code={children as string} + {...rest} + /> + ); +}; + +function navigate(url: string) { + addons.getChannel().emit(NAVIGATE_URL, url); +} + +// @ts-ignore +const A = components.a; + +interface AnchorInPageProps { + hash: string; +} + +const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => ( + <A + href={hash} + target="_self" + onClick={(event: SyntheticEvent) => { + const id = hash.substring(1); + const element = document.getElementById(id); + if (element) { + navigate(hash); + } + }} + > + {children} + </A> +); + +interface AnchorMdxProps { + href: string; + target: string; +} + +export const AnchorMdx: FC<AnchorMdxProps> = props => { + const { href, target, children, ...rest } = props; + + if (href) { + // Enable scrolling for in-page anchors. + if (href.startsWith('#')) { + return <AnchorInPage hash={href}>{children}</AnchorInPage>; + } + + // 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') { + return ( + <A + href={href} + onClick={(event: SyntheticEvent) => { + event.preventDefault(); + navigate(href); + }} + target={target} + {...rest} + > + {children} + </A> + ); + } + } + + // External URL dont need any modification. + return <A {...props} />; +}; + +const SUPPORTED_MDX_HEADERS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; + +const OcticonHeaders = SUPPORTED_MDX_HEADERS.reduce( + (acc, headerType) => ({ + ...acc, + // @ts-ignore + [headerType]: styled(components[headerType])({ + '& svg': { + visibility: 'hidden', + }, + '&:hover svg': { + visibility: 'visible', + }, + }), + }), + {} +); + +const OcticonAnchor = styled.a(() => ({ + float: 'left', + paddingRight: '4px', + marginLeft: '-20px', +})); + +interface HeaderWithOcticonAnchorProps { + as: string; + id: string; + children: any; +} + +const HeaderWithOcticonAnchor: FC<HeaderWithOcticonAnchorProps> = ({ + as, + id, + children, + ...rest +}) => { + // @ts-ignore + const OcticonHeader = OcticonHeaders[as]; + const hash = `#${id}`; + + return ( + <OcticonHeader id={id} {...rest}> + <OcticonAnchor + aria-hidden="true" + href={hash} + tabIndex={-1} + target="_self" + onClick={(event: SyntheticEvent) => { + const element = document.getElementById(id); + if (element) { + navigate(hash); + } + }} + > + <svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"> + <path + fillRule="evenodd" + d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z" + /> + </svg> + </OcticonAnchor> + {children} + </OcticonHeader> + ); +}; + +interface HeaderMdxProps { + as: string; + id: string; +} + +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 (id) { + return ( + <HeaderWithOcticonAnchor as={as} id={id} {...rest}> + {children} + </HeaderWithOcticonAnchor> + ); + } + + // @ts-ignore + const Header = components[as]; + + // Make sure it still work if "remark-slug" plugin is not present. + return <Header {...props} />; +}; + +export const HeadersMdx = SUPPORTED_MDX_HEADERS.reduce( + (acc, headerType) => ({ + ...acc, + // @ts-ignore + [headerType]: (props: object) => <HeaderMdx as={headerType} {...props} />, + }), + {} +); diff --git a/addons/docs/src/blocks/shared.ts b/addons/docs/src/blocks/shared.ts index 61d2a7f93f5f..baab2861c309 100644 --- a/addons/docs/src/blocks/shared.ts +++ b/addons/docs/src/blocks/shared.ts @@ -1,2 +1,41 @@ +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/utils.ts b/addons/docs/src/blocks/utils.ts new file mode 100644 index 000000000000..4125fdc83afc --- /dev/null +++ b/addons/docs/src/blocks/utils.ts @@ -0,0 +1,47 @@ +/* eslint-disable no-underscore-dangle */ +import { DocsContextProps } from './DocsContext'; +import { StoryData, Component } from './shared'; + +export const getDocsStories = (context: DocsContextProps): StoryData[] => { + const { storyStore, selectedKind } = context; + + if (!storyStore) { + return []; + } + + return storyStore + .getStoriesForKind(selectedKind) + .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)) + .join(''); + +export const getComponentName = (component: Component): string => { + if (!component) { + return undefined; + } + + if (typeof component === 'string') { + if (component.includes('-')) { + return titleCase(component); + } + return component; + } + if (component.__docgenInfo && component.__docgenInfo.displayName) { + return component.__docgenInfo.displayName; + } + + return component.name; +}; + +export function scrollToElement(element: any, block = 'start') { + element.scrollIntoView({ + behavior: 'smooth', + block, + inline: 'nearest', + }); +} diff --git a/addons/docs/src/frameworks/angular/compodoc.ts b/addons/docs/src/frameworks/angular/compodoc.ts index a8997ff0c11c..8362919f1755 100644 --- a/addons/docs/src/frameworks/angular/compodoc.ts +++ b/addons/docs/src/frameworks/angular/compodoc.ts @@ -2,7 +2,7 @@ /* global window */ import { PropDef } from '@storybook/components'; -import { Argument, CompodocJson, Component, Method, Property } from './types'; +import { Argument, CompodocJson, Component, Method, Property, Directive } from './types'; type Sections = Record<string, PropDef[]>; @@ -18,7 +18,7 @@ export const setCompodocJson = (compodocJson: CompodocJson) => { // @ts-ignore export const getCompdocJson = (): CompodocJson => window.__STORYBOOK_COMPODOC_JSON__; -export const checkValidComponent = (component: Component) => { +export const checkValidComponentOrDirective = (component: Component | Directive) => { if (!component.name) { throw new Error(`Invalid component ${JSON.stringify(component)}`); } @@ -71,15 +71,18 @@ const mapItemToSection = (key: string, item: Method | Property): string => { } }; -const getComponentData = (component: Component) => { +const getComponentData = (component: Component | Directive) => { if (!component) { return null; } - checkValidComponent(component); + checkValidComponentOrDirective(component); const compodocJson = getCompdocJson(); checkValidCompodocJson(compodocJson); const { name } = component; - return compodocJson.components.find((c: Component) => c.name === name); + return ( + compodocJson.components.find((c: Component) => c.name === name) || + compodocJson.directives.find((c: Directive) => c.name === name) + ); }; const displaySignature = (item: Method): string => { @@ -89,7 +92,7 @@ const displaySignature = (item: Method): string => { return `(${args.join(', ')}) => ${item.returnType}`; }; -export const extractProps = (component: Component) => { +export const extractProps = (component: Component | Directive) => { const componentData = getComponentData(component); if (!componentData) { return null; @@ -104,10 +107,10 @@ export const extractProps = (component: Component) => { data.forEach((item: Method | Property) => { const sectionItem: PropDef = { name: item.name, - type: { name: isMethod(item) ? displaySignature(item) : item.type }, + type: { summary: isMethod(item) ? displaySignature(item) : item.type }, required: isMethod(item) ? false : !item.optional, description: item.description, - defaultValue: isMethod(item) ? '' : item.defaultValue, + defaultValue: { summary: isMethod(item) ? '' : item.defaultValue }, }; const section = mapItemToSection(key, item); @@ -140,7 +143,7 @@ export const extractProps = (component: Component) => { return isEmpty(sections) ? null : { sections }; }; -export const extractComponentDescription = (component: Component) => { +export const extractComponentDescription = (component: Component | Directive) => { const componentData = getComponentData(component); if (!componentData) { return null; diff --git a/addons/docs/src/frameworks/angular/types.ts b/addons/docs/src/frameworks/angular/types.ts index eea87f4e8fb0..9bc7f760c852 100644 --- a/addons/docs/src/frameworks/angular/types.ts +++ b/addons/docs/src/frameworks/angular/types.ts @@ -15,7 +15,7 @@ export interface Property { description?: string; } -export interface Component { +export interface Directive { name: string; propertiesClass: Property[]; inputsClass: Property[]; @@ -24,6 +24,8 @@ export interface Component { rawdescription: string; } +export type Component = Directive; + export interface Argument { name: string; type: string; @@ -35,5 +37,6 @@ export interface Decorator { } export interface CompodocJson { + directives: Directive[]; components: Component[]; } diff --git a/addons/docs/src/frameworks/common/config.js b/addons/docs/src/frameworks/common/config.ts similarity index 100% rename from addons/docs/src/frameworks/common/config.js rename to addons/docs/src/frameworks/common/config.ts diff --git a/addons/docs/src/frameworks/common/index.ts b/addons/docs/src/frameworks/common/index.ts deleted file mode 100644 index 20a01c967eaa..000000000000 --- a/addons/docs/src/frameworks/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../../lib/docgenUtils'; diff --git a/addons/docs/src/frameworks/common/makePreset.ts b/addons/docs/src/frameworks/common/makePreset.ts index 420144b5ef42..08654c15063b 100644 --- a/addons/docs/src/frameworks/common/makePreset.ts +++ b/addons/docs/src/frameworks/common/makePreset.ts @@ -1,25 +1,17 @@ -import fs from 'fs'; -import * as common from './preset'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; +import * as common from '../../preset'; const makePreset = (framework: string) => { - const docsConfig = [`${__dirname}/config.js`]; - const frameworkConfig = `${__dirname}/../../../dist/frameworks/${framework}/config.js`; - if (fs.existsSync(frameworkConfig)) { - docsConfig.push(frameworkConfig); - } - function config(entry: any[] = []) { - return [...docsConfig, ...entry]; - } + deprecate( + () => {}, + dedent` + Framework-specific presets are no longer-needed as of Storybook 5.3 and will be removed in 6.0. - const configureJSX = framework !== 'react'; - const webpack = (webpackConfig: any, options: any) => - common.webpack(webpackConfig, { configureJSX, ...options }); - - return { - ...common, - webpack, - config, - }; + 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 9566d365f16a..f1c5764d6508 100644 --- a/addons/docs/src/frameworks/common/preset.ts +++ b/addons/docs/src/frameworks/common/preset.ts @@ -1,5 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import createCompiler from '@storybook/addon-docs/mdx-compiler-plugin'; +import remarkSlug from 'remark-slug'; +import remarkExternalLinks from 'remark-external-links'; function createBabelOptions(babelOptions?: any, configureJSX?: boolean) { if (!configureJSX) { @@ -20,7 +22,15 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { const { module = {} } = webpackConfig; // it will reuse babel options that are already in use in storybook // also, these babel options are chained with other presets. - const { babelOptions, configureJSX, sourceLoaderOptions = {} } = options; + const { + babelOptions, + configureJSX = options.framework !== 'react', // if not user-specified + sourceLoaderOptions = {}, + } = options; + + const mdxLoaderOptions = { + remarkPlugins: [remarkSlug, remarkExternalLinks], + }; // set `sourceLoaderOptions` to `null` to disable for manual configuration const sourceLoader = sourceLoaderOptions @@ -34,12 +44,24 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ] : []; - return { + const result = { ...webpackConfig, module: { ...module, rules: [ ...(module.rules || []), + { + test: /\.js$/, + include: /node_modules\/acorn-jsx/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [[require.resolve('@babel/preset-env'), { modules: 'commonjs' }]], + }, + }, + ], + }, { test: /\.(stories|story).mdx$/, use: [ @@ -51,6 +73,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { loader: '@mdx-js/loader', options: { compilers: [createCompiler(options)], + ...mdxLoaderOptions, }, }, ], @@ -65,6 +88,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { }, { loader: '@mdx-js/loader', + options: mdxLoaderOptions, }, ], }, @@ -72,8 +96,20 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ], }, }; + return result; } -export function addons(entry: any[] = []) { +export function managerEntries(entry: any[] = [], options: any) { return [...entry, require.resolve('../../register')]; } + +export function config(entry: any[] = [], options: any = {}) { + const { framework } = options; + const docsConfig = [require.resolve('./config')]; + try { + docsConfig.push(require.resolve(`../${framework}/config`)); + } catch (err) { + // there is no custom config for the user's framework, do nothing + } + return [...docsConfig, ...entry]; +} diff --git a/addons/docs/src/frameworks/ember/config.js b/addons/docs/src/frameworks/ember/config.js new file mode 100644 index 000000000000..2ba143d9b433 --- /dev/null +++ b/addons/docs/src/frameworks/ember/config.js @@ -0,0 +1,11 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { addParameters } from '@storybook/client-api'; +import { extractProps, extractComponentDescription } from './jsondoc'; + +addParameters({ + docs: { + iframeHeight: 80, + extractProps, + extractComponentDescription, + }, +}); diff --git a/addons/docs/src/frameworks/ember/index.js b/addons/docs/src/frameworks/ember/index.js new file mode 100644 index 000000000000..fab7166db9d7 --- /dev/null +++ b/addons/docs/src/frameworks/ember/index.js @@ -0,0 +1 @@ +export { setJSONDoc } from './jsondoc'; diff --git a/addons/docs/src/frameworks/ember/jsondoc.js b/addons/docs/src/frameworks/ember/jsondoc.js new file mode 100644 index 000000000000..a3a996d548c6 --- /dev/null +++ b/addons/docs/src/frameworks/ember/jsondoc.js @@ -0,0 +1,30 @@ +/* eslint-disable no-underscore-dangle */ +/* global window */ + +export const setJSONDoc = jsondoc => { + window.__EMBER_GENERATED_DOC_JSON__ = jsondoc; +}; +export const getJSONDoc = () => { + return window.__EMBER_GENERATED_DOC_JSON__; +}; + +export const extractProps = componentName => { + const json = getJSONDoc(); + const componentDoc = json.included.find(doc => doc.attributes.name === componentName); + 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, + }; + }); + return { rows }; +}; + +export const extractComponentDescription = componentName => { + const json = getJSONDoc(); + const componentDoc = json.included.find(doc => doc.attributes.name === componentName); + return componentDoc.attributes.description; +}; diff --git a/addons/docs/src/frameworks/react/config.ts b/addons/docs/src/frameworks/react/config.ts index 765e2813e984..11c34986a0a6 100644 --- a/addons/docs/src/frameworks/react/config.ts +++ b/addons/docs/src/frameworks/react/config.ts @@ -2,7 +2,7 @@ import { addParameters } from '@storybook/client-api'; import { StoryFn } from '@storybook/addons'; import { extractProps } from './extractProps'; -import { extractComponentDescription } from '../../lib/docgenUtils'; +import { extractComponentDescription } from '../../lib/docgen'; addParameters({ docs: { diff --git a/addons/docs/src/frameworks/react/extractProps.ts b/addons/docs/src/frameworks/react/extractProps.ts index 02da95bfb411..20d3032054c6 100644 --- a/addons/docs/src/frameworks/react/extractProps.ts +++ b/addons/docs/src/frameworks/react/extractProps.ts @@ -1,12 +1,10 @@ import PropTypes from 'prop-types'; import { isForwardRef, isMemo } from 'react-is'; import { PropDef } from '@storybook/components'; -import { - PropDefGetter, - PropsExtractor, - extractPropsFromDocgen, - hasDocgen, -} from '../../lib/docgenUtils'; +import { hasDocgen, extractComponentProps, PropsExtractor, TypeSystem } from '../../lib/docgen'; +import { Component } from '../../blocks/shared'; +import { enhancePropTypesProps } from './propTypes/handleProp'; +import { enhanceTypeScriptProps } from './typeScript/handleProp'; export interface PropDefMap { [p: string]: PropDef; @@ -22,21 +20,33 @@ Object.keys(PropTypes).forEach(typeName => { propTypesMap.set(type.isRequired, typeName); }); -export const getPropDefs: PropDefGetter = (type, section) => { - let processedType = type; +function getPropDefs(component: Component, section: string): PropDef[] { + let processedComponent = component; // eslint-disable-next-line react/forbid-foreign-prop-types - if (!hasDocgen(type) && !type.propTypes) { - if (isForwardRef(type) || type.render) { - processedType = type.render().type; + if (!hasDocgen(component) && !component.propTypes) { + if (isForwardRef(component) || component.render) { + processedComponent = component.render().type; } - if (isMemo(type)) { - processedType = type.type().type; + if (isMemo(component)) { + processedComponent = component.type().type; } } - return extractPropsFromDocgen(processedType, section); -}; + const extractedProps = extractComponentProps(processedComponent, section); + if (extractedProps.length === 0) { + return []; + } + + switch (extractedProps[0].typeSystem) { + case TypeSystem.JAVASCRIPT: + return enhancePropTypesProps(extractedProps, component); + case TypeSystem.TYPESCRIPT: + return enhanceTypeScriptProps(extractedProps); + default: + return extractedProps.map(x => x.propDef); + } +} export const extractProps: PropsExtractor = component => ({ rows: getPropDefs(component, 'props'), diff --git a/addons/docs/src/frameworks/react/lib/captions.ts b/addons/docs/src/frameworks/react/lib/captions.ts new file mode 100644 index 000000000000..83f968d2300a --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/captions.ts @@ -0,0 +1,6 @@ +export const CUSTOM_CAPTION = 'custom'; +export const OBJECT_CAPTION = 'object'; +export const ARRAY_CAPTION = 'array'; +export const CLASS_CAPTION = 'class'; +export const FUNCTION_CAPTION = 'func'; +export const ELEMENT_CAPTION = 'element'; diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts b/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts new file mode 100644 index 000000000000..09ead809bc50 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/createDefaultValue.ts @@ -0,0 +1,85 @@ +import { isNil } from 'lodash'; +import { PropDefaultValue } from '@storybook/components'; +import { FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions'; +import { + InspectionFunction, + InspectionResult, + InspectionType, + InspectionElement, + InspectionIdentifiableInferedType, + inspectValue, +} from '../inspection'; +import { isHtmlTag } from '../isHtmlTag'; +import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib'; +import { generateCode } from '../generateCode'; +import { generateObject } from './generateObject'; +import { generateArray } from './generateArray'; +import { getPrettyIdentifier } from './prettyIdentifier'; + +function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue { + const { identifier } = inferedType as InspectionFunction; + + if (!isNil(identifier)) { + return createSummaryValue( + getPrettyIdentifier(inferedType as InspectionIdentifiableInferedType), + generateCode(ast) + ); + } + + const prettyCaption = generateCode(ast, true); + + return !isTooLongForDefaultValueSummary(prettyCaption) + ? createSummaryValue(prettyCaption) + : createSummaryValue(FUNCTION_CAPTION, generateCode(ast)); +} + +// All elements are JSX elements. +// JSX elements are not supported by escodegen. +function generateElement( + defaultValue: string, + inspectionResult: InspectionResult +): PropDefaultValue { + const { inferedType } = inspectionResult; + const { identifier } = inferedType as InspectionElement; + + if (!isNil(identifier)) { + if (!isHtmlTag(identifier)) { + const prettyIdentifier = getPrettyIdentifier( + inferedType as InspectionIdentifiableInferedType + ); + + return createSummaryValue( + prettyIdentifier, + prettyIdentifier !== defaultValue ? defaultValue : undefined + ); + } + } + + return !isTooLongForDefaultValueSummary(defaultValue) + ? createSummaryValue(defaultValue) + : createSummaryValue(ELEMENT_CAPTION, defaultValue); +} + +export function createDefaultValue(defaultValue: string): PropDefaultValue { + try { + const inspectionResult = inspectValue(defaultValue); + + switch (inspectionResult.inferedType.type) { + case InspectionType.OBJECT: + return generateObject(inspectionResult); + case InspectionType.FUNCTION: + return generateFunc(inspectionResult); + case InspectionType.ELEMENT: + return generateElement(defaultValue, inspectionResult); + case InspectionType.ARRAY: + return generateArray(inspectionResult); + default: + return null; + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + + return null; +} diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts new file mode 100644 index 000000000000..9adcce0091f9 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts @@ -0,0 +1,189 @@ +import { PropDefaultValue, PropDef } from '@storybook/components'; +import { isNil, isPlainObject, isArray, isFunction, isString } from 'lodash'; +// @ts-ignore +import reactElementToJSXString from 'react-element-to-jsx-string'; +import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib'; +import { inspectValue, InspectionFunction } from '../inspection'; +import { generateObject } from './generateObject'; +import { generateArray } from './generateArray'; +import { getPrettyElementIdentifier, getPrettyFuncIdentifier } from './prettyIdentifier'; +import { OBJECT_CAPTION, FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions'; +import { isHtmlTag } from '../isHtmlTag'; + +export type TypeResolver = (rawDefaultProp: any, propDef: PropDef) => PropDefaultValue; + +export interface TypeResolvers { + string: TypeResolver; + object: TypeResolver; + function: TypeResolver; + default: TypeResolver; +} + +function isReactElement(element: any): boolean { + return !isNil(element.$$typeof); +} + +export function extractFunctionName(func: Function, propName: string): string { + const { name } = func; + + // Comparison with the prop name is to discard inferred function names. + if (name !== '' && name !== 'anoynymous' && name !== propName) { + return name; + } + + return null; +} + +const stringResolver: TypeResolver = rawDefaultProp => { + return createSummaryValue(rawDefaultProp); +}; + +function generateReactObject(rawDefaultProp: any) { + const { type } = rawDefaultProp; + const { displayName } = type; + + const jsx = reactElementToJSXString(rawDefaultProp); + + if (!isNil(displayName)) { + const prettyIdentifier = getPrettyElementIdentifier(displayName); + + return createSummaryValue(prettyIdentifier, prettyIdentifier !== jsx ? jsx : undefined); + } + + if (isString(type)) { + // This is an HTML element. + if (isHtmlTag(type)) { + const jsxCompact = reactElementToJSXString(rawDefaultProp, { tabStop: 0 }); + const jsxSummary = jsxCompact.replace(/\r?\n|\r/g, ''); + + if (!isTooLongForDefaultValueSummary(jsxSummary)) { + return createSummaryValue(jsxSummary); + } + } + } + + return createSummaryValue(ELEMENT_CAPTION, jsx); +} + +const objectResolver: TypeResolver = rawDefaultProp => { + if (isReactElement(rawDefaultProp) && !isNil(rawDefaultProp.type)) { + return generateReactObject(rawDefaultProp); + } + + if (isPlainObject(rawDefaultProp)) { + const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp)); + + return generateObject(inspectionResult); + } + + if (isArray(rawDefaultProp)) { + const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp)); + + return generateArray(inspectionResult); + } + + return createSummaryValue(OBJECT_CAPTION); +}; + +const functionResolver: TypeResolver = (rawDefaultProp, propDef) => { + let isElement = false; + let inspectionResult; + + // 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)) { + isElement = true; + } else { + let innerElement; + + try { + inspectionResult = inspectValue(rawDefaultProp.toString()); + + const { hasParams, params } = inspectionResult.inferedType as InspectionFunction; + if (hasParams) { + // It might be a functional component accepting props. + if (params.length === 1 && params[0].type === 'ObjectPattern') { + innerElement = rawDefaultProp({}); + } + } else { + innerElement = rawDefaultProp(); + } + + if (!isNil(innerElement)) { + if (isReactElement(innerElement)) { + isElement = true; + } + } + } catch (e) { + // do nothing. + } + } + + const funcName = extractFunctionName(rawDefaultProp, propDef.name); + if (!isNil(funcName)) { + if (isElement) { + return createSummaryValue(getPrettyElementIdentifier(funcName)); + } + + if (!isNil(inspectionResult)) { + inspectionResult = inspectValue(rawDefaultProp.toString()); + } + + const { hasParams } = inspectionResult.inferedType as InspectionFunction; + + return createSummaryValue(getPrettyFuncIdentifier(funcName, hasParams)); + } + + return createSummaryValue(isElement ? ELEMENT_CAPTION : FUNCTION_CAPTION); +}; + +const defaultResolver: TypeResolver = rawDefaultProp => { + return createSummaryValue(rawDefaultProp.toString()); +}; + +const DEFAULT_TYPE_RESOLVERS: TypeResolvers = { + string: stringResolver, + object: objectResolver, + function: functionResolver, + default: defaultResolver, +}; + +export function createTypeResolvers(customResolvers: Partial<TypeResolvers> = {}): TypeResolvers { + return { + ...DEFAULT_TYPE_RESOLVERS, + ...customResolvers, + }; +} + +// When react-docgen cannot provide a defaultValue we take it from the raw defaultProp. +// It works fine for types that are not transpiled. For the types that are transpiled, we can only provide partial support. +// This means that: +// - The detail might not be available. +// - Identifiers might not be "prettified" for all the types. +export function createDefaultValueFromRawDefaultProp( + rawDefaultProp: any, + propDef: PropDef, + typeResolvers: TypeResolvers = DEFAULT_TYPE_RESOLVERS +): PropDefaultValue { + try { + // Keep the extra () otherwise it will fail for functions. + // eslint-disable-next-line prettier/prettier + switch (typeof (rawDefaultProp)) { + case 'string': + return typeResolvers.string(rawDefaultProp, propDef); + case 'object': + return typeResolvers.object(rawDefaultProp, propDef); + case 'function': { + return typeResolvers.function(rawDefaultProp, propDef); + } + default: + return typeResolvers.default(rawDefaultProp, propDef); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + + return null; +} diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/generateArray.ts b/addons/docs/src/frameworks/react/lib/defaultValues/generateArray.ts new file mode 100644 index 000000000000..802370479535 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/generateArray.ts @@ -0,0 +1,19 @@ +import { PropDefaultValue } from '@storybook/components'; +import { ARRAY_CAPTION } from '../captions'; +import { InspectionResult, InspectionArray } from '../inspection'; +import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib'; +import { generateArrayCode } from '../generateCode'; + +export function generateArray({ inferedType, ast }: InspectionResult): PropDefaultValue { + const { depth } = inferedType as InspectionArray; + + if (depth <= 2) { + const compactArray = generateArrayCode(ast, true); + + if (!isTooLongForDefaultValueSummary(compactArray)) { + return createSummaryValue(compactArray); + } + } + + return createSummaryValue(ARRAY_CAPTION, generateArrayCode(ast)); +} diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/generateObject.ts b/addons/docs/src/frameworks/react/lib/defaultValues/generateObject.ts new file mode 100644 index 000000000000..1c9505c1efcc --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/generateObject.ts @@ -0,0 +1,19 @@ +import { PropDefaultValue } from '@storybook/components'; +import { OBJECT_CAPTION } from '../captions'; +import { InspectionResult, InspectionArray } from '../inspection'; +import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib'; +import { generateObjectCode } from '../generateCode'; + +export function generateObject({ inferedType, ast }: InspectionResult): PropDefaultValue { + const { depth } = inferedType as InspectionArray; + + if (depth === 1) { + const compactObject = generateObjectCode(ast, true); + + if (!isTooLongForDefaultValueSummary(compactObject)) { + return createSummaryValue(compactObject); + } + } + + return createSummaryValue(OBJECT_CAPTION, generateObjectCode(ast)); +} diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/index.ts b/addons/docs/src/frameworks/react/lib/defaultValues/index.ts new file mode 100644 index 000000000000..0bf4b028eb14 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/index.ts @@ -0,0 +1,2 @@ +export * from './createDefaultValue'; +export * from './createFromRawDefaultProp'; diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/prettyIdentifier.ts b/addons/docs/src/frameworks/react/lib/defaultValues/prettyIdentifier.ts new file mode 100644 index 000000000000..d912ac8fb17a --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/defaultValues/prettyIdentifier.ts @@ -0,0 +1,26 @@ +import { + InspectionIdentifiableInferedType, + InspectionFunction, + InspectionType, +} from '../inspection'; + +export function getPrettyIdentifier(inferedType: InspectionIdentifiableInferedType): string { + const { type, identifier } = inferedType; + + switch (type) { + case InspectionType.FUNCTION: + return getPrettyFuncIdentifier(identifier, (inferedType as InspectionFunction).hasParams); + case InspectionType.ELEMENT: + return getPrettyElementIdentifier(identifier); + default: + return identifier; + } +} + +export function getPrettyFuncIdentifier(identifier: string, hasArguments: boolean): string { + return hasArguments ? `${identifier}( ... )` : `${identifier}()`; +} + +export function getPrettyElementIdentifier(identifier: string) { + return `<${identifier} />`; +} diff --git a/addons/docs/src/frameworks/react/lib/generateCode.ts b/addons/docs/src/frameworks/react/lib/generateCode.ts new file mode 100644 index 000000000000..d1a2f67a2863 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/generateCode.ts @@ -0,0 +1,70 @@ +import { generate } from 'escodegen'; +import dedent from 'ts-dedent'; + +const BASIC_OPTIONS = { + format: { + indent: { + style: ' ', + }, + semicolons: false, + }, +}; + +const COMPACT_OPTIONS = { + ...BASIC_OPTIONS, + format: { + newline: '', + }, +}; + +const PRETTY_OPTIONS = { + ...BASIC_OPTIONS, +}; + +export function generateCode(ast: any, compact = false): string { + return generate(ast, compact ? COMPACT_OPTIONS : PRETTY_OPTIONS); +} + +export function generateObjectCode(ast: any, compact = false): string { + return !compact ? generateCode(ast) : generateCompactObjectCode(ast); +} + +function generateCompactObjectCode(ast: any): string { + let result = generateCode(ast, true); + + // Cannot get escodegen to add a space before the last } with the compact mode settings. + // Fix it until a better solution is found. + if (!result.endsWith(' }')) { + result = `${result.slice(0, -1)} }`; + } + + return result; +} + +export function generateArrayCode(ast: any, compact = false): string { + return !compact ? generateMultilineArrayCode(ast) : generateCompactArrayCode(ast); +} + +function generateMultilineArrayCode(ast: any): string { + let result = generateCode(ast); + + // escodegen add extra spacing before the closing bracket of a multile line array with a nested object. + // Fix it until a better solution is found. + if (result.endsWith(' }]')) { + result = dedent(result); + } + + return result; +} + +function generateCompactArrayCode(ast: any): string { + let result = generateCode(ast, true); + + // escodegen add extra an extra before the opening bracket of a compact array that contains primitive values. + // Fix it until a better solution is found. + if (result.startsWith('[ ')) { + result = result.replace('[ ', '['); + } + + return result; +} diff --git a/addons/docs/src/frameworks/react/lib/index.ts b/addons/docs/src/frameworks/react/lib/index.ts new file mode 100644 index 000000000000..2059b83f1920 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/index.ts @@ -0,0 +1,3 @@ +export * from './captions'; +export * from './isHtmlTag'; +export * from './generateCode'; diff --git a/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts new file mode 100644 index 000000000000..8ffe6fad77a4 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts @@ -0,0 +1,254 @@ +import { parse } from './acornParser'; +import { + InspectionType, + InspectionElement, + InspectionObject, + InspectionArray, + InspectionIdentifier, + InspectionLiteral, + InspectionFunction, + InspectionUnknown, +} from './types'; + +describe('parse', () => { + describe('expression', () => { + it('support HTML element', () => { + const result = parse('<div>Hello!</div>'); + const inferedType = result.inferedType as InspectionElement; + + expect(inferedType.type).toBe(InspectionType.ELEMENT); + expect(inferedType.identifier).toBe('div'); + expect(result.ast).toBeDefined(); + }); + + it('support React declaration', () => { + const result = parse('<FunctionalComponent />'); + const inferedType = result.inferedType as InspectionElement; + + expect(inferedType.type).toBe(InspectionType.ELEMENT); + expect(inferedType.identifier).toBe('FunctionalComponent'); + expect(result.ast).toBeDefined(); + }); + + it('support anonymous functional React component', () => { + const result = parse('() => { return <div>Hey!</div>; }'); + const inferedType = result.inferedType as InspectionElement; + + expect(inferedType.type).toBe(InspectionType.ELEMENT); + expect(inferedType.identifier).toBeUndefined(); + expect(result.ast).toBeDefined(); + }); + + it('support named functional React component', () => { + const result = parse('function NamedFunctionalComponent() { return <div>Hey!</div>; }'); + const inferedType = result.inferedType as InspectionElement; + + expect(inferedType.type).toBe(InspectionType.ELEMENT); + expect(inferedType.identifier).toBe('NamedFunctionalComponent'); + expect(result.ast).toBeDefined(); + }); + + it('support class React component', () => { + const result = parse(` + class ClassComponent extends React.PureComponent { + render() { + return <div>Hey!</div>; + } + }`); + const inferedType = result.inferedType as InspectionElement; + + expect(inferedType.type).toBe(InspectionType.ELEMENT); + expect(inferedType.identifier).toBe('ClassComponent'); + expect(result.ast).toBeDefined(); + }); + + it('support PropTypes.shape', () => { + const result = parse('PropTypes.shape({ foo: PropTypes.string })'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support deep PropTypes.shape', () => { + const result = parse('PropTypes.shape({ foo: PropTypes.shape({ bar: PropTypes.string }) })'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(2); + expect(result.ast).toBeDefined(); + }); + + it('support shape', () => { + const result = parse('shape({ foo: string })'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support deep shape', () => { + const result = parse('shape({ foo: shape({ bar: string }) })'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(2); + expect(result.ast).toBeDefined(); + }); + + it('support single prop object literal', () => { + const result = parse('{ foo: PropTypes.string }'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support multi prop object literal', () => { + const result = parse(` + { + foo: PropTypes.string, + bar: PropTypes.string + }`); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support deep object literal', () => { + const result = parse(` + { + foo: { + hey: PropTypes.string + }, + bar: PropTypes.string, + hey: { + ho: PropTypes.string + } + }`); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(2); + expect(result.ast).toBeDefined(); + }); + + it('support required prop', () => { + const result = parse('{ foo: PropTypes.string.isRequired }'); + const inferedType = result.inferedType as InspectionObject; + + expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support array', () => { + const result = parse("['bottom-left', 'botton-center', 'bottom-right']"); + const inferedType = result.inferedType as InspectionArray; + + expect(inferedType.type).toBe(InspectionType.ARRAY); + expect(inferedType.depth).toBe(1); + expect(result.ast).toBeDefined(); + }); + + it('support deep array', () => { + const result = parse("['bottom-left', { foo: string }, [['hey', 'ho']]]"); + const inferedType = result.inferedType as InspectionArray; + + expect(inferedType.type).toBe(InspectionType.ARRAY); + expect(inferedType.depth).toBe(3); + expect(result.ast).toBeDefined(); + }); + + it('support object identifier', () => { + const result = parse('NAMED_OBJECT'); + const inferedType = result.inferedType as InspectionIdentifier; + + expect(inferedType.type).toBe(InspectionType.IDENTIFIER); + expect(inferedType.identifier).toBe('NAMED_OBJECT'); + expect(result.ast).toBeDefined(); + }); + + it('support anonymous function', () => { + const result = parse('() => {}'); + const inferedType = result.inferedType as InspectionFunction; + + expect(inferedType.type).toBe(InspectionType.FUNCTION); + expect(inferedType.identifier).toBeUndefined(); + expect(inferedType.hasParams).toBeFalsy(); + expect(inferedType.params.length).toBe(0); + expect(result.ast).toBeDefined(); + }); + + it('support anonymous function with arguments', () => { + const result = parse('(a, b) => {}'); + const inferedType = result.inferedType as InspectionFunction; + + expect(inferedType.type).toBe(InspectionType.FUNCTION); + expect(inferedType.identifier).toBeUndefined(); + expect(inferedType.hasParams).toBeTruthy(); + expect(inferedType.params.length).toBe(2); + expect(result.ast).toBeDefined(); + }); + + it('support named function', () => { + const result = parse('function concat() {}'); + const inferedType = result.inferedType as InspectionFunction; + + expect(inferedType.type).toBe(InspectionType.FUNCTION); + expect(inferedType.identifier).toBe('concat'); + expect(inferedType.hasParams).toBeFalsy(); + expect(inferedType.params.length).toBe(0); + expect(result.ast).toBeDefined(); + }); + + it('support named function with arguments', () => { + const result = parse('function concat(a, b) {}'); + const inferedType = result.inferedType as InspectionFunction; + + expect(inferedType.type).toBe(InspectionType.FUNCTION); + expect(inferedType.identifier).toBe('concat'); + expect(inferedType.hasParams).toBeTruthy(); + expect(inferedType.params.length).toBe(2); + expect(result.ast).toBeDefined(); + }); + + it('support class', () => { + const result = parse('class Foo {}'); + const inferedType = result.inferedType as InspectionFunction; + + expect(inferedType.type).toBe(InspectionType.CLASS); + expect(inferedType.identifier).toBe('Foo'); + expect(result.ast).toBeDefined(); + }); + + [ + { name: 'string', value: "'string value'" }, + { name: 'numeric', value: '1' }, + { name: 'boolean (true)', value: 'true' }, + { name: 'boolean (false)', value: 'false' }, + { name: 'null', value: 'null' }, + ].forEach(x => { + it(`support ${x.name}`, () => { + const result = parse(x.value); + const inferedType = result.inferedType as InspectionLiteral; + + expect(inferedType.type).toBe(InspectionType.LITERAL); + expect(result.ast).toBeDefined(); + }); + }); + + it("returns Unknown when it's not supported", () => { + const result = parse("Symbol('foo')"); + const inferedType = result.inferedType as InspectionUnknown; + + expect(inferedType.type).toBe(InspectionType.UNKNOWN); + expect(result.ast).toBeDefined(); + }); + }); +}); diff --git a/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts new file mode 100644 index 000000000000..60b552abc2fc --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts @@ -0,0 +1,232 @@ +import { Parser } from 'acorn'; +// @ts-ignore +import jsx from 'acorn-jsx'; +import { isNil } from 'lodash'; +import estree from 'estree'; +// @ts-ignore +import * as acornWalk from 'acorn-walk'; +import { + InspectionType, + InspectionLiteral, + InspectionElement, + InspectionFunction, + InspectionClass, + InspectionObject, + InspectionUnknown, + InspectionIdentifier, + InspectionArray, + InspectionInferedType, +} from './types'; + +interface ParsingResult<T> { + inferedType: T; + ast: any; +} + +const ACORN_WALK_VISITORS = { + ...acornWalk.base, + JSXElement: () => {}, +}; + +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; +} + +function filterAncestors(ancestors: estree.Node[]): estree.Node[] { + return ancestors.filter(x => x.type === 'ObjectExpression' || x.type === 'ArrayExpression'); +} + +function calculateNodeDepth(node: estree.Expression): number { + const depths: number[] = []; + + acornWalk.ancestor( + node, + { + ObjectExpression(_: any, ancestors: estree.Node[]) { + depths.push(filterAncestors(ancestors).length); + }, + ArrayExpression(_: any, ancestors: estree.Node[]) { + depths.push(filterAncestors(ancestors).length); + }, + }, + ACORN_WALK_VISITORS + ); + + return Math.max(...depths); +} + +function parseIdentifier(identifierNode: estree.Identifier): ParsingResult<InspectionIdentifier> { + return { + inferedType: { + type: InspectionType.IDENTIFIER, + identifier: extractIdentifierName(identifierNode), + }, + ast: identifierNode, + }; +} + +function parseLiteral(literalNode: estree.Literal): ParsingResult<InspectionLiteral> { + return { + inferedType: { type: InspectionType.LITERAL }, + ast: literalNode, + }; +} + +function parseFunction( + funcNode: estree.FunctionExpression | estree.ArrowFunctionExpression +): ParsingResult<InspectionFunction | InspectionElement> { + let innerJsxElementNode; + + // If there is at least a JSXElement in the body of the function, then it's a React component. + acornWalk.simple( + funcNode.body, + { + JSXElement(node: any) { + innerJsxElementNode = node; + }, + }, + ACORN_WALK_VISITORS + ); + + const isJsx = !isNil(innerJsxElementNode); + + const inferedType: InspectionFunction | InspectionElement = { + type: isJsx ? InspectionType.ELEMENT : InspectionType.FUNCTION, + params: funcNode.params, + hasParams: funcNode.params.length !== 0, + }; + + const identifierName = extractIdentifierName((funcNode as estree.FunctionExpression).id); + if (!isNil(identifierName)) { + inferedType.identifier = identifierName; + } + + return { + inferedType, + ast: funcNode, + }; +} + +function parseClass( + classNode: estree.ClassExpression +): ParsingResult<InspectionClass | InspectionElement> { + let innerJsxElementNode; + + // If there is at least a JSXElement in the body of the class, then it's a React component. + acornWalk.simple( + classNode.body, + { + JSXElement(node: any) { + innerJsxElementNode = node; + }, + }, + ACORN_WALK_VISITORS + ); + + const inferedType: any = { + type: !isNil(innerJsxElementNode) ? InspectionType.ELEMENT : InspectionType.CLASS, + identifier: extractIdentifierName(classNode.id), + }; + + return { + inferedType, + ast: classNode, + }; +} + +function parseJsxElement(jsxElementNode: any): ParsingResult<InspectionElement> { + const inferedType: InspectionElement = { + type: InspectionType.ELEMENT, + }; + + const identifierName = extractIdentifierName(jsxElementNode.openingElement.name); + if (!isNil(identifierName)) { + inferedType.identifier = identifierName; + } + + return { + inferedType, + ast: jsxElementNode, + }; +} + +function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObject> { + const identifierNode = + callNode.callee.type === 'MemberExpression' ? callNode.callee.property : callNode.callee; + + const identifierName = extractIdentifierName(identifierNode); + if (identifierName === 'shape') { + return parseObject(callNode.arguments[0] as estree.ObjectExpression); + } + + return null; +} + +function parseObject(objectNode: estree.ObjectExpression): ParsingResult<InspectionObject> { + return { + inferedType: { type: InspectionType.OBJECT, depth: calculateNodeDepth(objectNode) }, + ast: objectNode, + }; +} + +function parseArray(arrayNode: estree.ArrayExpression): ParsingResult<InspectionArray> { + return { + inferedType: { type: InspectionType.ARRAY, depth: calculateNodeDepth(arrayNode) }, + ast: arrayNode, + }; +} + +// Cannot set "expression" type to "estree.Expression" because the type doesn't include JSX. +function parseExpression(expression: any): ParsingResult<InspectionInferedType> { + switch (expression.type) { + case 'Identifier': + return parseIdentifier(expression); + case 'Literal': + return parseLiteral(expression); + case 'FunctionExpression': + case 'ArrowFunctionExpression': + return parseFunction(expression); + case 'ClassExpression': + return parseClass(expression); + case 'JSXElement': + return parseJsxElement(expression); + case 'CallExpression': + return parseCall(expression); + case 'ObjectExpression': + return parseObject(expression); + case 'ArrayExpression': + return parseArray(expression); + default: + return null; + } +} + +export function parse(value: string): ParsingResult<InspectionInferedType> { + const ast = (acornParser.parse(`(${value})`) as unknown) as estree.Program; + + let parsingResult: ParsingResult<InspectionUnknown> = { + inferedType: { type: InspectionType.UNKNOWN }, + ast, + }; + + if (!isNil(ast.body[0])) { + const rootNode = ast.body[0]; + + switch (rootNode.type) { + case 'ExpressionStatement': { + const expressionResult = parseExpression(rootNode.expression); + if (!isNil(expressionResult)) { + parsingResult = expressionResult as any; + } + break; + } + default: + break; + } + } + + return parsingResult; +} diff --git a/addons/docs/src/frameworks/react/lib/inspection/index.ts b/addons/docs/src/frameworks/react/lib/inspection/index.ts new file mode 100644 index 000000000000..c9590fdec3af --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/inspection/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './inspectValue'; diff --git a/addons/docs/src/frameworks/react/lib/inspection/inspectValue.ts b/addons/docs/src/frameworks/react/lib/inspection/inspectValue.ts new file mode 100644 index 000000000000..b61044f5a8f4 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/inspection/inspectValue.ts @@ -0,0 +1,14 @@ +import { parse } from './acornParser'; +import { InspectionResult, InspectionType } from './types'; + +export function inspectValue(value: string): InspectionResult { + try { + const parsingResult = parse(value); + + return { ...parsingResult }; + } catch (e) { + // do nothing. + } + + return { inferedType: { type: InspectionType.UNKNOWN } }; +} diff --git a/addons/docs/src/frameworks/react/lib/inspection/types.ts b/addons/docs/src/frameworks/react/lib/inspection/types.ts new file mode 100644 index 000000000000..ffa1b43c48f5 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/inspection/types.ts @@ -0,0 +1,65 @@ +export enum InspectionType { + IDENTIFIER = 'Identifier', + LITERAL = 'Literal', + OBJECT = 'Object', + ARRAY = 'Array', + FUNCTION = 'Function', + CLASS = 'Class', + ELEMENT = 'Element', + UNKNOWN = 'Unknown', +} + +export interface InspectionInferedType { + type: InspectionType; +} + +export interface InspectionIdentifier extends InspectionInferedType { + type: InspectionType.IDENTIFIER; + identifier: string; +} + +export interface InspectionLiteral extends InspectionInferedType { + type: InspectionType.LITERAL; +} + +export interface InspectionObject extends InspectionInferedType { + type: InspectionType.OBJECT; + depth: number; +} + +export interface InspectionArray extends InspectionInferedType { + type: InspectionType.ARRAY; + depth: number; +} + +export interface InspectionClass extends InspectionInferedType { + type: InspectionType.CLASS; + identifier: string; +} + +export interface InspectionFunction extends InspectionInferedType { + type: InspectionType.FUNCTION; + identifier?: string; + params: any[]; + hasParams: boolean; +} + +export interface InspectionElement extends InspectionInferedType { + type: InspectionType.ELEMENT; + identifier?: string; +} + +export interface InspectionUnknown extends InspectionInferedType { + type: InspectionType.UNKNOWN; +} + +export type InspectionIdentifiableInferedType = + | InspectionIdentifier + | InspectionClass + | InspectionFunction + | InspectionElement; + +export interface InspectionResult { + inferedType: InspectionInferedType; + ast?: any; +} diff --git a/addons/docs/src/frameworks/react/lib/isHtmlTag.ts b/addons/docs/src/frameworks/react/lib/isHtmlTag.ts new file mode 100644 index 000000000000..eb13de490d02 --- /dev/null +++ b/addons/docs/src/frameworks/react/lib/isHtmlTag.ts @@ -0,0 +1,5 @@ +import htmlTags from 'html-tags'; + +export function isHtmlTag(tagName: string): boolean { + return htmlTags.includes(tagName.toLowerCase()); +} diff --git a/addons/docs/src/frameworks/react/propTypes/createType.ts b/addons/docs/src/frameworks/react/propTypes/createType.ts new file mode 100644 index 000000000000..84a9e55f5620 --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/createType.ts @@ -0,0 +1,410 @@ +import { isNil } from 'lodash'; +import { PropType } from '@storybook/components'; +import { createSummaryValue, isTooLongForTypeSummary } from '../../../lib'; +import { ExtractedProp, DocgenPropType } from '../../../lib/docgen'; +import { + generateFuncSignature, + generateShortFuncSignature, + toMultilineSignature, +} from './generateFuncSignature'; +import { + OBJECT_CAPTION, + ARRAY_CAPTION, + CLASS_CAPTION, + FUNCTION_CAPTION, + ELEMENT_CAPTION, + CUSTOM_CAPTION, + isHtmlTag, + generateObjectCode, + generateCode, +} from '../lib'; +import { + InspectionType, + inspectValue, + InspectionElement, + InspectionObject, + InspectionArray, +} from '../lib/inspection'; + +const MAX_FUNC_LENGTH = 150; + +enum PropTypesType { + CUSTOM = 'custom', + ANY = 'any', + FUNC = 'func', + SHAPE = 'shape', + OBJECT = 'object', + INSTANCEOF = 'instanceOf', + OBJECTOF = 'objectOf', + UNION = 'union', + ENUM = 'enum', + ARRAYOF = 'arrayOf', + ELEMENT = 'element', + ELEMENTTYPE = 'elementType', + NODE = 'node', +} + +interface EnumValue { + value: string; + computed: boolean; +} + +interface TypeDef { + name: string; + short: string; + compact: string; + full: string; + inferedType?: InspectionType; +} + +function createTypeDef({ + name, + short, + compact, + full, + inferedType, +}: { + name: string; + short: string; + compact: string; + full?: string; + inferedType?: InspectionType; +}): TypeDef { + return { + name, + short, + compact, + full: !isNil(full) ? full : short, + inferedType, + }; +} + +function cleanPropTypes(value: string): string { + return value.replace(/PropTypes./g, '').replace(/.isRequired/g, ''); +} + +function splitIntoLines(value: string): string[] { + return value.split(/\r?\n/); +} + +function prettyObject(ast: any, compact = false): string { + return cleanPropTypes(generateObjectCode(ast, compact)); +} + +function prettyArray(ast: any, compact = false): string { + return cleanPropTypes(generateCode(ast, compact)); +} + +function getCaptionForInspectionType(type: InspectionType): string { + switch (type) { + case InspectionType.OBJECT: + return OBJECT_CAPTION; + case InspectionType.ARRAY: + return ARRAY_CAPTION; + case InspectionType.CLASS: + return CLASS_CAPTION; + case InspectionType.FUNCTION: + return FUNCTION_CAPTION; + case InspectionType.ELEMENT: + return ELEMENT_CAPTION; + default: + return CUSTOM_CAPTION; + } +} + +function generateTypeFromString(value: string, originalTypeName: string): TypeDef { + const { inferedType, ast } = inspectValue(value); + const { type } = inferedType; + + let short; + let compact; + let full; + + switch (type) { + case InspectionType.IDENTIFIER: + case InspectionType.LITERAL: + short = value; + compact = value; + break; + case InspectionType.OBJECT: { + const { depth } = inferedType as InspectionObject; + + short = OBJECT_CAPTION; + compact = depth === 1 ? prettyObject(ast, true) : null; + full = prettyObject(ast); + break; + } + case InspectionType.ELEMENT: { + const { identifier } = inferedType as InspectionElement; + + short = !isNil(identifier) && !isHtmlTag(identifier) ? identifier : ELEMENT_CAPTION; + compact = splitIntoLines(value).length === 1 ? value : null; + full = value; + break; + } + case InspectionType.ARRAY: { + const { depth } = inferedType as InspectionArray; + + short = ARRAY_CAPTION; + compact = depth <= 2 ? prettyArray(ast, true) : null; + full = prettyArray(ast); + break; + } + default: + short = getCaptionForInspectionType(type); + compact = splitIntoLines(value).length === 1 ? value : null; + full = value; + break; + } + + return createTypeDef({ + name: originalTypeName, + short, + compact, + full, + inferedType: type, + }); +} + +function generateCustom({ raw }: DocgenPropType): TypeDef { + if (!isNil(raw)) { + return generateTypeFromString(raw, PropTypesType.CUSTOM); + } + + return createTypeDef({ + name: PropTypesType.CUSTOM, + short: CUSTOM_CAPTION, + compact: CUSTOM_CAPTION, + }); +} + +function generateFunc(extractedProp: ExtractedProp): TypeDef { + const { jsDocTags } = extractedProp; + + if (!isNil(jsDocTags)) { + if (!isNil(jsDocTags.params) || !isNil(jsDocTags.returns)) { + return createTypeDef({ + name: PropTypesType.FUNC, + short: generateShortFuncSignature(jsDocTags.params, jsDocTags.returns), + compact: null, + full: generateFuncSignature(jsDocTags.params, jsDocTags.returns), + }); + } + } + + return createTypeDef({ + name: PropTypesType.FUNC, + short: FUNCTION_CAPTION, + compact: FUNCTION_CAPTION, + }); +} + +function generateShape(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + const fields = Object.keys(type.value) + .map((key: string) => `${key}: ${generateType(type.value[key], extractedProp).full}`) + .join(', '); + + const { inferedType, ast } = inspectValue(`{ ${fields} }`); + const { depth } = inferedType as InspectionObject; + + return createTypeDef({ + name: PropTypesType.SHAPE, + short: OBJECT_CAPTION, + compact: depth === 1 ? prettyObject(ast, true) : null, + full: prettyObject(ast), + }); +} + +function objectOf(of: string): string { + return `objectOf(${of})`; +} + +function generateObjectOf(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + const { short, compact, full } = generateType(type.value, extractedProp); + + return createTypeDef({ + name: PropTypesType.OBJECTOF, + short: objectOf(short), + compact: !isNil(compact) ? objectOf(compact) : null, + full: objectOf(full), + }); +} + +function generateUnion(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + if (Array.isArray(type.value)) { + const values = type.value.reduce( + (acc: any, v: any) => { + const { short, compact, full } = generateType(v, extractedProp); + + acc.short.push(short); + acc.compact.push(compact); + acc.full.push(full); + + return acc; + }, + { short: [], compact: [], full: [] } + ); + + return createTypeDef({ + name: PropTypesType.UNION, + short: values.short.join(' | '), + compact: values.compact.every((x: string) => !isNil(x)) ? values.compact.join(' | ') : null, + full: values.full.join(' | '), + }); + } + + return createTypeDef({ name: PropTypesType.UNION, short: type.value, compact: null }); +} + +function generateEnumValue({ value, computed }: EnumValue): TypeDef { + return computed + ? generateTypeFromString(value, 'enumvalue') + : createTypeDef({ name: 'enumvalue', short: value, compact: value }); +} + +function generateEnum(type: DocgenPropType): TypeDef { + if (Array.isArray(type.value)) { + const values = type.value.reduce( + (acc: any, v: EnumValue) => { + const { short, compact, full } = generateEnumValue(v); + + acc.short.push(short); + acc.compact.push(compact); + acc.full.push(full); + + return acc; + }, + { short: [], compact: [], full: [] } + ); + + return createTypeDef({ + name: PropTypesType.ENUM, + short: values.short.join(' | '), + compact: values.compact.every((x: string) => !isNil(x)) ? values.compact.join(' | ') : null, + full: values.full.join(' | '), + }); + } + + return createTypeDef({ name: PropTypesType.ENUM, short: type.value, compact: type.value }); +} + +function braceAfter(of: string): string { + return `${of}[]`; +} + +function braceAround(of: string): string { + return `[${of}]`; +} + +function createArrayOfObjectTypeDef(short: string, compact: string, full: string): TypeDef { + return createTypeDef({ + name: PropTypesType.ARRAYOF, + short: braceAfter(short), + compact: !isNil(compact) ? braceAround(compact) : null, + full: braceAround(full), + }); +} + +function generateArray(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + const { name, short, compact, full, inferedType } = generateType(type.value, extractedProp); + + if (name === PropTypesType.CUSTOM) { + if (inferedType === InspectionType.OBJECT) { + return createArrayOfObjectTypeDef(short, compact, full); + } + } else if (name === PropTypesType.SHAPE) { + return createArrayOfObjectTypeDef(short, compact, full); + } + + return createTypeDef({ + name: PropTypesType.ARRAYOF, + short: braceAfter(short), + compact: braceAfter(short), + }); +} + +function generateType(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + try { + switch (type.name) { + case PropTypesType.CUSTOM: + return generateCustom(type); + case PropTypesType.FUNC: + return generateFunc(extractedProp); + case PropTypesType.SHAPE: + return generateShape(type, extractedProp); + case PropTypesType.INSTANCEOF: + return createTypeDef({ + name: PropTypesType.INSTANCEOF, + short: type.value, + compact: type.value, + }); + case PropTypesType.OBJECTOF: + return generateObjectOf(type, extractedProp); + case PropTypesType.UNION: + return generateUnion(type, extractedProp); + case PropTypesType.ENUM: + return generateEnum(type); + case PropTypesType.ARRAYOF: + return generateArray(type, extractedProp); + default: + return createTypeDef({ name: type.name, short: type.name, compact: type.name }); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + + return createTypeDef({ name: 'unknown', short: 'unknown', compact: 'unknown' }); +} + +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)) { + return null; + } + + try { + switch (type.name) { + case PropTypesType.CUSTOM: + case PropTypesType.SHAPE: + case PropTypesType.INSTANCEOF: + case PropTypesType.OBJECTOF: + case PropTypesType.UNION: + case PropTypesType.ENUM: + case PropTypesType.ARRAYOF: { + const { short, compact, full } = generateType(type, extractedProp); + + if (!isNil(compact)) { + if (!isTooLongForTypeSummary(compact)) { + return createSummaryValue(compact); + } + } + + return createSummaryValue(short, short !== full ? full : undefined); + } + case PropTypesType.FUNC: { + const { short, full } = generateType(type, extractedProp); + + let summary = short; + let detail; + + if (full.length < MAX_FUNC_LENGTH) { + summary = full; + } else { + detail = toMultilineSignature(full); + } + + return createSummaryValue(summary, detail); + } + default: + return null; + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + + return null; +} diff --git a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts new file mode 100644 index 000000000000..92f6575c9374 --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts @@ -0,0 +1,187 @@ +import { generateFuncSignature, generateShortFuncSignature } from './generateFuncSignature'; +import { parseJsDoc } from '../../../lib/jsdocParser'; + +describe('generateFuncSignature', () => { + it('should return an empty string when there is no @params and @returns tags', () => { + const result = generateFuncSignature(null, null); + + expect(result).toBe(''); + }); + + it('should return a signature with a single arg when there is a @param tag with a name', () => { + const { params, returns } = parseJsDoc('@param event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event)'); + }); + + it('should return a signature with a single arg when there is a @param tag with a name and a type', () => { + const { params, returns } = parseJsDoc('@param {SyntheticEvent} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: SyntheticEvent)'); + }); + + it('should return a signature with a single arg when there is a @param tag with a name, a type and a desc', () => { + const { params, returns } = parseJsDoc( + '@param {SyntheticEvent} event - React event' + ).extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: SyntheticEvent)'); + }); + + it('should support @param of record type', () => { + const { params, returns } = parseJsDoc('@param {{a: number}} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: ({a: number}))'); + }); + + it('should support @param of union type', () => { + const { params, returns } = parseJsDoc('@param {(number|boolean)} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: (number|boolean))'); + }); + + it('should support @param of array type', () => { + const { params, returns } = parseJsDoc('@param {number[]} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: number[])'); + }); + + it('should support @param with a nullable type', () => { + const { params, returns } = parseJsDoc('@param {?number} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: number)'); + }); + + it('should support @param with a non nullable type', () => { + const { params, returns } = parseJsDoc('@param {!number} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: number)'); + }); + + it('should support optional @param with []', () => { + const { params, returns } = parseJsDoc('@param {number} [event]').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: number)'); + }); + + it('should support optional @param with =', () => { + const { params, returns } = parseJsDoc('@param {number=} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: number)'); + }); + + it('should support @param of type any', () => { + const { params, returns } = parseJsDoc('@param {*} event').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: any)'); + }); + + it('should support multiple @param tags', () => { + const { params, returns } = parseJsDoc( + '@param {SyntheticEvent} event\n@param {string} customData' + ).extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: SyntheticEvent, customData: string)'); + }); + + it('should return a signature with a return type when there is a @returns with a type', () => { + const { params, returns } = parseJsDoc('@returns {string}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => string'); + }); + + it('should support @returns of record type', () => { + const { params, returns } = parseJsDoc('@returns {{a: number, b: string}}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => ({a: number, b: string})'); + }); + + it('should support @returns of array type', () => { + const { params, returns } = parseJsDoc('@returns {integer[]}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => integer[]'); + }); + + it('should support @returns of union type', () => { + const { params, returns } = parseJsDoc('@returns {(number|boolean)}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => (number|boolean)'); + }); + + it('should support @returns type any', () => { + const { params, returns } = parseJsDoc('@returns {*}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => any'); + }); + + it('should support @returns of type void', () => { + const { params, returns } = parseJsDoc('@returns {void}').extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('() => void'); + }); + + it('should return a full signature when there is a single @param tag and a @returns', () => { + const { params, returns } = parseJsDoc( + '@param {SyntheticEvent} event - React event.\n@returns {string}' + ).extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: SyntheticEvent) => string'); + }); + + it('should return a full signature when there is a multiple @param tags and a @returns', () => { + const { params, returns } = parseJsDoc( + '@param {SyntheticEvent} event - React event.\n@param {string} data\n@returns {string}' + ).extractedTags; + const result = generateFuncSignature(params, returns); + + expect(result).toBe('(event: SyntheticEvent, data: string) => string'); + }); +}); + +describe('generateShortFuncSignature', () => { + it('should return an empty string when there is no @params and @returns tags', () => { + const result = generateShortFuncSignature(null, null); + + expect(result).toBe(''); + }); + + it('should return ( ... ) when there is @params', () => { + const { params, returns } = parseJsDoc('@param event').extractedTags; + const result = generateShortFuncSignature(params, returns); + + expect(result).toBe('( ... )'); + }); + + it('should return ( ... ) => returnsType when there is @params and a @returns', () => { + const { params, returns } = parseJsDoc('@param event\n@returns {string}').extractedTags; + const result = generateShortFuncSignature(params, returns); + + expect(result).toBe('( ... ) => string'); + }); + + it('should return () => returnsType when there is only a @returns', () => { + const { params, returns } = parseJsDoc('@returns {string}').extractedTags; + const result = generateShortFuncSignature(params, returns); + + expect(result).toBe('() => string'); + }); +}); diff --git a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts new file mode 100644 index 000000000000..63624c418958 --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts @@ -0,0 +1,69 @@ +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); + + if (!hasParams && !hasReturns) { + return ''; + } + + const funcParts = []; + + if (hasParams) { + const funcParams = params.map((x: ExtractedJsDocParam) => { + const prettyName = x.getPrettyName(); + const typeName = x.getTypeName(); + + if (!isNil(typeName)) { + return `${prettyName}: ${typeName}`; + } + + return prettyName; + }); + + funcParts.push(`(${funcParams.join(', ')})`); + } else { + funcParts.push('()'); + } + + if (hasReturns) { + funcParts.push(`=> ${returns.getTypeName()}`); + } + + return funcParts.join(' '); +} + +export function generateShortFuncSignature( + params: ExtractedJsDocParam[], + returns: ExtractedJsDocReturns +): string { + const hasParams = !isNil(params); + const hasReturns = !isNil(returns); + + if (!hasParams && !hasReturns) { + return ''; + } + + const funcParts = []; + + if (hasParams) { + funcParts.push('( ... )'); + } else { + funcParts.push('()'); + } + + if (hasReturns) { + funcParts.push(`=> ${returns.getTypeName()}`); + } + + return funcParts.join(' '); +} + +export function toMultilineSignature(signature: string): string { + return signature.replace(/,/g, ',\r\n'); +} diff --git a/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx b/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx new file mode 100644 index 000000000000..71417d46effe --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/handleProp.test.tsx @@ -0,0 +1,1469 @@ +/* eslint-disable no-underscore-dangle */ + +import { PropDef } from '@storybook/components'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { Component } from '../../../blocks/shared'; +import { extractComponentProps, DocgenInfo, DocgenPropDefaultValue } from '../../../lib/docgen'; +import { enhancePropTypesProp, enhancePropTypesProps } from './handleProp'; + +const DOCGEN_SECTION = 'props'; + +function ReactComponent() { + return <div>React Component!</div>; +} + +function createDocgenSection(docgenInfo: DocgenInfo): Record<string, any> { + return { + [DOCGEN_SECTION]: { + ...docgenInfo, + }, + }; +} + +function createDocgenProp({ + name, + type, + ...others +}: Partial<DocgenInfo> & { name: string }): Record<string, any> { + return { + [name]: { + type, + required: false, + ...others, + }, + }; +} + +// eslint-disable-next-line react/forbid-foreign-prop-types +function createComponent({ propTypes = {}, defaultProps = {}, docgenInfo = {} }): Component { + const component = () => { + return <div>Hey!</div>; + }; + component.propTypes = propTypes; + component.defaultProps = defaultProps; + + // @ts-ignore + component.__docgenInfo = createDocgenSection(docgenInfo); + + return component; +} + +function createDefaultValue(defaultValue: string): DocgenPropDefaultValue { + return { value: defaultValue }; +} + +function extractPropDef(component: Component, rawDefaultProp?: any): PropDef { + return enhancePropTypesProp(extractComponentProps(component, DOCGEN_SECTION)[0], rawDefaultProp); +} + +describe('enhancePropTypesProp', () => { + describe('type', () => { + function createTestComponent(docgenInfo: Partial<DocgenInfo>): Component { + return createComponent({ + docgenInfo: { + ...createDocgenProp({ name: 'prop', ...docgenInfo }), + }, + }); + } + + describe('custom', () => { + describe('when raw value is available', () => { + it('should support literal', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: 'MY_LITERAL', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('MY_LITERAL'); + expect(type.detail).toBeUndefined(); + }); + + it('should support short object', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: '{\n text: PropTypes.string.isRequired,\n}', + }, + }); + + const { type } = extractPropDef(component); + + const expectedSummary = '{ text: string }'; + + expect(type.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, '')); + expect(type.detail).toBeUndefined(); + }); + + it('should support long object', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: + '{\n text: PropTypes.string.isRequired,\n value1: PropTypes.string.isRequired,\n value2: PropTypes.string.isRequired,\n value3: PropTypes.string.isRequired,\n value4: PropTypes.string.isRequired,\n}', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object'); + + const expectedDetail = `{ + text: string, + value1: string, + value2: string, + value3: string, + value4: string + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have a deep object as summary', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: '{\n foo: { bar: PropTypes.string.isRequired,\n }}', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object'); + }); + + it('should use identifier of a React element when available', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: + 'function InlinedFunctionalComponent() {\n return <div>Inlined FunctionnalComponent!</div>;\n}', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('InlinedFunctionalComponent'); + + const expectedDetail = `function InlinedFunctionalComponent() { + return <div>Inlined FunctionnalComponent!</div>; + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not use identifier of a HTML element', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: + '<div>Hello world from Montreal, Quebec, Canada!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('element'); + + const expectedDetail = + '<div>Hello world from Montreal, Quebec, Canada!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>'; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support element without identifier', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: '() => {\n return <div>Inlined FunctionalComponent!</div>;\n}', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('element'); + + const expectedDetail = `() => { + return <div>Inlined FunctionalComponent!</div>; + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + describe('when it is not a known type', () => { + it('should return "custom" when its a long type', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: + 'Symbol("A very very very very very very lonnnngggggggggggggggggggggggggggggggggggg symbol")', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('custom'); + expect(type.detail).toBe( + 'Symbol("A very very very very very very lonnnngggggggggggggggggggggggggggggggggggg symbol")' + ); + }); + + it('should return "custom" when its a short type', () => { + const component = createTestComponent({ + type: { + name: 'custom', + raw: 'Symbol("Hey!")', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('Symbol("Hey!")'); + expect(type.detail).toBeUndefined(); + }); + }); + }); + + it("should return 'custom' when there is no raw value", () => { + const component = createTestComponent({ + type: { + name: 'custom', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('custom'); + }); + }); + + [ + 'any', + 'bool', + 'string', + 'number', + 'symbol', + 'object', + 'element', + 'elementType', + 'node', + ].forEach(x => { + it(`should return '${x}' when type is ${x}`, () => { + const component = createTestComponent({ + type: { + name: x, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe(x); + }); + }); + + it('should support short shape', () => { + const component = createTestComponent({ + type: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + const expectedSummary = '{ foo: string }'; + + expect(type.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, '')); + expect(type.detail).toBeUndefined(); + }); + + it('should support long shape', () => { + const component = createTestComponent({ + type: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + bar: { + name: 'string', + required: false, + }, + another: { + name: 'string', + required: false, + }, + another2: { + name: 'string', + required: false, + }, + another3: { + name: 'string', + required: false, + }, + another4: { + name: 'string', + required: false, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object'); + + const expectedDetail = `{ + foo: string, + bar: string, + another: string, + another2: string, + another3: string, + another4: string + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have a deep shape as summary', () => { + const component = createTestComponent({ + type: { + name: 'shape', + value: { + bar: { + name: 'shape', + value: { + hey: { + name: 'string', + required: false, + }, + }, + required: false, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object'); + }); + + it('should support enum of string', () => { + const component = createTestComponent({ + type: { + name: 'enum', + value: [ + { + value: "'News'", + computed: false, + }, + { + value: "'Photos'", + computed: false, + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe("'News' | 'Photos'"); + }); + + it('should support enum of object', () => { + const component = createTestComponent({ + type: { + name: 'enum', + value: [ + { + value: + '{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}', + computed: true, + }, + { + value: + '{\n foo: PropTypes.string,\n bar: PropTypes.string,\n hey: PropTypes.string,\n ho: PropTypes.string,\n}', + computed: true, + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object | object'); + + const expectedDetail = `{ + text: string, + value: string + } | { + foo: string, + bar: string, + hey: string, + ho: string + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support short object in enum summary', () => { + const component = createTestComponent({ + type: { + name: 'enum', + value: [ + { + value: '{\n text: PropTypes.string.isRequired,\n}', + computed: true, + }, + { + value: '{\n foo: PropTypes.string,\n}', + computed: true, + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('{ text: string } | { foo: string }'); + }); + + it('should not have a deep object in an enum summary', () => { + const component = createTestComponent({ + type: { + name: 'enum', + value: [ + { + value: '{\n text: { foo: PropTypes.string.isRequired,\n }\n}', + computed: true, + }, + { + value: '{\n foo: PropTypes.string,\n}', + computed: true, + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object | object'); + }); + + it('should support enum of element', () => { + const component = createTestComponent({ + type: { + name: 'enum', + value: [ + { + value: '() => {\n return <div>FunctionnalComponent!</div>;\n}', + computed: true, + }, + { + value: + 'class ClassComponent extends React.PureComponent {\n render() {\n return <div>ClassComponent!</div>;\n }\n}', + computed: true, + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('element | ClassComponent'); + + const expectedDetail = `() => { + return <div>FunctionnalComponent!</div>; + } | class ClassComponent extends React.PureComponent { + render() { + return <div>ClassComponent!</div>; + } + }`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + describe('func', () => { + it('should return "func" when the prop dont have a description', () => { + const component = createTestComponent({ + type: { + name: 'func', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('func'); + }); + + it('should return "func" when the prop have a description without JSDoc tags', () => { + const component = createTestComponent({ + type: { + name: 'func', + }, + description: 'Hey! Hey!', + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('func'); + }); + + it('should return a func signature when there is JSDoc tags.', () => { + const component = createTestComponent({ + type: { + name: 'func', + }, + description: '@param event\n@param data\n@returns {string}', + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('(event, data) => string'); + }); + }); + + it('should return the instance type when type is instanceOf', () => { + const component = createTestComponent({ + type: { + name: 'instanceOf', + value: 'Set', + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('Set'); + }); + + describe('objectOf', () => { + it('should support objectOf primitive', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'number', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(number)'); + expect(type.detail).toBeUndefined(); + }); + + it('should support objectOf of identifier', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'custom', + raw: 'NAMED_OBJECT', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(NAMED_OBJECT)'); + expect(type.detail).toBeUndefined(); + }); + + it('should support objectOf short object', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'custom', + raw: '{\n foo: PropTypes.string,\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf({ foo: string })'); + expect(type.detail).toBeUndefined(); + }); + + it('should support objectOf long object', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'custom', + raw: + '{\n foo: PropTypes.string,\n bar: PropTypes.string,\n another: PropTypes.string,\n anotherAnother: PropTypes.string,\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(object)'); + + const expectedDetail = `objectOf({ + foo: string, + bar: string, + another: string, + anotherAnother: string + })`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep object in summary', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'custom', + raw: '{\n foo: { bar: PropTypes.string,\n }\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(object)'); + }); + + it('should support objectOf short shape', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf({ foo: string })'); + expect(type.detail).toBeUndefined(); + }); + + it('should support objectOf long shape', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + bar: { + name: 'string', + required: false, + }, + another: { + name: 'string', + required: false, + }, + anotherAnother: { + name: 'string', + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(object)'); + + const expectedDetail = `objectOf({ + foo: string, + bar: string, + another: string, + anotherAnother: string + })`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have a deep shape in summary', () => { + const component = createTestComponent({ + type: { + name: 'objectOf', + value: { + name: 'shape', + value: { + bar: { + name: 'shape', + value: { + hey: { + name: 'string', + required: false, + }, + }, + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('objectOf(object)'); + }); + }); + + it('should support union', () => { + const component = createTestComponent({ + type: { + name: 'union', + value: [ + { + name: 'string', + }, + { + name: 'instanceOf', + value: 'Set', + }, + ], + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('string | Set'); + expect(type.detail).toBeUndefined(); + }); + + describe('array', () => { + it('should support array of primitive', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'number', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('number[]'); + expect(type.detail).toBeUndefined(); + }); + + it('should support array of identifier', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'custom', + raw: 'NAMED_OBJECT', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('NAMED_OBJECT[]'); + expect(type.detail).toBeUndefined(); + }); + + it('should support array of short object', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'custom', + raw: '{\n foo: PropTypes.string,\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('[{ foo: string }]'); + expect(type.detail).toBeUndefined(); + }); + + it('should support array of long object', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'custom', + raw: + '{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n another: PropTypes.string.isRequired,\n another2: PropTypes.string.isRequired,\n another3: PropTypes.string.isRequired,\n another4: PropTypes.string.isRequired,\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object[]'); + + const expectedDetail = `[{ + text: string, + value: string, + another: string, + another2: string, + another3: string, + another4: string + }]`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep object in summary', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'custom', + raw: '{\n foo: { bar: PropTypes.string, }\n}', + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object[]'); + }); + + it('should support array of short shape', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('[{ foo: string }]'); + expect(type.detail).toBeUndefined(); + }); + + it('should support array of long shape', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'shape', + value: { + foo: { + name: 'string', + required: false, + }, + bar: { + name: 'string', + required: false, + }, + another: { + name: 'string', + required: false, + }, + another2: { + name: 'string', + required: false, + }, + another3: { + name: 'string', + required: false, + }, + another4: { + name: 'string', + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object[]'); + + const expectedDetail = `[{ + foo: string, + bar: string, + another: string, + another2: string, + another3: string, + another4: string + }]`; + + expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep shape in summary', () => { + const component = createTestComponent({ + type: { + name: 'arrayOf', + value: { + name: 'shape', + value: { + bar: { + name: 'shape', + value: { + hey: { + name: 'string', + required: false, + }, + }, + required: false, + }, + }, + }, + }, + }); + + const { type } = extractPropDef(component); + + expect(type.summary).toBe('object[]'); + }); + }); + }); + + describe('defaultValue', () => { + function createTestComponent( + defaultValue: DocgenPropDefaultValue, + typeName = 'anything-is-fine' + ): Component { + return createComponent({ + docgenInfo: { + ...createDocgenProp({ + name: 'prop', + type: { name: typeName }, + defaultValue, + }), + }, + }); + } + + it('should support short object', () => { + const component = createTestComponent(createDefaultValue("{ foo: 'foo', bar: 'bar' }")); + + const { defaultValue } = extractPropDef(component); + + const expectedSummary = "{ foo: 'foo', bar: 'bar' }"; + + expect(defaultValue.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, '')); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long object', () => { + const component = createTestComponent( + createDefaultValue("{ foo: 'foo', bar: 'bar', another: 'another' }") + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('object'); + + const expectedDetail = `{ + foo: 'foo', + bar: 'bar', + another: 'another' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep object in summary', () => { + const component = createTestComponent( + createDefaultValue("{ foo: 'foo', bar: { hey: 'ho' } }") + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('object'); + }); + + it('should support short function', () => { + const component = createTestComponent(createDefaultValue('() => {}')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('() => {}'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long function', () => { + const component = createTestComponent( + createDefaultValue( + '(foo, bar) => {\n const concat = foo + bar;\n const append = concat + " hey!";\n \n return append;\n}' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('func'); + + const expectedDetail = `(foo, bar) => { + const concat = foo + bar; + const append = concat + ' hey!'; + return append + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should use the name of function when available and indicate that args are present', () => { + const component = createTestComponent( + createDefaultValue('function concat(a, b) {\n return a + b;\n}') + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('concat( ... )'); + + const expectedDetail = `function concat(a, b) { + return a + b + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should use the name of function when available', () => { + const component = createTestComponent( + createDefaultValue('function hello() {\n return "hello";\n}') + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('hello()'); + + const expectedDetail = `function hello() { + return 'hello' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support short element', () => { + const component = createTestComponent(createDefaultValue('<div>Hey!</div>')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<div>Hey!</div>'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long element', () => { + const component = createTestComponent( + createDefaultValue( + '<div>Hey! Hey! Hey!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBe( + '<div>Hey! Hey! Hey!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>' + ); + }); + + it('should support element with props', () => { + const component = createTestComponent(createDefaultValue('<Component className="toto" />')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<Component />'); + expect(defaultValue.detail).toBe('<Component className="toto" />'); + }); + + it("should use the name of the React component when it's available", () => { + const component = createTestComponent( + createDefaultValue( + 'function InlinedFunctionalComponent() {\n return <div>Inlined FunctionnalComponent!</div>;\n}' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + + const expectedDetail = `function InlinedFunctionalComponent() { + return <div>Inlined FunctionnalComponent!</div>; + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not use the name of an HTML element', () => { + const component = createTestComponent(createDefaultValue('<div>Hey!</div>')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).not.toBe('<div />'); + }); + + it('should support short array', () => { + const component = createTestComponent(createDefaultValue('[1]')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('[1]'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long array', () => { + const component = createTestComponent( + createDefaultValue( + '[\n {\n thing: {\n id: 2,\n func: () => {},\n arr: [],\n },\n },\n]' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('array'); + + const expectedDetail = `[{ + thing: { + id: 2, + func: () => { + }, + arr: [] + } + }]`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep array in summary', () => { + const component = createTestComponent(createDefaultValue('[[[1]]]')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('array'); + }); + + describe('fromRawDefaultProp', () => { + [ + { type: 'string', defaultProp: 'foo' }, + { type: 'number', defaultProp: 1 }, + { type: 'boolean', defaultProp: true }, + { type: 'symbol', defaultProp: Symbol('hey!') }, + ].forEach(x => { + it(`should support ${x.type}`, () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, x.defaultProp); + + expect(defaultValue.summary).toBe(x.defaultProp.toString()); + expect(defaultValue.detail).toBeUndefined(); + }); + }); + + it('should support array of primitives', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [1, 2, 3]); + + expect(defaultValue.summary).toBe('[1, 2, 3]'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support array of short object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [{ foo: 'bar' }]); + + expect(defaultValue.summary).toBe("[{ 'foo': 'bar' }]"); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support array of long object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [{ foo: 'bar', bar: 'foo', hey: 'ho' }]); + + expect(defaultValue.summary).toBe('array'); + + const expectedDetail = `[{ + 'foo': 'bar', + 'bar': 'foo', + 'hey': 'ho' + }]`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support short object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, { foo: 'bar' }); + + expect(defaultValue.summary).toBe("{ 'foo': 'bar' }"); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, { foo: 'bar', bar: 'foo', hey: 'ho' }); + + expect(defaultValue.summary).toBe('object'); + + const expectedDetail = `{ + 'foo': 'bar', + 'bar': 'foo', + 'hey': 'ho' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support anonymous function', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, () => 'hey!'); + + expect(defaultValue.summary).toBe('func'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support named function', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, function hello() { + return 'world!'; + }); + + expect(defaultValue.summary).toBe('hello()'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support named function with params', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, function add(a: number, b: number) { + return a + b; + }); + + expect(defaultValue.summary).toBe('add( ... )'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support React element', () => { + const component = createTestComponent(null); + + const defaultProp = <ReactComponent />; + // Simulate babel-plugin-add-react-displayname. + defaultProp.type.displayName = 'ReactComponent'; + + const { defaultValue } = extractPropDef(component, defaultProp); + + expect(defaultValue.summary).toBe('<ReactComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support React element with props', () => { + const component = createTestComponent(null); + + // @ts-ignore + const defaultProp = <ReactComponent className="toto" />; + // Simulate babel-plugin-add-react-displayname. + defaultProp.type.displayName = 'ReactComponent'; + + const { defaultValue } = extractPropDef(component, defaultProp); + + expect(defaultValue.summary).toBe('<ReactComponent />'); + expect(defaultValue.detail).toBe('<ReactComponent className="toto" />'); + }); + + it('should support short HTML element', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, <div>HTML element</div>); + + expect(defaultValue.summary).toBe('<div>HTML element</div>'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long HTML element', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef( + component, + <div>HTML element!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div> + ); + + expect(defaultValue.summary).toBe('element'); + + const expectedDetail = `<div> + HTML element!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + </div>`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + ['element', 'elementType'].forEach(x => { + it(`should support inlined React class component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef( + component, + class InlinedClassComponent extends React.PureComponent { + render() { + return <div>Inlined ClassComponent!</div>; + } + } + ); + + expect(defaultValue.summary).toBe('<InlinedClassComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined anonymous React functional component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, () => { + return <div>Inlined FunctionnalComponent!</div>; + }); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined anonymous React functional component with props for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, ({ foo }: { foo: string }) => { + return <div>{foo}</div>; + }); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined named React functional component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, function InlinedFunctionalComponent() { + return <div>Inlined FunctionnalComponent!</div>; + }); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined named React functional component with props for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, function InlinedFunctionalComponent({ + foo, + }: { + foo: string; + }) { + return <div>{foo}</div>; + }); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + }); + }); + }); +}); + +describe('enhancePropTypesProps', () => { + it('should keep the original definition order', () => { + const component = createComponent({ + propTypes: { + foo: PropTypes.string, + middleWithDefaultValue: PropTypes.string, + bar: PropTypes.string, + endWithDefaultValue: PropTypes.string, + }, + docgenInfo: { + ...createDocgenProp({ + name: 'middleWithDefaultValue', + type: { name: 'string' }, + defaultValue: { value: 'Middle!' }, + }), + ...createDocgenProp({ + name: 'endWithDefaultValue', + type: { name: 'string' }, + defaultValue: { value: 'End!' }, + }), + ...createDocgenProp({ + name: 'foo', + type: { name: 'string' }, + }), + ...createDocgenProp({ + name: 'bar', + type: { name: 'string' }, + }), + }, + }); + + const props = enhancePropTypesProps( + extractComponentProps(component, DOCGEN_SECTION), + component + ); + + expect(props.length).toBe(4); + expect(props[0].name).toBe('foo'); + expect(props[1].name).toBe('middleWithDefaultValue'); + expect(props[2].name).toBe('bar'); + expect(props[3].name).toBe('endWithDefaultValue'); + }); + + it('should not include @ignore props', () => { + const component = createComponent({ + propTypes: { + foo: PropTypes.string, + bar: PropTypes.string, + }, + docgenInfo: { + ...createDocgenProp({ + name: 'foo', + type: { name: 'string' }, + }), + ...createDocgenProp({ + name: 'bar', + type: { name: 'string' }, + description: '@ignore', + }), + }, + }); + + const props = enhancePropTypesProps( + extractComponentProps(component, DOCGEN_SECTION), + component + ); + + expect(props.length).toBe(1); + expect(props[0].name).toBe('foo'); + }); +}); diff --git a/addons/docs/src/frameworks/react/propTypes/handleProp.ts b/addons/docs/src/frameworks/react/propTypes/handleProp.ts new file mode 100644 index 000000000000..819b223cbdbb --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/handleProp.ts @@ -0,0 +1,50 @@ +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 { keepOriginalDefinitionOrder } from './sortProps'; +import { rawDefaultPropTypeResolvers } from './rawDefaultPropResolvers'; + +export function enhancePropTypesProp(extractedProp: ExtractedProp, rawDefaultProp?: any): PropDef { + const { propDef } = extractedProp; + + const newtype = createType(extractedProp); + if (!isNil(newtype)) { + propDef.type = newtype; + } + + const { defaultValue } = extractedProp.docgenInfo; + if (!isNil(defaultValue) && !isNil(defaultValue.value)) { + const newDefaultValue = createDefaultValue(defaultValue.value); + + if (!isNil(newDefaultValue)) { + propDef.defaultValue = newDefaultValue; + } + } else if (!isNil(rawDefaultProp)) { + const newDefaultValue = createDefaultValueFromRawDefaultProp( + rawDefaultProp, + propDef, + rawDefaultPropTypeResolvers + ); + + if (!isNil(newDefaultValue)) { + propDef.defaultValue = newDefaultValue; + } + } + + return propDef; +} + +export function enhancePropTypesProps( + extractedProps: ExtractedProp[], + component: Component +): PropDef[] { + const rawDefaultProps = !isNil(component.defaultProps) ? component.defaultProps : {}; + const enhancedProps = extractedProps.map(x => + enhancePropTypesProp(x, rawDefaultProps[x.propDef.name]) + ); + + return keepOriginalDefinitionOrder(enhancedProps, component); +} diff --git a/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts b/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts new file mode 100644 index 000000000000..452396222a93 --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/rawDefaultPropResolvers.ts @@ -0,0 +1,31 @@ +import { isNil } from 'lodash'; +import { TypeResolver, extractFunctionName, createTypeResolvers } from '../lib/defaultValues'; +import { createSummaryValue } from '../../../lib'; +import { FUNCTION_CAPTION, ELEMENT_CAPTION } from '../lib'; +import { + getPrettyElementIdentifier, + getPrettyFuncIdentifier, +} from '../lib/defaultValues/prettyIdentifier'; +import { inspectValue, InspectionFunction } from '../lib/inspection'; + +const funcResolver: TypeResolver = (rawDefaultProp, { name, type }) => { + const isElement = type.summary === 'element' || type.summary === 'elementType'; + + const funcName = extractFunctionName(rawDefaultProp, name); + if (!isNil(funcName)) { + // 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)); + } + + const { hasParams } = inspectValue(rawDefaultProp.toString()).inferedType as InspectionFunction; + + return createSummaryValue(getPrettyFuncIdentifier(funcName, hasParams)); + } + + return createSummaryValue(isElement ? ELEMENT_CAPTION : FUNCTION_CAPTION); +}; + +export const rawDefaultPropTypeResolvers = createTypeResolvers({ + function: funcResolver, +}); diff --git a/addons/docs/src/frameworks/react/propTypes/sortProps.ts b/addons/docs/src/frameworks/react/propTypes/sortProps.ts new file mode 100644 index 000000000000..9061ee0ded52 --- /dev/null +++ b/addons/docs/src/frameworks/react/propTypes/sortProps.ts @@ -0,0 +1,21 @@ +import { PropDef } from '@storybook/components'; +import { isNil } from 'lodash'; +import { Component } from '../../../blocks/shared'; + +// 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. +export function keepOriginalDefinitionOrder( + extractedProps: PropDef[], + component: Component +): PropDef[] { + // eslint-disable-next-line react/forbid-foreign-prop-types + const { propTypes } = component; + + if (!isNil(propTypes)) { + return Object.keys(propTypes) + .map(x => extractedProps.find(y => y.name === x)) + .filter(x => x); + } + + return extractedProps; +} diff --git a/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx b/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx new file mode 100644 index 000000000000..a0cd380901bb --- /dev/null +++ b/addons/docs/src/frameworks/react/typeScript/handleProp.test.tsx @@ -0,0 +1,502 @@ +/* eslint-disable no-underscore-dangle */ + +import { PropDef } from '@storybook/components'; +import React from 'react'; +import { Component } from '../../../blocks/shared'; +import { extractComponentProps, DocgenInfo, DocgenPropDefaultValue } from '../../../lib/docgen'; +import { enhanceTypeScriptProp } from './handleProp'; + +const DOCGEN_SECTION = 'props'; + +function ReactComponent() { + return <div>React Component!</div>; +} + +function createDocgenSection(docgenInfo: DocgenInfo): Record<string, any> { + return { + [DOCGEN_SECTION]: { + ...docgenInfo, + }, + }; +} + +function createDocgenProp({ + name, + tsType, + ...others +}: Partial<DocgenInfo> & { name: string }): Record<string, any> { + return { + [name]: { + tsType, + required: false, + ...others, + }, + }; +} + +// eslint-disable-next-line react/forbid-foreign-prop-types +function createComponent({ propTypes = {}, defaultProps = {}, docgenInfo = {} }): Component { + const component = () => { + return <div>Hey!</div>; + }; + component.propTypes = propTypes; + component.defaultProps = defaultProps; + + // @ts-ignore + component.__docgenInfo = createDocgenSection(docgenInfo); + + return component; +} + +function createDefaultValue(defaultValue: string): DocgenPropDefaultValue { + return { value: defaultValue }; +} + +function extractPropDef(component: Component, rawDefaultProp?: any): PropDef { + return enhanceTypeScriptProp(extractComponentProps(component, DOCGEN_SECTION)[0], rawDefaultProp); +} + +describe('enhanceTypeScriptProp', () => { + describe('defaultValue', () => { + function createTestComponent( + defaultValue: DocgenPropDefaultValue, + typeName = 'anything-is-fine' + ): Component { + return createComponent({ + docgenInfo: { + ...createDocgenProp({ + name: 'prop', + tsType: { name: typeName }, + defaultValue, + }), + }, + }); + } + + it('should support short object', () => { + const component = createTestComponent(createDefaultValue("{ foo: 'foo', bar: 'bar' }")); + + const { defaultValue } = extractPropDef(component); + + const expectedSummary = "{ foo: 'foo', bar: 'bar' }"; + + expect(defaultValue.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, '')); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long object', () => { + const component = createTestComponent( + createDefaultValue("{ foo: 'foo', bar: 'bar', another: 'another' }") + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('object'); + + const expectedDetail = `{ + foo: 'foo', + bar: 'bar', + another: 'another' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep object in summary', () => { + const component = createTestComponent( + createDefaultValue("{ foo: 'foo', bar: { hey: 'ho' } }") + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('object'); + }); + + it('should support short function', () => { + const component = createTestComponent(createDefaultValue('() => {}')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('() => {}'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long function', () => { + const component = createTestComponent( + createDefaultValue( + '(foo, bar) => {\n const concat = foo + bar;\n const append = concat + " hey!";\n \n return append;\n}' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('func'); + + const expectedDetail = `(foo, bar) => { + const concat = foo + bar; + const append = concat + ' hey!'; + return append + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should use the name of function when available and indicate that args are present', () => { + const component = createTestComponent( + createDefaultValue('function concat(a, b) {\n return a + b;\n}') + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('concat( ... )'); + + const expectedDetail = `function concat(a, b) { + return a + b + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should use the name of function when available', () => { + const component = createTestComponent( + createDefaultValue('function hello() {\n return "hello";\n}') + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('hello()'); + + const expectedDetail = `function hello() { + return 'hello' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support short element', () => { + const component = createTestComponent(createDefaultValue('<div>Hey!</div>')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<div>Hey!</div>'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long element', () => { + const component = createTestComponent( + createDefaultValue( + '<div>Hey! Hey! Hey!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBe( + '<div>Hey! Hey! Hey!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div>' + ); + }); + + it('should support element with props', () => { + const component = createTestComponent(createDefaultValue('<Component className="toto" />')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<Component />'); + expect(defaultValue.detail).toBe('<Component className="toto" />'); + }); + + it("should use the name of the React component when it's available", () => { + const component = createTestComponent( + createDefaultValue( + 'function InlinedFunctionalComponent() {\n return <div>Inlined FunctionnalComponent!</div>;\n}' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + + const expectedDetail = `function InlinedFunctionalComponent() { + return <div>Inlined FunctionnalComponent!</div>; + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not use the name of an HTML element', () => { + const component = createTestComponent(createDefaultValue('<div>Hey!</div>')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).not.toBe('<div />'); + }); + + it('should support short array', () => { + const component = createTestComponent(createDefaultValue('[1]')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('[1]'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long array', () => { + const component = createTestComponent( + createDefaultValue( + '[\n {\n thing: {\n id: 2,\n func: () => {},\n arr: [],\n },\n },\n]' + ) + ); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('array'); + + const expectedDetail = `[{ + thing: { + id: 2, + func: () => { + }, + arr: [] + } + }]`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should not have deep array in summary', () => { + const component = createTestComponent(createDefaultValue('[[[1]]]')); + + const { defaultValue } = extractPropDef(component); + + expect(defaultValue.summary).toBe('array'); + }); + + describe('fromRawDefaultProp', () => { + [ + { type: 'string', defaultProp: 'foo' }, + { type: 'number', defaultProp: 1 }, + { type: 'boolean', defaultProp: true }, + { type: 'symbol', defaultProp: Symbol('hey!') }, + ].forEach(x => { + it(`should support ${x.type}`, () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, x.defaultProp); + + expect(defaultValue.summary).toBe(x.defaultProp.toString()); + expect(defaultValue.detail).toBeUndefined(); + }); + }); + + it('should support array of primitives', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [1, 2, 3]); + + expect(defaultValue.summary).toBe('[1, 2, 3]'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support array of short object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [{ foo: 'bar' }]); + + expect(defaultValue.summary).toBe("[{ 'foo': 'bar' }]"); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support array of long object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, [{ foo: 'bar', bar: 'foo', hey: 'ho' }]); + + expect(defaultValue.summary).toBe('array'); + + const expectedDetail = `[{ + 'foo': 'bar', + 'bar': 'foo', + 'hey': 'ho' + }]`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support short object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, { foo: 'bar' }); + + expect(defaultValue.summary).toBe("{ 'foo': 'bar' }"); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long object', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, { foo: 'bar', bar: 'foo', hey: 'ho' }); + + expect(defaultValue.summary).toBe('object'); + + const expectedDetail = `{ + 'foo': 'bar', + 'bar': 'foo', + 'hey': 'ho' + }`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + it('should support anonymous function', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, () => 'hey!'); + + expect(defaultValue.summary).toBe('func'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support named function', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, function hello() { + return 'world!'; + }); + + expect(defaultValue.summary).toBe('hello()'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support named function with params', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, function add(a: number, b: number) { + return a + b; + }); + + expect(defaultValue.summary).toBe('add( ... )'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support React element', () => { + const component = createTestComponent(null); + + const defaultProp = <ReactComponent />; + // Simulate babel-plugin-add-react-displayname. + defaultProp.type.displayName = 'ReactComponent'; + + const { defaultValue } = extractPropDef(component, defaultProp); + + expect(defaultValue.summary).toBe('<ReactComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support React element with props', () => { + const component = createTestComponent(null); + + // @ts-ignore + const defaultProp = <ReactComponent className="toto" />; + // Simulate babel-plugin-add-react-displayname. + defaultProp.type.displayName = 'ReactComponent'; + + const { defaultValue } = extractPropDef(component, defaultProp); + + expect(defaultValue.summary).toBe('<ReactComponent />'); + expect(defaultValue.detail).toBe('<ReactComponent className="toto" />'); + }); + + it('should support short HTML element', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef(component, <div>HTML element</div>); + + expect(defaultValue.summary).toBe('<div>HTML element</div>'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it('should support long HTML element', () => { + const component = createTestComponent(null); + + const { defaultValue } = extractPropDef( + component, + <div>HTML element!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</div> + ); + + expect(defaultValue.summary).toBe('element'); + + const expectedDetail = `<div> + HTML element!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + </div>`; + + expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); + }); + + ['element', 'elementType'].forEach(x => { + it(`should support inlined React class component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef( + component, + class InlinedClassComponent extends React.PureComponent { + render() { + return <div>Inlined ClassComponent!</div>; + } + } + ); + + expect(defaultValue.summary).toBe('<InlinedClassComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined anonymous React functional component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, () => { + return <div>Inlined FunctionnalComponent!</div>; + }); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined anonymous React functional component with props for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, ({ foo }: { foo: string }) => { + return <div>{foo}</div>; + }); + + expect(defaultValue.summary).toBe('element'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined named React functional component for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, function InlinedFunctionalComponent() { + return <div>Inlined FunctionnalComponent!</div>; + }); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + + it(`should support inlined named React functional component with props for ${x}`, () => { + const component = createTestComponent(null, x); + + const { defaultValue } = extractPropDef(component, function InlinedFunctionalComponent({ + foo, + }: { + foo: string; + }) { + return <div>{foo}</div>; + }); + + expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />'); + expect(defaultValue.detail).toBeUndefined(); + }); + }); + }); + }); +}); diff --git a/addons/docs/src/frameworks/react/typeScript/handleProp.ts b/addons/docs/src/frameworks/react/typeScript/handleProp.ts new file mode 100644 index 000000000000..9f840806de52 --- /dev/null +++ b/addons/docs/src/frameworks/react/typeScript/handleProp.ts @@ -0,0 +1,28 @@ +import { isNil } from 'lodash'; +import { PropDef } from '@storybook/components'; +import { ExtractedProp } from '../../../lib/docgen'; +import { createDefaultValue, createDefaultValueFromRawDefaultProp } from '../lib/defaultValues'; + +export function enhanceTypeScriptProp(extractedProp: ExtractedProp, rawDefaultProp?: any): PropDef { + const { propDef } = extractedProp; + + const { defaultValue } = extractedProp.docgenInfo; + if (!isNil(defaultValue) && !isNil(defaultValue.value)) { + const newDefaultValue = createDefaultValue(defaultValue.value); + if (!isNil(newDefaultValue)) { + propDef.defaultValue = newDefaultValue; + } + } else if (!isNil(rawDefaultProp)) { + const newDefaultValue = createDefaultValueFromRawDefaultProp(rawDefaultProp, propDef); + + if (!isNil(newDefaultValue)) { + propDef.defaultValue = newDefaultValue; + } + } + + return propDef; +} + +export function enhanceTypeScriptProps(extractedProps: ExtractedProp[]): PropDef[] { + return extractedProps.map(enhanceTypeScriptProp); +} diff --git a/addons/docs/src/frameworks/vue/config.tsx b/addons/docs/src/frameworks/vue/config.tsx index 0f395e0f47a6..680e46165797 100644 --- a/addons/docs/src/frameworks/vue/config.tsx +++ b/addons/docs/src/frameworks/vue/config.tsx @@ -4,7 +4,7 @@ import toReact from '@egoist/vue-to-react'; import { StoryFn } from '@storybook/addons'; import { addParameters } from '@storybook/client-api'; import { extractProps } from './extractProps'; -import { extractComponentDescription } from '../../lib/docgenUtils'; +import { extractComponentDescription } from '../../lib/docgen'; addParameters({ docs: { diff --git a/addons/docs/src/frameworks/vue/extractProps.ts b/addons/docs/src/frameworks/vue/extractProps.ts index 27397cf38617..f4a26acc76cf 100644 --- a/addons/docs/src/frameworks/vue/extractProps.ts +++ b/addons/docs/src/frameworks/vue/extractProps.ts @@ -1,5 +1,5 @@ import { PropDef } from '@storybook/components'; -import { PropsExtractor, extractPropsFromDocgen, hasDocgen } from '../../lib/docgenUtils'; +import { PropsExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; const SECTIONS = ['props', 'events', 'slots']; @@ -9,7 +9,7 @@ export const extractProps: PropsExtractor = component => { } const sections: Record<string, PropDef[]> = {}; SECTIONS.forEach(section => { - sections[section] = extractPropsFromDocgen(component, 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 new file mode 100644 index 000000000000..527ce55570c9 --- /dev/null +++ b/addons/docs/src/frameworks/vue/preset.ts @@ -0,0 +1,8 @@ +export function webpack(webpackConfig: any = {}, options: any = {}) { + webpackConfig.module.rules.push({ + test: /\.vue$/, + loader: 'vue-docgen-loader', + enforce: 'post', + }); + return webpackConfig; +} diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index a474900c1a05..883c269e4e9f 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -8,10 +8,10 @@ import { render } from 'lit-html'; function mapData(data) { return data.map(item => ({ name: item.name, - type: { name: item.type }, + type: { summary: item.type }, required: '', description: item.description, - defaultValue: item.default, + defaultValue: { summary: item.default !== undefined ? item.default : item.defaultValue }, })); } @@ -32,7 +32,7 @@ addParameters({ sections.attributes = mapData(metaData.attributes); } if (metaData.properties) { - sections.props = mapData(metaData.properties); + sections.properties = mapData(metaData.properties); } if (metaData.events) { sections.events = mapData(metaData.events); diff --git a/addons/docs/src/lib/DocgenInfo.ts b/addons/docs/src/lib/DocgenInfo.ts deleted file mode 100644 index 9330dc136425..000000000000 --- a/addons/docs/src/lib/DocgenInfo.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface DocgenInfo { - type?: { - name: string; - value?: { - name?: string; - raw?: string; - }; - }; - flowType?: any; - tsType?: any; - required: boolean; - description?: string; - defaultValue?: { - value: string; - }; -} diff --git a/addons/docs/src/lib/docgen/createPropDef.ts b/addons/docs/src/lib/docgen/createPropDef.ts new file mode 100644 index 000000000000..9d844cf00fb5 --- /dev/null +++ b/addons/docs/src/lib/docgen/createPropDef.ts @@ -0,0 +1,106 @@ +import { isNil } from 'lodash'; +import { PropDef, PropDefaultValue } from '@storybook/components'; +import { TypeSystem, DocgenInfo, DocgenType, DocgenPropDefaultValue } from './types'; +import { JsDocParsingResult } from '../jsdocParser'; +import { createSummaryValue } from '../utils'; +import { createFlowPropDef } from './flow/createPropDef'; +import { isDefaultValueBlacklisted } from './utils/defaultValue'; +import { createTsPropDef } from './typeScript/createPropDef'; + +export type PropDefFactory = ( + propName: string, + docgenInfo: DocgenInfo, + jsDocParsingResult?: JsDocParsingResult +) => PropDef; + +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; +} + +function createDefaultValue(defaultValue: DocgenPropDefaultValue): PropDefaultValue { + if (!isNil(defaultValue)) { + const { value } = defaultValue; + + if (!isDefaultValueBlacklisted(value)) { + return createSummaryValue(value); + } + } + + return null; +} + +function createBasicPropDef(name: string, type: DocgenType, docgenInfo: DocgenInfo): PropDef { + const { description, required, defaultValue } = docgenInfo; + + return { + name, + type: createType(type), + required, + description, + defaultValue: createDefaultValue(defaultValue), + }; +} + +function applyJsDocResult(propDef: PropDef, jsDocParsingResult: JsDocParsingResult): PropDef { + if (jsDocParsingResult.includesJsDoc) { + const { description, extractedTags } = jsDocParsingResult; + + if (!isNil(description)) { + // 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); + + if (hasParams || hasReturns) { + // eslint-disable-next-line no-param-reassign + propDef.jsDocTags = { + params: + hasParams && + extractedTags.params.map(x => ({ name: x.getPrettyName(), description: x.description })), + returns: hasReturns && { description: extractedTags.returns.description }, + }; + } + } + + return propDef; +} + +export const javaScriptFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createBasicPropDef(propName, docgenInfo.type, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); +}; + +export const tsFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createTsPropDef(propName, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); +}; + +export const flowFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createFlowPropDef(propName, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); +}; + +export const unknownFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createBasicPropDef(propName, { name: 'unknown' }, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); +}; + +export const getPropDefFactory = (typeSystem: TypeSystem): PropDefFactory => { + switch (typeSystem) { + case TypeSystem.JAVASCRIPT: + return javaScriptFactory; + case TypeSystem.TYPESCRIPT: + return tsFactory; + case TypeSystem.FLOW: + return flowFactory; + default: + return unknownFactory; + } +}; diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.test.ts b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts new file mode 100644 index 000000000000..d2b7c57aa0a5 --- /dev/null +++ b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts @@ -0,0 +1,180 @@ +/* eslint-disable no-underscore-dangle */ + +import { Component } from '../../blocks/shared'; +import { extractComponentProps } from './extractDocgenProps'; + +const DOCGEN_SECTION = 'props'; +const PROP_NAME = 'propName'; + +interface TypeSystemDef { + name: string; + typeProperty?: string; +} + +const TypeSystems: TypeSystemDef[] = [ + { name: 'javascript', typeProperty: 'type' }, + { name: 'typescript', typeProperty: 'tsType' }, + { name: 'flow', typeProperty: 'flowType' }, +]; + +function createType(typeName: string, others: Record<string, any> = {}): Record<string, string> { + return { + name: typeName, + ...others, + }; +} + +function createStringType(typeSystemDef: TypeSystemDef, others: Record<string, any> = {}): any { + return { + [typeSystemDef.typeProperty]: createType('string', others), + }; +} + +function createFuncType(typeSystemDef: TypeSystemDef, others: Record<string, any> = {}): any { + const typeName = typeSystemDef.name === 'javascript' ? 'func' : '() => {}'; + + return { + [typeSystemDef.typeProperty]: createType(typeName, others), + }; +} + +function createComponent(docgenInfo: Record<string, any>): Component { + const component = () => {}; + // @ts-ignore + component.__docgenInfo = { + [DOCGEN_SECTION]: { + [PROP_NAME]: { + required: false, + ...docgenInfo, + }, + }, + }; + + return component; +} + +TypeSystems.forEach(x => { + describe(`${x.name}`, () => { + it('should map defaults docgen info properly', () => { + const component = createComponent({ + ...createStringType(x), + description: 'Hey! Hey!', + defaultValue: { + value: 'Default', + }, + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.name).toBe(PROP_NAME); + expect(propDef.type.summary).toBe('string'); + expect(propDef.description).toBe('Hey! Hey!'); + expect(propDef.required).toBe(false); + expect(propDef.defaultValue.summary).toBe('Default'); + }); + + it('should remove JSDoc tags from the description', () => { + const component = createComponent({ + ...createStringType(x), + description: 'Hey!\n@param event\nreturns {string}', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('Hey!'); + }); + + it('should not remove newline characters of multilines description without JSDoc tags', () => { + const component = createComponent({ + ...createStringType(x), + description: 'onClick description\nis a\nmulti-lines\ndescription', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); + }); + + it('should not remove newline characters of multilines description with JSDoc tags', () => { + const component = createComponent({ + ...createFuncType(x), + description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); + }); + + it('should not remove markdown from description without JSDoc tags', () => { + const component = createComponent({ + ...createStringType(x), + description: 'onClick *emphasis*, **strong**, `formatted` description.', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); + }); + + it('should not remove markdown from description with JSDoc tags', () => { + const component = createComponent({ + ...createFuncType(x), + description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); + }); + + it('should return null when the property is marked with @ignore', () => { + const component = createComponent({ + ...createStringType(x), + description: 'onClick description\n@ignore', + }); + + expect(extractComponentProps(component, DOCGEN_SECTION).length).toBe(0); + }); + + it('should provide raw @param tags', () => { + const component = createComponent({ + ...createFuncType(x), + description: + 'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value', + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.description).toBe('onClick description'); + expect(propDef.jsDocTags).toBeDefined(); + expect(propDef.jsDocTags.params).toBeDefined(); + expect(propDef.jsDocTags.params[0].name).toBe('event'); + expect(propDef.jsDocTags.params[0].description).toBe('Original event.'); + expect(propDef.jsDocTags.params[1].name).toBe('value'); + expect(propDef.jsDocTags.params[1].description).toBeNull(); + }); + + it("should not return 'null' default value", () => { + const component = createComponent({ + ...createStringType(x), + defaultValue: { value: 'null' }, + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.defaultValue).toBeNull(); + }); + + it("should not return 'undefined' default value", () => { + const component = createComponent({ + ...createStringType(x), + defaultValue: { value: 'undefined' }, + }); + + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; + + expect(propDef.defaultValue).toBeNull(); + }); + }); +}); diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.ts b/addons/docs/src/lib/docgen/extractDocgenProps.ts new file mode 100644 index 000000000000..939b522d6f09 --- /dev/null +++ b/addons/docs/src/lib/docgen/extractDocgenProps.ts @@ -0,0 +1,97 @@ +import { isNil } from 'lodash'; +import { PropDef } from '@storybook/components'; +import { Component } from '../../blocks/shared'; +import { ExtractedJsDoc, parseJsDoc } from '../jsdocParser'; +import { DocgenInfo, TypeSystem } from './types'; +import { getDocgenSection, isValidDocgenSection, getDocgenDescription } from './utils'; +import { getPropDefFactory, PropDefFactory } from './createPropDef'; + +export interface ExtractedProp { + propDef: PropDef; + docgenInfo: DocgenInfo; + jsDocTags: ExtractedJsDoc; + typeSystem: TypeSystem; +} + +export type ExtractProps = (component: Component, section: string) => ExtractedProp[]; + +const getTypeSystem = (docgenInfo: DocgenInfo): TypeSystem => { + if (!isNil(docgenInfo.type)) { + return TypeSystem.JAVASCRIPT; + } + + if (!isNil(docgenInfo.flowType)) { + return TypeSystem.FLOW; + } + + if (!isNil(docgenInfo.tsType)) { + return TypeSystem.TYPESCRIPT; + } + + return TypeSystem.UNKNOWN; +}; + +export const extractComponentSectionArray = (docgenSection: any) => { + const typeSystem = getTypeSystem(docgenSection[0]); + const createPropDef = getPropDefFactory(typeSystem); + + return docgenSection + .map((item: any) => extractProp(item.name, item, typeSystem, createPropDef)) + .filter(Boolean); +}; + +export const extractComponentSectionObject = (docgenSection: any) => { + const docgenPropsKeys = Object.keys(docgenSection); + const typeSystem = getTypeSystem(docgenSection[docgenPropsKeys[0]]); + const createPropDef = getPropDefFactory(typeSystem); + + return docgenPropsKeys + .map(propName => { + const docgenInfo = docgenSection[propName]; + + return !isNil(docgenInfo) + ? extractProp(propName, docgenInfo, typeSystem, createPropDef) + : null; + }) + .filter(Boolean); +}; + +export const extractComponentProps: ExtractProps = (component, section) => { + const docgenSection = getDocgenSection(component, section); + + if (!isValidDocgenSection(docgenSection)) { + return []; + } + + // vue-docgen-api has diverged from react-docgen and returns an array + return Array.isArray(docgenSection) + ? extractComponentSectionArray(docgenSection) + : extractComponentSectionObject(docgenSection); +}; + +function extractProp( + propName: string, + docgenInfo: DocgenInfo, + typeSystem: TypeSystem, + createPropDef: PropDefFactory +): ExtractedProp { + const jsDocParsingResult = parseJsDoc(docgenInfo.description); + const isIgnored = jsDocParsingResult.includesJsDoc && jsDocParsingResult.ignore; + + if (!isIgnored) { + const propDef = createPropDef(propName, docgenInfo, jsDocParsingResult); + + return { + propDef, + jsDocTags: jsDocParsingResult.extractedTags, + docgenInfo, + typeSystem, + }; + } + + return null; +} + +export function extractComponentDescription(component?: Component): string { + return !isNil(component) && getDocgenDescription(component); +} diff --git a/addons/docs/src/lib/docgen/flow/createDefaultValue.ts b/addons/docs/src/lib/docgen/flow/createDefaultValue.ts new file mode 100644 index 000000000000..0bec4162d388 --- /dev/null +++ b/addons/docs/src/lib/docgen/flow/createDefaultValue.ts @@ -0,0 +1,22 @@ +import { PropDefaultValue } from '@storybook/components'; +import { isNil } from 'lodash'; +import { DocgenPropDefaultValue, DocgenPropType } from '../types'; +import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../utils'; +import { isDefaultValueBlacklisted } from '../utils/defaultValue'; + +export function createDefaultValue( + defaultValue: DocgenPropDefaultValue, + type: DocgenPropType +): PropDefaultValue { + if (!isNil(defaultValue)) { + const { value } = defaultValue; + + if (!isDefaultValueBlacklisted(value)) { + return !isTooLongForDefaultValueSummary(value) + ? createSummaryValue(value) + : createSummaryValue(type.name, value); + } + } + + return null; +} diff --git a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts new file mode 100644 index 000000000000..0b6a4e7156df --- /dev/null +++ b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts @@ -0,0 +1,292 @@ +import { createFlowPropDef } from './createPropDef'; +import { DocgenInfo } from '../types'; + +const PROP_NAME = 'propName'; + +function createDocgenInfo({ flowType, ...others }: Partial<DocgenInfo>): DocgenInfo { + return { + flowType, + required: false, + ...others, + }; +} + +describe('type', () => { + ['string', 'number', 'boolean', 'any', 'void', 'Object', 'String', 'MyClass', 'literal'].forEach( + x => { + it(`should support ${x}`, () => { + const docgenInfo = createDocgenInfo({ + flowType: { name: x }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe(x); + expect(type.detail).toBeUndefined(); + }); + } + ); + + ['Array', 'Class', 'MyClass'].forEach(x => { + it(`should support untyped ${x}`, () => { + const docgenInfo = createDocgenInfo({ + flowType: { name: x }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe(x); + expect(type.detail).toBeUndefined(); + }); + + it(`should support typed ${x}`, () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: x, + elements: [ + { + name: 'any', + }, + ], + raw: `${x}<any>`, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe(`${x}<any>`); + expect(type.detail).toBeUndefined(); + }); + }); + + it('should support short object signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: '{ foo: string, bar?: mixed }', + signature: { + properties: [ + { + key: 'foo', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'bar', + value: { + name: 'mixed', + required: false, + }, + }, + ], + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('{ foo: string, bar?: mixed }'); + expect(type.detail).toBeUndefined(); + }); + + it('should support long object signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'object', + raw: + '{ (x: string): void, prop1: string, prop2: string, prop3: string, prop4: string, prop5: string, prop6: string, prop7: string, prop8: string }', + signature: { + properties: [ + { + key: 'prop1', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop2', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop3', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop4', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop5', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop5', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop6', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop7', + value: { + name: 'string', + required: true, + }, + }, + { + key: 'prop8', + value: { + name: 'string', + required: true, + }, + }, + ], + constructor: { + name: 'signature', + type: 'function', + raw: '(x: string): void', + signature: { + arguments: [ + { + name: 'x', + type: { + name: 'string', + }, + }, + ], + return: { + name: 'void', + }, + }, + }, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('object'); + expect(type.detail).toBe( + '{ (x: string): void, prop1: string, prop2: string, prop3: string, prop4: string, prop5: string, prop6: string, prop7: string, prop8: string }' + ); + }); + + it('should support func signature', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'signature', + type: 'function', + raw: '(x: string) => void', + signature: { + arguments: [ + { + name: 'x', + type: { + name: 'string', + }, + }, + ], + return: { + name: 'void', + }, + }, + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('(x: string) => void'); + expect(type.detail).toBeUndefined(); + }); + + it('should support tuple', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'tuple', + raw: '[foo, "value", number]', + elements: [ + { + name: 'foo', + }, + { + name: 'literal', + value: '"value"', + }, + { + name: 'number', + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('[foo, "value", number]'); + }); + + it('should support union', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: 'number | string', + elements: [ + { + name: 'number', + }, + { + name: 'string', + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('number | string'); + }); + + it('should support intersection', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'intersection', + raw: 'number & string', + elements: [ + { + name: 'number', + }, + { + name: 'string', + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('number & string'); + }); +}); diff --git a/addons/docs/src/lib/docgen/flow/createPropDef.ts b/addons/docs/src/lib/docgen/flow/createPropDef.ts new file mode 100644 index 000000000000..74c03e280a9b --- /dev/null +++ b/addons/docs/src/lib/docgen/flow/createPropDef.ts @@ -0,0 +1,15 @@ +import { PropDefFactory } from '../createPropDef'; +import { createType } from './createType'; +import { createDefaultValue } from './createDefaultValue'; + +export const createFlowPropDef: PropDefFactory = (propName, docgenInfo) => { + const { flowType, description, required, defaultValue } = docgenInfo; + + return { + name: propName, + type: createType(flowType), + required, + description, + defaultValue: createDefaultValue(defaultValue, flowType), + }; +}; diff --git a/addons/docs/src/lib/docgen/flow/createType.ts b/addons/docs/src/lib/docgen/flow/createType.ts new file mode 100644 index 000000000000..24530648f4e7 --- /dev/null +++ b/addons/docs/src/lib/docgen/flow/createType.ts @@ -0,0 +1,71 @@ +import { PropType } from '@storybook/components'; +import { isNil } from 'lodash'; +import { DocgenFlowType } from '../types'; +import { createSummaryValue, isTooLongForTypeSummary } from '../../utils'; + +enum FlowTypesType { + UNION = 'union', + SIGNATURE = 'signature', +} + +interface DocgenFlowUnionType extends DocgenFlowType { + elements: { name: string; value: string }[]; +} + +function generateUnion({ name, raw, elements }: DocgenFlowUnionType): PropType { + if (!isNil(raw)) { + return createSummaryValue(raw); + } + + if (!isNil(elements)) { + return createSummaryValue(elements.map(x => x.value).join(' | ')); + } + + return createSummaryValue(name); +} + +function generateFuncSignature({ type, raw }: DocgenFlowType): PropType { + if (!isNil(raw)) { + return createSummaryValue(raw); + } + + return createSummaryValue(type); +} + +function generateObjectSignature({ type, raw }: DocgenFlowType): PropType { + if (!isNil(raw)) { + return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(type, raw); + } + + return createSummaryValue(type); +} + +function generateSignature(flowType: DocgenFlowType): PropType { + const { type } = flowType; + + return type === 'object' ? generateObjectSignature(flowType) : generateFuncSignature(flowType); +} + +function generateDefault({ name, raw }: DocgenFlowType): PropType { + if (!isNil(raw)) { + return !isTooLongForTypeSummary(raw) ? createSummaryValue(raw) : createSummaryValue(name, raw); + } + + return createSummaryValue(name); +} + +export function createType(type: DocgenFlowType): PropType { + // A type could be null if a defaultProp has been provided without a type definition. + if (isNil(type)) { + return null; + } + + switch (type.name) { + case FlowTypesType.UNION: + return generateUnion(type as DocgenFlowUnionType); + case FlowTypesType.SIGNATURE: + return generateSignature(type); + default: + return generateDefault(type); + } +} diff --git a/addons/docs/src/lib/docgen/index.ts b/addons/docs/src/lib/docgen/index.ts new file mode 100644 index 000000000000..4116325caa86 --- /dev/null +++ b/addons/docs/src/lib/docgen/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './utils'; +export * from './extractDocgenProps'; diff --git a/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts b/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts new file mode 100644 index 000000000000..f63067c49b3a --- /dev/null +++ b/addons/docs/src/lib/docgen/typeScript/createDefaultValue.ts @@ -0,0 +1,17 @@ +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)) { + const { value } = defaultValue; + + if (!isDefaultValueBlacklisted(value)) { + return createSummaryValue(value); + } + } + + return null; +} diff --git a/addons/docs/src/lib/docgen/typeScript/createPropDef.test.ts b/addons/docs/src/lib/docgen/typeScript/createPropDef.test.ts new file mode 100644 index 000000000000..f0536ac9fc11 --- /dev/null +++ b/addons/docs/src/lib/docgen/typeScript/createPropDef.test.ts @@ -0,0 +1,26 @@ +import { createTsPropDef } from './createPropDef'; +import { DocgenInfo } from '../types'; + +const PROP_NAME = 'propName'; + +function createDocgenInfo({ tsType, ...others }: Partial<DocgenInfo>): DocgenInfo { + return { + tsType, + required: true, + ...others, + }; +} + +describe('type', () => { + it("should remove ' | undefined' from optional props type", () => { + const docgenInfo = createDocgenInfo({ + tsType: { name: 'string | undefined' }, + required: false, + }); + + const { type } = createTsPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('string'); + expect(type.detail).toBeUndefined(); + }); +}); diff --git a/addons/docs/src/lib/docgen/typeScript/createPropDef.ts b/addons/docs/src/lib/docgen/typeScript/createPropDef.ts new file mode 100644 index 000000000000..0737004390ca --- /dev/null +++ b/addons/docs/src/lib/docgen/typeScript/createPropDef.ts @@ -0,0 +1,15 @@ +import { PropDefFactory } from '../createPropDef'; +import { createType } from './createType'; +import { createDefaultValue } from './createDefaultValue'; + +export const createTsPropDef: PropDefFactory = (propName, docgenInfo) => { + const { description, required } = docgenInfo; + + return { + name: propName, + type: createType(docgenInfo), + required, + description, + defaultValue: createDefaultValue(docgenInfo), + }; +}; diff --git a/addons/docs/src/lib/docgen/typeScript/createType.ts b/addons/docs/src/lib/docgen/typeScript/createType.ts new file mode 100644 index 000000000000..a1e1edbafef9 --- /dev/null +++ b/addons/docs/src/lib/docgen/typeScript/createType.ts @@ -0,0 +1,17 @@ +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)) { + return null; + } + + if (!required) { + return createSummaryValue(tsType.name.replace(' | undefined', '')); + } + + return createSummaryValue(tsType.name); +} diff --git a/addons/docs/src/lib/docgen/types.ts b/addons/docs/src/lib/docgen/types.ts new file mode 100644 index 000000000000..b59af2b3ec99 --- /dev/null +++ b/addons/docs/src/lib/docgen/types.ts @@ -0,0 +1,48 @@ +import { PropsTableProps } from '@storybook/components'; +import { Component } from '../../blocks/shared'; + +export type PropsExtractor = (component: Component) => PropsTableProps | null; + +export interface DocgenType { + name: string; + description?: string; + required?: boolean; +} + +export interface DocgenPropType extends DocgenType { + value?: any; + raw?: string; + computed?: boolean; +} + +export interface DocgenFlowType extends DocgenType { + type?: string; + raw?: string; + signature?: any; + elements?: any[]; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DocgenTypeScriptType extends DocgenType {} + +// export type DocgenType = DocgenPropType | DocgenFlowType | DocgenTypeScriptType; + +export interface DocgenPropDefaultValue { + value: string; +} + +export interface DocgenInfo { + type?: DocgenPropType; + flowType?: DocgenFlowType; + tsType?: DocgenTypeScriptType; + required: boolean; + description?: string; + defaultValue?: DocgenPropDefaultValue; +} + +export enum TypeSystem { + JAVASCRIPT = 'JavaScript', + FLOW = 'Flow', + TYPESCRIPT = 'TypeScript', + UNKNOWN = 'Unknown', +} diff --git a/addons/docs/src/lib/docgen/utils/defaultValue.ts b/addons/docs/src/lib/docgen/utils/defaultValue.ts new file mode 100644 index 000000000000..0757f9eef6e6 --- /dev/null +++ b/addons/docs/src/lib/docgen/utils/defaultValue.ts @@ -0,0 +1,5 @@ +const BLACKLIST = ['null', 'undefined']; + +export function isDefaultValueBlacklisted(value: string): boolean { + 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 new file mode 100644 index 000000000000..623047c7e380 --- /dev/null +++ b/addons/docs/src/lib/docgen/utils/docgenInfo.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-underscore-dangle */ + +import { isNil } from 'lodash'; +import { Component } from '../../../blocks/shared'; +import { str } from './string'; + +export function hasDocgen(component: Component): boolean { + return !!component.__docgenInfo; +} + +export function isValidDocgenSection(docgenSection: any) { + return !isNil(docgenSection) && Object.keys(docgenSection).length > 0; +} + +export function getDocgenSection(component: Component, section: string): any { + return hasDocgen(component) ? component.__docgenInfo[section] : null; +} + +export function getDocgenDescription(component: Component): string { + return hasDocgen(component) && str(component.__docgenInfo.description); +} diff --git a/addons/docs/src/lib/docgen/utils/index.ts b/addons/docs/src/lib/docgen/utils/index.ts new file mode 100644 index 000000000000..d62a105a1828 --- /dev/null +++ b/addons/docs/src/lib/docgen/utils/index.ts @@ -0,0 +1,3 @@ +export * from './defaultValue'; +export * from './string'; +export * from './docgenInfo'; diff --git a/addons/docs/src/lib/docgen/utils/string.ts b/addons/docs/src/lib/docgen/utils/string.ts new file mode 100644 index 000000000000..9accdd8d0639 --- /dev/null +++ b/addons/docs/src/lib/docgen/utils/string.ts @@ -0,0 +1,9 @@ +export const str = (obj: any) => { + if (!obj) { + return ''; + } + if (typeof obj === 'string') { + return obj as string; + } + throw new Error(`Description: expected string, got: ${JSON.stringify(obj)}`); +}; diff --git a/addons/docs/src/lib/docgenUtils.ts b/addons/docs/src/lib/docgenUtils.ts deleted file mode 100644 index 447ade46e9d0..000000000000 --- a/addons/docs/src/lib/docgenUtils.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { PropDef, PropsTableProps } from '@storybook/components'; -import { Component } from '../blocks/shared'; -import { getTypeSystemHandler, getPropTypeSystem } from './type-system-handlers'; - -export type PropsExtractor = (component: Component) => PropsTableProps | null; - -export type PropDefGetter = (component: Component, section: string) => PropDef[]; - -export const str = (o: any) => { - if (!o) { - return ''; - } - if (typeof o === 'string') { - return o as string; - } - throw new Error(`Description: expected string, got: ${JSON.stringify(o)}`); -}; - -export const hasDocgen = (component: Component) => !!component.__docgenInfo; - -export const hasDocgenSection = (component: Component, section: string) => - component && - component.__docgenInfo && - component.__docgenInfo[section] && - Object.keys(component.__docgenInfo[section]).length > 0; - -export const extractPropsFromDocgen: PropDefGetter = (component, section) => { - if (!hasDocgenSection(component, section)) { - return []; - } - - const props: Record<string, PropDef> = {}; - const docgenInfoProps = component.__docgenInfo[section]; - const propKeys = Object.keys(docgenInfoProps); - - // Assuming the props for a given component will all have the same type system. - const typeSystem = getPropTypeSystem(docgenInfoProps[propKeys[0]]); - const typeSystemHandler = getTypeSystemHandler(typeSystem); - - propKeys.forEach(propKey => { - const docgenInfoProp = docgenInfoProps[propKey]; - - const result = typeSystemHandler(propKey, docgenInfoProp); - - if (!result.ignore) { - props[propKey] = result.propDef; - } - }); - - return Object.values(props); -}; - -export const extractComponentDescription = (component?: Component) => - component && component.__docgenInfo && str(component.__docgenInfo.description); diff --git a/addons/docs/src/lib/index.ts b/addons/docs/src/lib/index.ts new file mode 100644 index 000000000000..04bca77e0dec --- /dev/null +++ b/addons/docs/src/lib/index.ts @@ -0,0 +1 @@ +export * from './utils'; diff --git a/addons/docs/src/lib/jsdoc-parser.ts b/addons/docs/src/lib/jsdoc-parser.ts deleted file mode 100644 index 577cc763087b..000000000000 --- a/addons/docs/src/lib/jsdoc-parser.ts +++ /dev/null @@ -1,187 +0,0 @@ -import doctrine, { Annotation } from 'doctrine'; -import { isNil } from 'lodash'; -import { DocgenInfo } from './DocgenInfo'; - -export type ParseJsDoc = (docgenInfo: DocgenInfo) => JsDocParsingResult; - -export interface JsDocParsingResult { - ignore: boolean; - description?: string; - extractedTags?: ExtractedJsDocTags; -} - -export interface ExtractedJsDocParamTag { - name: string; - type?: doctrine.Type; - description?: string; - raw: doctrine.Tag; - getPrettyName: () => string; - getTypeName: () => string; -} - -export interface ExtractedJsDocReturnsTag { - type?: doctrine.Type; - description?: string; - raw: doctrine.Tag; - getTypeName: () => string; -} - -export interface ExtractedJsDocTags { - params?: ExtractedJsDocParamTag[]; - returns?: ExtractedJsDocReturnsTag; - ignore: boolean; -} - -function parse(content: string): Annotation { - let ast; - - try { - ast = doctrine.parse(content, { - tags: ['param', 'arg', 'argument', 'returns', 'ignore'], - sloppy: true, - }); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - - throw new Error('Cannot parse JSDoc tags.'); - } - - return ast; -} - -export const parseJsDoc: ParseJsDoc = (docgenInfo: DocgenInfo) => { - const jsDocAst = parse(docgenInfo.description); - const extractedTags = extractJsDocTags(jsDocAst); - - if (extractedTags.ignore) { - // There is no point in doing other stuff since this prop will not be rendered. - return { - ignore: true, - }; - } - - return { - ignore: false, - // Always use the parsed description to ensure JSDoc is removed from the description. - description: jsDocAst.description, - extractedTags, - }; -}; - -function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDocTags { - const extractedTags: ExtractedJsDocTags = { - params: null, - returns: null, - ignore: false, - }; - - for (let i = 0; i < ast.tags.length; i += 1) { - const tag = ast.tags[i]; - - // arg & argument are aliases for param. - if (tag.title === 'param' || tag.title === 'arg' || tag.title === 'argument') { - 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 (isNil(extractedTags.params)) { - extractedTags.params = []; - } - - extractedTags.params.push({ - name: tag.name, - type: tag.type, - description: tag.description, - raw: tag, - getPrettyName: () => { - if (paramName.includes('null')) { - // There is a few cases in which the returned param name contains "null". - // - @param {SyntheticEvent} event- Original SyntheticEvent - // - @param {SyntheticEvent} event.\n@returns {string} - return paramName.replace('-null', '').replace('.null', ''); - } - - return tag.name; - }, - getTypeName: () => { - return !isNil(tag.type) ? extractJsDocTypeName(tag.type) : null; - }, - }); - } - } else if (tag.title === 'returns') { - if (!isNil(tag.type)) { - extractedTags.returns = { - type: tag.type, - description: tag.description, - raw: tag, - getTypeName: () => { - return extractJsDocTypeName(tag.type); - }, - }; - } - } else if (tag.title === 'ignore') { - extractedTags.ignore = true; - // Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop. - break; - } - } - - return extractedTags; -} - -// FIXME: type argument should be doctrine.Type instead of any. -function extractJsDocTypeName(type: any): string { - if (type.type === 'NameExpression') { - return type.name; - } - - if (type.type === 'RecordType') { - const recordFields = type.fields.map((field: doctrine.type.FieldType) => { - if (!isNil(field.value)) { - const valueTypeName = extractJsDocTypeName(field.value); - - return `${field.key}: ${valueTypeName}`; - } - - return field.key; - }); - - return `({${recordFields.join(', ')}})`; - } - - if (type.type === 'UnionType') { - const unionElements = type.elements.map(extractJsDocTypeName); - - return `(${unionElements.join('|')})`; - } - - // Only support untyped array: []. Might add more support later if required. - if (type.type === 'ArrayType') { - return '[]'; - } - - if (type.type === 'TypeApplication') { - if (!isNil(type.expression)) { - if (type.expression.name === 'Array') { - const arrayType = extractJsDocTypeName(type.applications[0]); - - return `${arrayType}[]`; - } - } - } - - if ( - type.type === 'NullableType' || - type.type === 'NonNullableType' || - type.type === 'OptionalType' - ) { - return extractJsDocTypeName(type.expression); - } - - if (type.type === 'AllLiteral') { - return 'any'; - } - - return null; -} diff --git a/addons/docs/src/lib/jsdocParser.test.ts b/addons/docs/src/lib/jsdocParser.test.ts new file mode 100644 index 000000000000..eb2b539d3bb3 --- /dev/null +++ b/addons/docs/src/lib/jsdocParser.test.ts @@ -0,0 +1,344 @@ +import { parseJsDoc } from './jsdocParser'; + +describe('parseJsDoc', () => { + it('should set includesJsDoc to false when the value is null', () => { + const { includesJsDoc, description, extractedTags } = parseJsDoc(null); + + expect(includesJsDoc).toBeFalsy(); + expect(description).toBeUndefined(); + expect(extractedTags).toBeUndefined(); + }); + + it('should set includesJsDocto to false when the value dont contains JSDoc', () => { + const { includesJsDoc, description, extractedTags } = parseJsDoc('Hey!'); + + expect(includesJsDoc).toBeFalsy(); + expect(description).toBeUndefined(); + expect(extractedTags).toBeUndefined(); + }); + + it('should set includesJsDoc to true when the value contains JSDoc', () => { + const { includesJsDoc } = parseJsDoc('Hey!\n@version 1.2'); + + expect(includesJsDoc).toBeTruthy(); + }); + + it('should remove all JSDoc tags from the description', () => { + const { description } = parseJsDoc('Hey!\n@version 1.2\n@deprecated'); + + expect(description).toBe('Hey!'); + }); + + describe('@ignore', () => { + it('should set ignore to true when @ignore is present', () => { + const { ignore, description, extractedTags } = parseJsDoc('Hey!\n@ignore'); + + expect(ignore).toBeTruthy(); + expect(description).toBeUndefined(); + expect(extractedTags).toBeUndefined(); + }); + + it('should set ignore to false when @ignore is not present', () => { + const { ignore } = parseJsDoc('Hey!\n@version 1.2'); + + expect(ignore).toBeFalsy(); + }); + }); + + describe('@param', () => { + it('should ignore invalid @param tags', () => { + const { extractedTags } = parseJsDoc('@param'); + + expect(extractedTags.params).toBeNull(); + }); + + it('should return a @param with a name', () => { + const { extractedTags } = parseJsDoc('@param event'); + + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].type).toBeNull(); + expect(extractedTags.params[0].description).toBeNull(); + }); + + it('should return a @param with a name and a type', () => { + const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event'); + + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].type).not.toBeNull(); + expect(extractedTags.params[0].type.name).toBe('SyntheticEvent'); + expect(extractedTags.params[0].description).toBeNull(); + }); + + it('should return a @param with a name, a type and a description', () => { + const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event'); + + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].type).not.toBeNull(); + expect(extractedTags.params[0].type.name).toBe('SyntheticEvent'); + expect(extractedTags.params[0].description).toBe('React event'); + }); + + it('should support multiple @param tags', () => { + const { extractedTags } = parseJsDoc( + '@param {SyntheticEvent} event1 - React event\n@param {SyntheticEvent} event2 - React event\n@param {SyntheticEvent} event3 - React event' + ); + + ['event1', 'event2', 'event3'].forEach((x, i) => { + expect(extractedTags.params[i].name).toBe(x); + expect(extractedTags.params[i].type).not.toBeNull(); + expect(extractedTags.params[i].type.name).toBe('SyntheticEvent'); + expect(extractedTags.params[i].description).toBe('React event'); + }); + }); + + it('should not return extra @param', () => { + const { extractedTags } = parseJsDoc('@param event'); + + expect(Object.keys(extractedTags.params).length).toBe(1); + }); + + it('should support multiline description when there is a @param', () => { + const { description, extractedTags } = parseJsDoc( + 'This is a\nmultiline description\n@param event' + ); + + expect(description).toBe('This is a\nmultiline description'); + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + }); + + it('should support multiline @param description', () => { + const { extractedTags } = parseJsDoc( + '@param event - This is a\nmultiline description\n@param anotherEvent' + ); + + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].description).toBe('This is a\nmultiline description'); + expect(extractedTags.params[1].name).toBe('anotherEvent'); + }); + + ['@arg', '@argument'].forEach(x => { + it(`should support ${x} alias`, () => { + const { extractedTags } = parseJsDoc(`${x} {SyntheticEvent} event - React event`); + + expect(extractedTags.params).not.toBeNull(); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].type).not.toBeNull(); + expect(extractedTags.params[0].type.name).toBe('SyntheticEvent'); + expect(extractedTags.params[0].description).toBe('React event'); + }); + }); + + describe('getTypeName', () => { + it('should support record type with a single field', () => { + const { extractedTags } = parseJsDoc('@param {{a: number}} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('({a: number})'); + }); + + it('should support record type with multiple fields', () => { + const { extractedTags } = parseJsDoc('@param {{a: number, b: string}} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('({a: number, b: string})'); + }); + + it('should support record type with a field having only a name', () => { + const { extractedTags } = parseJsDoc('@param {{a}} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('({a})'); + }); + + it('should support union type', () => { + const { extractedTags } = parseJsDoc('@param {(number|boolean)} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('(number|boolean)'); + }); + + it('should support array type', () => { + const { extractedTags } = parseJsDoc('@param {number[]} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('number[]'); + }); + + it('should support untyped array type', () => { + const { extractedTags } = parseJsDoc('@param {[]} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('[]'); + }); + + it('should support nullable type', () => { + const { extractedTags } = parseJsDoc('@param {?number} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('number'); + }); + + it('should support non nullable type', () => { + const { extractedTags } = parseJsDoc('@param {!number} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('number'); + }); + + it('should support optional param with []', () => { + const { extractedTags } = parseJsDoc('@param {number} [event]'); + + expect(extractedTags.params[0].getTypeName()).toBe('number'); + }); + + it('should support optional param with =', () => { + const { extractedTags } = parseJsDoc('@param {number=} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('number'); + }); + + it('should support any type', () => { + const { extractedTags } = parseJsDoc('@param {*} event'); + + expect(extractedTags.params[0].getTypeName()).toBe('any'); + }); + }); + + describe('getPrettyName', () => { + it('should return @param name', () => { + const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event'); + + expect(extractedTags.params[0].getPrettyName()).toBe('event'); + }); + + it('should fix missing space between the @param name and the description separator', () => { + const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event- React event'); + + expect(extractedTags.params[0].getPrettyName()).toBe('event'); + }); + + it('should fix @param name ending with . followed by a @returns tag', () => { + const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event.\n'); + + expect(extractedTags.params[0].getPrettyName()).toBe('event'); + }); + }); + }); + + describe('@returns', () => { + it('should ignore invalid @returns', () => { + const { extractedTags } = parseJsDoc('@returns'); + + expect(extractedTags.returns).toBeNull(); + }); + + it('should return a @returns with a type', () => { + const { extractedTags } = parseJsDoc('@returns {string}'); + + expect(extractedTags.returns).not.toBeNull(); + expect(extractedTags.returns.type).not.toBeNull(); + expect(extractedTags.returns.type.name).toBe('string'); + }); + + it('should return a @returns with a type and a description', () => { + const { extractedTags } = parseJsDoc('@returns {string} - A bar description'); + + expect(extractedTags.returns).not.toBeNull(); + expect(extractedTags.returns.type).not.toBeNull(); + expect(extractedTags.returns.type.name).toBe('string'); + expect(extractedTags.returns.description).toBe('A bar description'); + }); + + it('should support multiline @returns description', () => { + const { extractedTags } = parseJsDoc( + '@returns {string} - This is\na multiline\ndescription\n' + ); + + expect(extractedTags.returns).not.toBeNull(); + expect(extractedTags.returns.type).not.toBeNull(); + expect(extractedTags.returns.type.name).toBe('string'); + expect(extractedTags.returns.description).toBe('This is\na multiline\ndescription'); + }); + + it('should only consider the last @returns tag when there is multiple', () => { + const { extractedTags } = parseJsDoc('@returns {string}\n@returns {number}'); + + expect(extractedTags.returns).not.toBeNull(); + expect(extractedTags.returns.type).not.toBeNull(); + expect(extractedTags.returns.type.name).toBe('number'); + }); + + describe('getTypeName', () => { + it('should support named type', () => { + const { extractedTags } = parseJsDoc('@returns {string}'); + + expect(extractedTags.returns.getTypeName()).toBe('string'); + }); + + it('should support record type with a single field', () => { + const { extractedTags } = parseJsDoc('@returns {{a: number}}'); + + expect(extractedTags.returns.getTypeName()).toBe('({a: number})'); + }); + + it('should support record type with multiple fields', () => { + const { extractedTags } = parseJsDoc('@returns {{a: number, b: string}}'); + + expect(extractedTags.returns.getTypeName()).toBe('({a: number, b: string})'); + }); + + it('should support record type with a field having only a name', () => { + const { extractedTags } = parseJsDoc('@returns {{a}}'); + + expect(extractedTags.returns.getTypeName()).toBe('({a})'); + }); + + it('should support array type', () => { + const { extractedTags } = parseJsDoc('@returns {integer[]}'); + + expect(extractedTags.returns.getTypeName()).toBe('integer[]'); + }); + + it('should support untyped array type', () => { + const { extractedTags } = parseJsDoc('@returns {[]}'); + + expect(extractedTags.returns.getTypeName()).toBe('[]'); + }); + + it('should support union type', () => { + const { extractedTags } = parseJsDoc('@returns {(number|boolean)}'); + + expect(extractedTags.returns.getTypeName()).toBe('(number|boolean)'); + }); + + it('should support any type', () => { + const { extractedTags } = parseJsDoc('@returns {*}'); + + expect(extractedTags.returns.getTypeName()).toBe('any'); + }); + + it('should support void', () => { + const { extractedTags } = parseJsDoc('@returns {void}'); + + expect(extractedTags.returns.getTypeName()).toBe('void'); + }); + }); + }); + + it('should ignore unsupported JSDoc tags', () => { + const { extractedTags } = parseJsDoc('Hey!\n@param event', { tags: [] }); + + expect(extractedTags.params).toBeNull(); + }); + + it('should remove extra newline characters between tags', () => { + const { extractedTags } = parseJsDoc( + 'Hey!\n@param {SyntheticEvent} event - Original event.\n \n \n \n@returns {string}' + ); + + expect(extractedTags.params).not.toBeNull(); + expect(Object.keys(extractedTags.params).length).toBe(1); + expect(extractedTags.params[0].name).toBe('event'); + expect(extractedTags.params[0].type.name).toBe('SyntheticEvent'); + expect(extractedTags.params[0].description).toBe('Original event.'); + expect(extractedTags.returns).not.toBeNull(); + expect(extractedTags.returns.type.name).toBe('string'); + }); +}); diff --git a/addons/docs/src/lib/jsdocParser.ts b/addons/docs/src/lib/jsdocParser.ts new file mode 100644 index 000000000000..d489baf76d23 --- /dev/null +++ b/addons/docs/src/lib/jsdocParser.ts @@ -0,0 +1,234 @@ +import doctrine, { Annotation } from 'doctrine'; +import { isNil } from 'lodash'; + +export interface ExtractedJsDocParam { + name: string; + type?: any; + description?: string; + getPrettyName: () => string; + getTypeName: () => string; +} + +export interface ExtractedJsDocReturns { + type?: any; + description?: string; + getTypeName: () => string; +} + +export interface ExtractedJsDoc { + params?: ExtractedJsDocParam[]; + returns?: ExtractedJsDocReturns; + ignore: boolean; +} + +export interface JsDocParsingOptions { + tags?: string[]; +} + +export interface JsDocParsingResult { + includesJsDoc: boolean; + ignore: boolean; + description?: string; + extractedTags?: ExtractedJsDoc; +} + +export type ParseJsDoc = (value?: string, options?: JsDocParsingOptions) => JsDocParsingResult; + +function containsJsDoc(value?: string): boolean { + return !isNil(value) && value.includes('@'); +} + +function parse(content: string, tags: string[]): Annotation { + let ast; + + try { + ast = doctrine.parse(content, { + tags, + sloppy: true, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + + throw new Error('Cannot parse JSDoc tags.'); + } + + return ast; +} + +const DEFAULT_OPTIONS = { + tags: ['param', 'arg', 'argument', 'returns', 'ignore'], +}; + +export const parseJsDoc: ParseJsDoc = ( + value?: string, + options: JsDocParsingOptions = DEFAULT_OPTIONS +) => { + if (!containsJsDoc(value)) { + return { + includesJsDoc: false, + ignore: false, + }; + } + + const jsDocAst = parse(value, options.tags); + const extractedTags = extractJsDocTags(jsDocAst); + + if (extractedTags.ignore) { + // There is no point in doing other stuff since this prop will not be rendered. + return { + includesJsDoc: true, + ignore: true, + }; + } + + return { + includesJsDoc: true, + ignore: false, + // Always use the parsed description to ensure JSDoc is removed from the description. + description: jsDocAst.description, + extractedTags, + }; +}; + +function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDoc { + const extractedTags: ExtractedJsDoc = { + params: null, + returns: null, + ignore: false, + }; + + for (let i = 0; i < ast.tags.length; i += 1) { + const tag = ast.tags[i]; + + if (tag.title === 'ignore') { + extractedTags.ignore = true; + // Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop. + break; + } else { + switch (tag.title) { + // arg & argument are aliases for param. + case 'param': + case 'arg': + case 'argument': { + const paramTag = extractParam(tag); + if (!isNil(paramTag)) { + if (isNil(extractedTags.params)) { + extractedTags.params = []; + } + extractedTags.params.push(paramTag); + } + break; + } + case 'returns': { + const returnsTag = extractReturns(tag); + if (!isNil(returnsTag)) { + extractedTags.returns = returnsTag; + } + break; + } + default: + break; + } + } + } + + return extractedTags; +} + +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') { + return { + name: tag.name, + type: tag.type, + description: tag.description, + getPrettyName: () => { + if (paramName.includes('null')) { + // There is a few cases in which the returned param name contains "null". + // - @param {SyntheticEvent} event- Original SyntheticEvent + // - @param {SyntheticEvent} event.\n@returns {string} + return paramName.replace('-null', '').replace('.null', ''); + } + + return tag.name; + }, + getTypeName: () => { + return !isNil(tag.type) ? extractTypeName(tag.type) : null; + }, + }; + } + + return null; +} + +function extractReturns(tag: doctrine.Tag): ExtractedJsDocReturns { + if (!isNil(tag.type)) { + return { + type: tag.type, + description: tag.description, + getTypeName: () => { + return extractTypeName(tag.type); + }, + }; + } + + return null; +} + +function extractTypeName(type: doctrine.Type): string { + if (type.type === 'NameExpression') { + return type.name; + } + + if (type.type === 'RecordType') { + const recordFields = type.fields.map((field: doctrine.type.FieldType) => { + if (!isNil(field.value)) { + const valueTypeName = extractTypeName(field.value); + + return `${field.key}: ${valueTypeName}`; + } + + return field.key; + }); + + return `({${recordFields.join(', ')}})`; + } + + if (type.type === 'UnionType') { + const unionElements = type.elements.map(extractTypeName); + + return `(${unionElements.join('|')})`; + } + + // Only support untyped array: []. Might add more support later if required. + if (type.type === 'ArrayType') { + return '[]'; + } + + if (type.type === 'TypeApplication') { + if (!isNil(type.expression)) { + if ((type.expression as doctrine.type.NameExpression).name === 'Array') { + const arrayType = extractTypeName(type.applications[0]); + + return `${arrayType}[]`; + } + } + } + + if ( + type.type === 'NullableType' || + type.type === 'NonNullableType' || + type.type === 'OptionalType' + ) { + return extractTypeName(type.expression); + } + + if (type.type === 'AllLiteral') { + return 'any'; + } + + return null; +} diff --git a/addons/docs/src/lib/type-system-handlers.test.ts b/addons/docs/src/lib/type-system-handlers.test.ts deleted file mode 100644 index 4c02c60d41db..000000000000 --- a/addons/docs/src/lib/type-system-handlers.test.ts +++ /dev/null @@ -1,903 +0,0 @@ -import { propTypesHandler, tsHandler, flowHandler, unknownHandler } from './type-system-handlers'; -import { DocgenInfo } from './DocgenInfo'; - -const DEFAULT_PROP_NAME = 'propName'; - -const PROP_TYPES_STRING_TYPE = { - name: 'string', -}; - -const PROP_TYPES_FUNC_TYPE = { - name: 'func', -}; - -const TS_FUNC_TYPE = { - name: '() => void', -}; - -const TS_STRING_TYPE = { - name: 'string', -}; - -function createDocgenInfo(overrides: Record<string, any> = {}): DocgenInfo { - return { - type: null, - required: true, - description: 'A string prop', - defaultValue: { - value: 'default string', - }, - ...overrides, - }; -} - -describe('prop-types handler', () => { - it('should map defaults docgen info properly', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.name).toBe(DEFAULT_PROP_NAME); - expect(propDef.type.name).toBe(docgenInfo.type.name); - expect(propDef.description).toBe(docgenInfo.description); - expect(propDef.required).toBe(docgenInfo.required); - expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value); - }); - - describe('for all prop types', () => { - it('should handle prop without a description', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: undefined, - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBeUndefined(); - }); - - it('should clean the description', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick description\n@param {SyntheticEvent} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - }); - - it('should have an empty description when the description only contains JSDoc', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: '@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe(''); - }); - - it('should not remove newline characters of multilines description without JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick description\nis a\nmulti-lines\ndescription', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); - }); - - it('should not remove newline characters of multilines description with JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); - }); - - it('should not remove markdown from description without JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick *emphasis*, **strong**, `formatted` description.', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); - }); - - it('should not remove markdown from description with JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); - }); - - it('should not remove @ characters that does not match JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick @description@', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick @description@'); - }); - - it('should not set ignore to true when the property is not marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick description', - }); - - const { ignore } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeFalsy(); - }); - - it('should set ignore to true when the property is marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_STRING_TYPE, - description: 'onClick description\n@ignore', - }); - - const { ignore } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeTruthy(); - }); - }); - - describe('when the prop is a function', () => { - it("should have func as type when the props doesn't have a description", () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: undefined, - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBeUndefined(); - expect(propDef.type.name).toBe('func'); - }); - - it('should have func as type when the prop have a description without JSDoc', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have an empty description when the description only contains JSDoc', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: '@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe(''); - }); - - describe('when the description contains a @param tag', () => { - it('should have func as type when it is an invalid @param tag', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a func signature with a single arg as type when it is a @param tag with a name', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a func signature with a single arg as type when it is a @param tag with a name and a type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {SyntheticEvent} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a func signature with a single arg as type when it is a @param tag with a name, a type and a desc', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original SyntheticEvent', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have func as type when it is @param tag without a name 1', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param - Original SyntheticEvent', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have func as type when it is @param tag without a name 2', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {SyntheticEvent} - Original SyntheticEvent', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of record type with a single field', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {{a: number}} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: ({a: number}))'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of record type with multiple fields', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {{a: number, b: string}} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: ({a: number, b: string}))'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of record type with a field having only a name', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {{a}} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: ({a}))'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of union type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {(number|boolean)} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: (number|boolean))'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of array type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {number[]} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: number[])'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of untyped array type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {[]} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: [])'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param with a nullable type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {?number} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: number)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param with a non nullable type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {!number} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: number)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support optional param 1', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {number} [event]', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: number)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support optional param 2', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {number=} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: number)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support param of type any', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {*} event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: any)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support multilines description when there is a @param', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\nis a\nmulti-lines\ndescription\n@param event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event)'); - expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); - }); - - it('should support multilines @param description', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param event - This is my param\nmultiline description\n@param customData', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event, customData)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should autofix missing space between the param name and the description separator', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event- Original SyntheticEvent', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should autofix param name ending with . followed by a @returns tag', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param {SyntheticEvent} event.\n', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should provide raw @param tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.params).toBeDefined(); - expect(propDef.jsDocTags.params[0].name).toBe('event'); - expect(propDef.jsDocTags.params[0].description).toBe('Original event.'); - expect(propDef.jsDocTags.params[1].name).toBe('value'); - expect(propDef.jsDocTags.params[1].description).toBeNull(); - }); - }); - - describe('when the description contains multiple @param tags', () => { - it('should have a func signature with multiple args as type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event\n@param {string} customData', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should ignore invalid @param tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event\n@param {string} customData\n@param {SyntheticEvent} - Original event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string)'); - expect(propDef.description).toBe('onClick description'); - }); - }); - - it('should support @arg alias', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@arg event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support @argument alias', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@argument event', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event)'); - expect(propDef.description).toBe('onClick description'); - }); - - describe('when the description contains a @returns tag', () => { - it('should have func as type when it is an invalid @returns tag', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a func signature with a return type as type when it is a @returns tag with a type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {string}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => string'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a func signature with a return type as type when it is a @returns tag with a type and a description', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {string} - A custom return type', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => string'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have func as type when it is a @returns tag without a type and there is no params.', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns - A custom return type', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('func'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have no return type when it is a @returns tag without a type and there is params.', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@param event\n@returns - A custom return type', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a full signature as type when there is a @param and a @returns tag 1', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent) => string'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should have a full signature as type when there is a @param and a @returns tag 2', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} customData\n@returns {string}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent, customData: string) => string'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should only consider the last @returns tag when there is more than one', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {string}\n@returns {integer}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => integer'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support returns of record type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {{a: number, b: string}}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => ({a: number, b: string})'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support returns of array type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {integer[]}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => integer[]'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support returns of union type', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {(number|boolean)}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => (number|boolean)'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support returns of type any', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {*}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => any'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should support returns of type void', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: 'onClick description\n@returns {void}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('() => void'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should provide raw @returns tags when a description is defined', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string} - An awesome string.', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.returns).toBeDefined(); - expect(propDef.jsDocTags.returns.description).toBe('An awesome string.'); - }); - - it('should provide raw @returns tags when there is no description', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.returns).toBeDefined(); - expect(propDef.jsDocTags.returns.description).toBeNull(); - }); - }); - - it('should remove extra newline characters between tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n \n \n \n@returns {string}', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent) => string'); - expect(propDef.description).toBe('onClick description'); - }); - - it('should ignore unsupported JSDoc tags', () => { - const docgenInfo = createDocgenInfo({ - type: PROP_TYPES_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event\n@type {number}\n@returns {string}\n@version 2', - }); - - const { propDef } = propTypesHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.type.name).toBe('(event: SyntheticEvent) => string'); - expect(propDef.description).toBe('onClick description'); - }); - }); -}); - -describe('ts handler', () => { - it('should map defaults docgen info properly', () => { - const docgenInfo = createDocgenInfo({ - tsType: PROP_TYPES_STRING_TYPE, - }); - - const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.name).toBe(DEFAULT_PROP_NAME); - expect(propDef.type.name).toBe(docgenInfo.tsType.name); - expect(propDef.description).toBe(docgenInfo.description); - expect(propDef.required).toBe(docgenInfo.required); - expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value); - }); - - it('should provide raw @param tags', () => { - const docgenInfo = createDocgenInfo({ - tsType: TS_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@param {string} value', - }); - - const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.params).toBeDefined(); - expect(propDef.jsDocTags.params[0].name).toBe('event'); - expect(propDef.jsDocTags.params[0].description).toBe('Original event.'); - expect(propDef.jsDocTags.params[1].name).toBe('value'); - expect(propDef.jsDocTags.params[1].description).toBeNull(); - }); - - it('should provide raw @returns tags when a description is defined', () => { - const docgenInfo = createDocgenInfo({ - tsType: TS_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string} - An awesome string.', - }); - - const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.returns).toBeDefined(); - expect(propDef.jsDocTags.returns.description).toBe('An awesome string.'); - }); - - it('should provide raw @returns tags when there is no description', () => { - const docgenInfo = createDocgenInfo({ - tsType: TS_FUNC_TYPE, - description: - 'onClick description\n@param {SyntheticEvent} event - Original event.\n@returns {string}', - }); - - const { propDef } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.description).toBe('onClick description'); - expect(propDef.jsDocTags).toBeDefined(); - expect(propDef.jsDocTags.returns).toBeDefined(); - expect(propDef.jsDocTags.returns.description).toBeNull(); - }); - - it('should not set ignore to true when the property is not marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - tsType: TS_STRING_TYPE, - description: 'onClick description', - }); - - const { ignore } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeFalsy(); - }); - - it('should set ignore to true when the property is marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - tsType: TS_STRING_TYPE, - description: 'onClick description\n@ignore', - }); - - const { ignore } = tsHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeTruthy(); - }); -}); - -describe('flow handler', () => { - it('should map defaults docgen info properly', () => { - const docgenInfo = createDocgenInfo({ - flowType: { - name: 'string', - }, - }); - - const { propDef } = flowHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.name).toBe(DEFAULT_PROP_NAME); - expect(propDef.type.name).toBe(docgenInfo.flowType.name); - expect(propDef.description).toBe(docgenInfo.description); - expect(propDef.required).toBe(docgenInfo.required); - expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value); - }); - - it('should not set ignore to true when the property is not marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - flowType: { - name: 'string', - }, - description: 'onClick description', - }); - - const { ignore } = flowHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeFalsy(); - }); - - it('should set ignore to true when the property is marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - flowType: { - name: 'string', - }, - description: 'onClick description\n@ignore', - }); - - const { ignore } = flowHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeTruthy(); - }); -}); - -describe('unknown handler', () => { - it('should map defaults docgen info properly', () => { - const docgenInfo = createDocgenInfo(); - - const { propDef } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(propDef.name).toBe(DEFAULT_PROP_NAME); - expect(propDef.type.name).toBe('unknown'); - expect(propDef.description).toBe(docgenInfo.description); - expect(propDef.required).toBe(docgenInfo.required); - expect(propDef.defaultValue).toBe(docgenInfo.defaultValue.value); - }); - - it('should not set ignore to true when the property is not marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - description: 'onClick description', - }); - - const { ignore } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo); - - expect(ignore).toBeFalsy(); - }); - - it('should set ignore to true when the property is marked with @ignore', () => { - const docgenInfo = createDocgenInfo({ - description: 'onClick description\n@ignore', - }); - - const { ignore } = unknownHandler(DEFAULT_PROP_NAME, docgenInfo); - expect(ignore).toBeTruthy(); - }); -}); diff --git a/addons/docs/src/lib/type-system-handlers.ts b/addons/docs/src/lib/type-system-handlers.ts deleted file mode 100644 index 71d8fe95c0d2..000000000000 --- a/addons/docs/src/lib/type-system-handlers.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { PropDef } from '@storybook/components'; -import { isNil } from 'lodash'; -import { parseJsDoc, ExtractedJsDocTags, ExtractedJsDocParamTag } from './jsdoc-parser'; -import { DocgenInfo } from './DocgenInfo'; - -export const TypeSystem = { - Flow: 'Flow', - TypeScript: 'TypeScript', - PropTypes: 'PropTypes', - Unknown: 'Unknown', -}; - -export interface TypeSystemHandlerResult { - propDef?: PropDef; - ignore: boolean; -} - -export type TypeSystemHandler = ( - propName: string, - docgenInfo: DocgenInfo -) => TypeSystemHandlerResult; - -interface HandlePropResult extends TypeSystemHandlerResult { - extractedJsDocTags?: ExtractedJsDocTags; -} - -function createDefaultPropDef( - propName: string, - propType: { - name: string; - }, - docgenInfo: DocgenInfo -): PropDef { - const { description, required, defaultValue } = docgenInfo; - - return { - name: propName, - type: propType, - required, - description, - defaultValue: isNil(defaultValue) ? null : defaultValue.value, - }; -} - -function propMightContainsJsDoc(docgenInfo: DocgenInfo): boolean { - return !isNil(docgenInfo.description) && docgenInfo.description.includes('@'); -} - -function handleProp( - propName: string, - propType: { - name: string; - }, - docgenInfo: DocgenInfo -): HandlePropResult { - const propDef = createDefaultPropDef(propName, propType, docgenInfo); - - if (propMightContainsJsDoc(docgenInfo)) { - const { ignore, description, extractedTags } = parseJsDoc(docgenInfo); - - if (ignore) { - return { - ignore: true, - }; - } - - if (!isNil(description)) { - propDef.description = description; - } - - const hasParams = !isNil(extractedTags.params); - const hasReturns = !isNil(extractedTags.returns) && !isNil(extractedTags.returns.type); - - if (hasParams || hasReturns) { - propDef.jsDocTags = { - params: hasParams && extractedTags.params.map(x => x.raw), - returns: hasReturns && extractedTags.returns.raw, - }; - - return { - propDef, - extractedJsDocTags: extractedTags, - ignore: false, - }; - } - } - - return { - propDef, - ignore: false, - }; -} - -export const propTypesHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => { - const result = handleProp(propName, docgenInfo.type, docgenInfo); - - if (!result.ignore) { - const { propDef, extractedJsDocTags } = result; - - // When available use the proper JSDoc tags to describe the function signature instead of displaying "func". - if (propDef.type.name === 'func') { - if (!isNil(extractedJsDocTags)) { - const hasParams = !isNil(extractedJsDocTags.params); - const hasReturns = !isNil(extractedJsDocTags.returns); - - if (hasParams || hasReturns) { - const funcParts = []; - - if (hasParams) { - const funcParams = extractedJsDocTags.params.map((x: ExtractedJsDocParamTag) => { - const prettyName = x.getPrettyName(); - const typeName = x.getTypeName(); - - if (!isNil(typeName)) { - return `${prettyName}: ${typeName}`; - } - - return prettyName; - }); - - funcParts.push(`(${funcParams.join(', ')})`); - } else { - funcParts.push('()'); - } - - if (hasReturns) { - funcParts.push(`=> ${extractedJsDocTags.returns.getTypeName()}`); - } - - propDef.type = { - name: funcParts.join(' '), - }; - } - } - } - } - - return result; -}; - -export const tsHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => { - return handleProp(propName, docgenInfo.tsType, docgenInfo); -}; - -export const flowHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => { - return handleProp(propName, docgenInfo.flowType, docgenInfo); -}; - -export const unknownHandler: TypeSystemHandler = (propName: string, docgenInfo: DocgenInfo) => { - return handleProp(propName, { name: 'unknown' }, docgenInfo); -}; - -export const TypeSystemHandlers: Record<string, TypeSystemHandler> = { - [TypeSystem.Flow]: flowHandler, - [TypeSystem.TypeScript]: tsHandler, - [TypeSystem.PropTypes]: propTypesHandler, - [TypeSystem.Unknown]: unknownHandler, -}; - -export const getPropTypeSystem = (docgenInfo: DocgenInfo): string => { - if (!isNil(docgenInfo.flowType)) { - return TypeSystem.Flow; - } - - if (!isNil(docgenInfo.tsType)) { - return TypeSystem.TypeScript; - } - - if (!isNil(docgenInfo.type)) { - return TypeSystem.PropTypes; - } - - return TypeSystem.Unknown; -}; - -export const getTypeSystemHandler = (typeSystem: string): TypeSystemHandler => { - return TypeSystemHandlers[typeSystem]; -}; diff --git a/addons/docs/src/lib/utils.ts b/addons/docs/src/lib/utils.ts new file mode 100644 index 000000000000..4a0af2fb7a5b --- /dev/null +++ b/addons/docs/src/lib/utils.ts @@ -0,0 +1,16 @@ +import { PropSummaryValue } from '@storybook/components'; + +export const MAX_TYPE_SUMMARY_LENGTH = 90; +export const MAX_DEFALUT_VALUE_SUMMARY_LENGTH = 50; + +export function isTooLongForTypeSummary(value: string): boolean { + return value.length > MAX_TYPE_SUMMARY_LENGTH; +} + +export function isTooLongForDefaultValueSummary(value: string): boolean { + return value.length > MAX_DEFALUT_VALUE_SUMMARY_LENGTH; +} + +export function createSummaryValue(summary: string, detail?: string): PropSummaryValue { + return { summary, detail }; +} diff --git a/addons/docs/src/mdx/__snapshots__/mdx-compiler-plugin.test.js.snap b/addons/docs/src/mdx/__snapshots__/mdx-compiler-plugin.test.js.snap deleted file mode 100644 index 76cfd8a74090..000000000000 --- a/addons/docs/src/mdx/__snapshots__/mdx-compiler-plugin.test.js.snap +++ /dev/null @@ -1,849 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`docs-mdx-compiler-plugin decorators.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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\\" - decorators={[ - storyFn => ( - <div - style={{ - backgroundColor: 'yellow', - }} - > - {storyFn()} - </div> - ), - ]} - mdxType=\\"Meta\\" - /> - <h1>{\`Decorated story\`}</h1> - <Story - name=\\"one\\" - decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]} - mdxType=\\"Story\\" - > - <Button mdxType=\\"Button\\">One</Button> - </Story> - </MDXLayout> - ); -} - -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>]; - -const componentMeta = { - title: 'Button', - decorators: [ - storyFn => ( - <div - style={{ - backgroundColor: 'yellow', - }} - > - {storyFn()} - </div> - ), - ], - includeStories: ['one'], -}; - -const mdxStoryNameToId = { one: 'button--one' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin docs-only.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { 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=\\"docs-only\\" mdxType=\\"Meta\\" /> - <h1>{\`Documentation only\`}</h1> - <p> - {\`This is a documentation-only MDX file which generates a dummy \`} - <inlineCode parentName=\\"p\\">{\`docsOnly: true\`}</inlineCode> - {\` story.\`} - </p> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const __page = () => { - throw new Error('Docs-only story'); -}; - -__page.story = { parameters: { docsOnly: true } }; - -const componentMeta = { title: 'docs-only', includeStories: ['__page'] }; - -const mdxStoryNameToId = {}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin non-story-exports.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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 => - function MDXDefaultShortcode(props) { - console.warn( - 'Component ' + - name + - ' was not imported, exported, or provided by MDXProvider as global scope' - ); - return <div {...props} />; - }; - -const layoutProps = { - two, -}; -const MDXLayout = 'wrapper'; -function MDXContent({ components, ...props }) { - return ( - <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> - <Meta title=\\"Button\\" mdxType=\\"Meta\\" /> - <h1>{\`Story definition\`}</h1> - <Story name=\\"one\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">One</Button> - </Story> - - <Story name=\\"hello story\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">Hello button</Button> - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const one = () => <Button>One</Button>; -one.story = {}; -one.story.name = 'one'; -one.story.parameters = { mdxSource: '<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>' }; - -const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] }; - -const mdxStoryNameToId = { one: 'button--one', 'hello story': 'button--hello-story' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin parameters.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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\\" - component={Button} - parameters={{ - notes: 'component notes', - }} - mdxType=\\"Meta\\" - /> - <Story name=\\"component notes\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">Component notes</Button> - </Story> - <Story - name=\\"story notes\\" - parameters={{ - notes: 'story notes', - }} - mdxType=\\"Story\\" - > - <Button mdxType=\\"Button\\">Story notes</Button> - </Story> - </MDXLayout> - ); -} - -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>' }; - -export const storyNotes = () => <Button>Story notes</Button>; -storyNotes.story = {}; -storyNotes.story.name = 'story notes'; -storyNotes.story.parameters = { - mdxSource: '<Button>Story notes</Button>', - ...{ - notes: 'story notes', - }, -}; - -const componentMeta = { - title: 'Button', - parameters: { - notes: 'component notes', - }, - includeStories: ['componentNotes', 'storyNotes'], -}; - -const mdxStoryNameToId = { - 'component notes': 'button--component-notes', - 'story notes': 'button--story-notes', -}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin previews.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Button } from '@storybook/react/demo'; -import { Preview, 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\\" - component={Button} - parameters={{ - notes: 'component notes', - }} - mdxType=\\"Meta\\" - /> - <h1>{\`Preview\`}</h1> - <p>{\`Previews can contain normal components, stories, and story references\`}</p> - <Preview mdxType=\\"Preview\\"> - <Button mdxType=\\"Button\\">Just a button</Button> - <Story name=\\"hello button\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">Hello button</Button> - </Story> - <Story name=\\"two\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">Two</Button> - </Story> - <Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" /> - </Preview> - <p>{\`Preview wthout a story\`}</p> - <Preview mdxSource=\\"%0A%3CButton%3EJust%20a%20button%3C/Button%3E%0A\\" mdxType=\\"Preview\\"> - <Button mdxType=\\"Button\\">Just a button</Button> - </Preview> - </MDXLayout> - ); -} - -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>' }; - -export const two = () => <Button>Two</Button>; -two.story = {}; -two.story.name = 'two'; -two.story.parameters = { mdxSource: '<Button>Two</Button>' }; - -const componentMeta = { - title: 'Button', - parameters: { - notes: 'component notes', - }, - includeStories: ['helloButton', 'two'], -}; - -const mdxStoryNameToId = { 'hello button': 'button--hello-button', two: 'button--two' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-current.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Story } 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\\"> - <h1>{\`Current story\`}</h1> - <Story id=\\".\\" mdxType=\\"Story\\" /> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -const componentMeta = { includeStories: [] }; - -const mdxStoryNameToId = {}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-def-text-only.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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=\\"Text\\" mdxType=\\"Meta\\" /> - <h1>{\`Story definition\`}</h1> - <Story name=\\"text\\" mdxType=\\"Story\\"> - Plain text - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const text = makeStoryFn('Plain text'); -text.story = {}; -text.story.name = 'text'; -text.story.parameters = { mdxSource: \\"'Plain text'\\" }; - -const componentMeta = { title: 'Text', includeStories: ['text'] }; - -const mdxStoryNameToId = { text: 'text--text' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-definitions.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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>{\`Story definition\`}</h1> - <Story name=\\"one\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">One</Button> - </Story> - <Story name=\\"hello story\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">Hello button</Button> - </Story> - <Story name=\\"w/punctuation\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">with punctuation</Button> - </Story> - <Story name=\\"1 fine day\\" mdxType=\\"Story\\"> - <Button mdxType=\\"Button\\">starts with number</Button> - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const one = () => <Button>One</Button>; -one.story = {}; -one.story.name = 'one'; -one.story.parameters = { mdxSource: '<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>' }; - -export const wPunctuation = () => <Button>with punctuation</Button>; -wPunctuation.story = {}; -wPunctuation.story.name = 'w/punctuation'; -wPunctuation.story.parameters = { mdxSource: '<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>' }; - -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', -}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } 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 Story = makeShortcode('Story'); -const layoutProps = {}; -const MDXLayout = 'wrapper'; -function MDXContent({ components, ...props }) { - return ( - <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> - <Story name=\\"function\\" height=\\"100px\\" mdxType=\\"Story\\"> - {() => { - const btn = document.createElement('button'); - btn.innerHTML = 'Hello Button'; - btn.addEventListener('click', action('Click')); - return btn; - }} - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const functionStory = makeStoryFn(() => { - const btn = document.createElement('button'); - btn.innerHTML = 'Hello Button'; - 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}\\", -}; - -const componentMeta = { includeStories: ['functionStory'] }; - -const mdxStoryNameToId = {}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-function-var.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Meta, Story } from '@storybook/addon-docs/blocks'; -export const basicFn = () => <Button mdxType=\\"Button\\" />; -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 Button = makeShortcode('Button'); -const layoutProps = { - basicFn, -}; -const MDXLayout = 'wrapper'; -function MDXContent({ components, ...props }) { - return ( - <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> - <Meta title=\\"story-function-var\\" mdxType=\\"Meta\\" /> - - <h1>{\`Button\`}</h1> - <p>{\`I can define a story with the function defined in CSF:\`}</p> - <Story name=\\"basic\\" mdxType=\\"Story\\"> - {basicFn} - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const basic = makeStoryFn(basicFn); -basic.story = {}; -basic.story.name = 'basic'; -basic.story.parameters = { mdxSource: 'basicFn' }; - -const componentMeta = { title: 'story-function-var', includeStories: ['basic'] }; - -const mdxStoryNameToId = { basic: 'story-function-var--basic' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-object.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Story, Meta } from '@storybook/addon-docs/blocks'; -import { Welcome, Button } from '@storybook/angular/demo'; -import { linkTo } from '@storybook/addon-links'; - -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=\\"MDX|Welcome\\" mdxType=\\"Meta\\" /> - <h1>{\`Story object\`}</h1> - <Story name=\\"to storybook\\" height=\\"300px\\" mdxType=\\"Story\\"> - {{ - template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`, - props: { - showApp: linkTo('Button'), - }, - moduleMetadata: { - declarations: [Welcome], - }, - }} - </Story> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -export const toStorybook = makeStoryFn({ - template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`, - props: { - showApp: linkTo('Button'), - }, - moduleMetadata: { - 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}', -}; - -const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] }; - -const mdxStoryNameToId = { 'to storybook': 'mdx-welcome--to-storybook' }; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin story-references.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Story } 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\\"> - <h1>{\`Story reference\`}</h1> - <Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" /> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -const componentMeta = { includeStories: [] }; - -const mdxStoryNameToId = {}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; - -exports[`docs-mdx-compiler-plugin vanilla.mdx 1`] = ` -"/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; - -import { Button } from '@storybook/react/demo'; - -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\\"> - <h1>{\`Hello MDX\`}</h1> - <p>{\`This is some random content.\`}</p> - <Button mdxType=\\"Button\\">Hello button</Button> - </MDXLayout> - ); -} - -MDXContent.isMDXComponent = true; - -const componentMeta = { includeStories: [] }; - -const mdxStoryNameToId = {}; - -componentMeta.parameters = componentMeta.parameters || {}; -componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> - ), - page: MDXContent, -}; - -export default componentMeta; -" -`; diff --git a/addons/docs/src/mdx/__testfixtures__/component-id.mdx b/addons/docs/src/mdx/__testfixtures__/component-id.mdx new file mode 100644 index 000000000000..e6ca2de9dadb --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/component-id.mdx @@ -0,0 +1,8 @@ +import { Button } from '@storybook/react/demo'; +import { Story, Meta } from '@storybook/addon-docs/blocks'; + +<Meta title="Button" component={Button} id="button-id" /> + +<Story name="component notes"> + <Button>Component notes</Button> +</Story> diff --git a/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot new file mode 100644 index 000000000000..bcddea3d8a4f --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin component-id.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\\" component={Button} id=\\"button-id\\" mdxType=\\"Meta\\" /> + <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.story = {}; +componentNotes.story.name = 'component notes'; +componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' }; + +const componentMeta = { title: 'Button', id: 'button-id', 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__/decorators.output.snapshot b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot new file mode 100644 index 000000000000..efa5da26dcff --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin decorators.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\\" + decorators={[ + storyFn => ( + <div + style={{ + backgroundColor: 'yellow', + }} + > + {storyFn()} + </div> + ), + ]} + mdxType=\\"Meta\\" + /> + <h1>{\`Decorated story\`}</h1> + <Story + name=\\"one\\" + decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]} + mdxType=\\"Story\\" + > + <Button mdxType=\\"Button\\">One</Button> + </Story> + </MDXLayout> + ); +} + +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>]; + +const componentMeta = { + title: 'Button', + decorators: [ + storyFn => ( + <div + style={{ + backgroundColor: 'yellow', + }} + > + {storyFn()} + </div> + ), + ], + includeStories: ['one'], +}; + +const mdxStoryNameToKey = { one: 'one' }; + +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__/docs-only.output.snapshot b/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot new file mode 100644 index 000000000000..d4088e09fbc8 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin docs-only.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { 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=\\"docs-only\\" mdxType=\\"Meta\\" /> + <h1>{\`Documentation only\`}</h1> + <p> + {\`This is a documentation-only MDX file which generates a dummy \`} + <inlineCode parentName=\\"p\\">{\`docsOnly: true\`}</inlineCode> + {\` story.\`} + </p> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const __page = () => { + throw new Error('Docs-only story'); +}; + +__page.story = { parameters: { docsOnly: true } }; + +const componentMeta = { title: 'docs-only', includeStories: ['__page'] }; + +const mdxStoryNameToKey = {}; + +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__/meta-quotes-in-title.mdx b/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.mdx new file mode 100644 index 000000000000..83705fe69282 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.mdx @@ -0,0 +1,5 @@ +import { Meta } from '@storybook/addon-docs/blocks'; + +<Meta + title="Addons/Docs/what's in a title?" +/> \ No newline at end of file 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 new file mode 100644 index 000000000000..0179ab30d196 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/meta-quotes-in-title.output.snapshot @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin meta-quotes-in-title.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { 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=\\"Addons/Docs/what's in a title?\\" mdxType=\\"Meta\\" /> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const __page = () => { + throw new Error('Docs-only story'); +}; + +__page.story = { parameters: { docsOnly: true } }; + +const componentMeta = { title: \\"Addons/Docs/what's in a title?\\", includeStories: ['__page'] }; + +const mdxStoryNameToKey = {}; + +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__/non-story-exports.output.snapshot b/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot new file mode 100644 index 000000000000..73e2e57b8264 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin non-story-exports.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'; +export const two = 2; +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 = { + two, +}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Meta title=\\"Button\\" mdxType=\\"Meta\\" /> + <h1>{\`Story definition\`}</h1> + <Story name=\\"one\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">One</Button> + </Story> + + <Story name=\\"hello story\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Hello button</Button> + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const one = () => <Button>One</Button>; +one.story = {}; +one.story.name = 'one'; +one.story.parameters = { mdxSource: '<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>' }; + +const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] }; + +const mdxStoryNameToKey = { one: 'one', 'hello story': 'helloStory' }; + +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__/parameters.output.snapshot b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot new file mode 100644 index 000000000000..653874a33800 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin parameters.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\\" + component={Button} + parameters={{ + notes: 'component notes', + }} + mdxType=\\"Meta\\" + /> + <Story name=\\"component notes\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Component notes</Button> + </Story> + <Story + name=\\"story notes\\" + parameters={{ + notes: 'story notes', + }} + mdxType=\\"Story\\" + > + <Button mdxType=\\"Button\\">Story notes</Button> + </Story> + </MDXLayout> + ); +} + +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>' }; + +export const storyNotes = () => <Button>Story notes</Button>; +storyNotes.story = {}; +storyNotes.story.name = 'story notes'; +storyNotes.story.parameters = { + mdxSource: '<Button>Story notes</Button>', + ...{ + notes: 'story notes', + }, +}; + +const componentMeta = { + title: 'Button', + parameters: { + notes: 'component notes', + }, + includeStories: ['componentNotes', 'storyNotes'], +}; + +const mdxStoryNameToKey = { 'component notes': 'componentNotes', 'story notes': 'storyNotes' }; + +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__/previews.output.snapshot b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot new file mode 100644 index 000000000000..84c62354f846 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot @@ -0,0 +1,87 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin previews.mdx 1`] = ` +"/* @jsx mdx */ +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 => + 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\\" + component={Button} + parameters={{ + notes: 'component notes', + }} + mdxType=\\"Meta\\" + /> + <h1>{\`Preview\`}</h1> + <p>{\`Previews can contain normal components, stories, and story references\`}</p> + <Preview mdxType=\\"Preview\\"> + <Button mdxType=\\"Button\\">Just a button</Button> + <Story name=\\"hello button\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Hello button</Button> + </Story> + <Story name=\\"two\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Two</Button> + </Story> + <Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" /> + </Preview> + <p>{\`Preview wthout a story\`}</p> + <Preview mdxSource=\\"%0A%3CButton%3EJust%20a%20button%3C/Button%3E%0A\\" mdxType=\\"Preview\\"> + <Button mdxType=\\"Button\\">Just a button</Button> + </Preview> + </MDXLayout> + ); +} + +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>' }; + +export const two = () => <Button>Two</Button>; +two.story = {}; +two.story.name = 'two'; +two.story.parameters = { mdxSource: '<Button>Two</Button>' }; + +const componentMeta = { + title: 'Button', + parameters: { + notes: 'component notes', + }, + includeStories: ['helloButton', 'two'], +}; + +const mdxStoryNameToKey = { 'hello button': 'helloButton', two: 'two' }; + +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 new file mode 100644 index 000000000000..fe5b1c640d9f --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-current.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Story } 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\\"> + <h1>{\`Current story\`}</h1> + <Story id=\\".\\" mdxType=\\"Story\\" /> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +const componentMeta = { includeStories: [] }; + +const mdxStoryNameToKey = {}; + +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-def-text-only.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot new file mode 100644 index 000000000000..bf3119af433e --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-def-text-only.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=\\"Text\\" mdxType=\\"Meta\\" /> + <h1>{\`Story definition\`}</h1> + <Story name=\\"text\\" mdxType=\\"Story\\"> + Plain text + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const text = () => 'Plain text'; +text.story = {}; +text.story.name = 'text'; +text.story.parameters = { mdxSource: \\"'Plain text'\\" }; + +const componentMeta = { title: 'Text', includeStories: ['text'] }; + +const mdxStoryNameToKey = { text: 'text' }; + +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-definitions.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot new file mode 100644 index 000000000000..fe961c6a0b78 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-definitions.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>{\`Story definition\`}</h1> + <Story name=\\"one\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">One</Button> + </Story> + <Story name=\\"hello story\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">Hello button</Button> + </Story> + <Story name=\\"w/punctuation\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">with punctuation</Button> + </Story> + <Story name=\\"1 fine day\\" mdxType=\\"Story\\"> + <Button mdxType=\\"Button\\">starts with number</Button> + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const one = () => <Button>One</Button>; +one.story = {}; +one.story.name = 'one'; +one.story.parameters = { mdxSource: '<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>' }; + +export const wPunctuation = () => <Button>with punctuation</Button>; +wPunctuation.story = {}; +wPunctuation.story.name = 'w/punctuation'; +wPunctuation.story.parameters = { mdxSource: '<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>' }; + +const componentMeta = { + title: 'Button', + includeStories: ['one', 'helloStory', 'wPunctuation', '_1FineDay'], +}; + +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 mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}> + <MDXContent /> + </AddContext> + ), +}; + +export default componentMeta; +" +`; diff --git a/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot new file mode 100644 index 000000000000..e8f34c252fba --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-function-var.mdx 1`] = ` +"/* @jsx mdx */ +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 => + function MDXDefaultShortcode(props) { + console.warn( + 'Component ' + + name + + ' was not imported, exported, or provided by MDXProvider as global scope' + ); + return <div {...props} />; + }; +const Button = makeShortcode('Button'); +const layoutProps = { + basicFn, +}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Meta title=\\"story-function-var\\" mdxType=\\"Meta\\" /> + + <h1>{\`Button\`}</h1> + <p>{\`I can define a story with the function defined in CSF:\`}</p> + <Story name=\\"basic\\" mdxType=\\"Story\\"> + {basicFn} + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const basic = assertIsFn(basicFn); +basic.story = {}; +basic.story.name = 'basic'; +basic.story.parameters = { mdxSource: 'basicFn' }; + +const componentMeta = { title: 'story-function-var', includeStories: ['basic'] }; + +const mdxStoryNameToKey = { basic: 'basic' }; + +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-function.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot new file mode 100644 index 000000000000..b07cfe9ba90e --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } 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 Story = makeShortcode('Story'); +const layoutProps = {}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\"> + <Story name=\\"function\\" height=\\"100px\\" mdxType=\\"Story\\"> + {() => { + const btn = document.createElement('button'); + btn.innerHTML = 'Hello Button'; + btn.addEventListener('click', action('Click')); + return btn; + }} + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const functionStory = () => { + const btn = document.createElement('button'); + btn.innerHTML = 'Hello Button'; + 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}\\", +}; + +const componentMeta = { includeStories: ['functionStory'] }; + +const mdxStoryNameToKey = { function: 'functionStory' }; + +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 new file mode 100644 index 000000000000..052034b5ef65 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-object.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Story, Meta } from '@storybook/addon-docs/blocks'; +import { Welcome, Button } from '@storybook/angular/demo'; +import { linkTo } from '@storybook/addon-links'; + +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=\\"MDX|Welcome\\" mdxType=\\"Meta\\" /> + <h1>{\`Story object\`}</h1> + <Story name=\\"to storybook\\" height=\\"300px\\" mdxType=\\"Story\\"> + {{ + template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`, + props: { + showApp: linkTo('Button'), + }, + moduleMetadata: { + declarations: [Welcome], + }, + }} + </Story> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const toStorybook = () => ({ + template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`, + props: { + showApp: linkTo('Button'), + }, + moduleMetadata: { + 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}', +}; + +const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] }; + +const mdxStoryNameToKey = { 'to storybook': 'toStorybook' }; + +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-references.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot new file mode 100644 index 000000000000..19ef458f7217 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin story-references.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Story } 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\\"> + <h1>{\`Story reference\`}</h1> + <Story id=\\"welcome--welcome\\" mdxType=\\"Story\\" /> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +const componentMeta = { includeStories: [] }; + +const mdxStoryNameToKey = {}; + +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__/title-template-string.mdx b/addons/docs/src/mdx/__testfixtures__/title-template-string.mdx new file mode 100644 index 000000000000..3e41ab169fb5 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/title-template-string.mdx @@ -0,0 +1,4 @@ +import { Meta, Story } from '@storybook/addon-docs/blocks'; +import { titleFunction } from '../title-generators'; + +<Meta title={`${titleFunction('template')}`} /> \ No newline at end of file diff --git a/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot b/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot new file mode 100644 index 000000000000..a15b7adffd0d --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/title-template-string.output.snapshot @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin title-template-string.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Meta, Story } from '@storybook/addon-docs/blocks'; +import { titleFunction } from '../title-generators'; + +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={\`\${titleFunction('template')}\`} mdxType=\\"Meta\\" /> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +export const __page = () => { + throw new Error('Docs-only story'); +}; + +__page.story = { parameters: { docsOnly: true } }; + +const componentMeta = { title: \`\${titleFunction('template')}\`, includeStories: ['__page'] }; + +const mdxStoryNameToKey = {}; + +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__/vanilla.output.snapshot b/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot new file mode 100644 index 000000000000..49c72d1bbbc7 --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin vanilla.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Button } from '@storybook/react/demo'; + +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\\"> + <h1>{\`Hello MDX\`}</h1> + <p>{\`This is some random content.\`}</p> + <Button mdxType=\\"Button\\">Hello button</Button> + </MDXLayout> + ); +} + +MDXContent.isMDXComponent = true; + +const componentMeta = { includeStories: [] }; + +const mdxStoryNameToKey = {}; + +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/mdx-compiler-plugin.js b/addons/docs/src/mdx/mdx-compiler-plugin.js index e72c5c548ade..2e66b82446f3 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/router/utils'); // Generate the MDX as is, but append named exports for every // story in the contents @@ -55,7 +54,7 @@ function genStoryExport(ast, context) { let body = ast.children.find(n => n.type !== 'JSXText'); let storyCode = null; - let isJsx = false; + if (!body) { // plain text node const { code } = generate(ast.children[0], {}); @@ -64,21 +63,29 @@ function genStoryExport(ast, context) { if (body.type === 'JSXExpressionContainer') { // FIXME: handle fragments body = body.expression; - } else { - isJsx = true; } const { code } = generate(body, {}); storyCode = code; } - if (isJsx) { - statements.push( - `export const ${storyKey} = () => ( + + 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} - );` - ); - } else { - statements.push(`export const ${storyKey} = makeStoryFn(${storyCode});`); + )`; + break; } + + statements.push(`export const ${storyKey} = ${storyVal};`); statements.push(`${storyKey}.story = {};`); // always preserve the name, since CSF exports can get modified by displayName @@ -128,11 +135,29 @@ function genPreviewExports(ast, context) { return previewExports; } -function genMeta(ast) { +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'); - title = title && `'${title.value}'`; + if (title) { + if (title.type === 'StringLiteral') { + title = "'".concat(jsStringEscape(title.value), "'"); + } else { + try { + // generate code, so the expression is evaluated by the CSF compiler + const { code } = generate(title, {}); + // remove the curly brackets at start and end of code + title = code.replace(/^\{(.+)\}$/, '$1'); + } catch (e) { + // eat exception if title parsing didn't go well + // eslint-disable-next-line no-console + console.warn('Invalid title:', options.filepath); + title = undefined; + } + } + } + id = id && `'${id.value}'`; if (parameters && parameters.expression) { const { code: params } = generate(parameters.expression, {}); parameters = params; @@ -143,12 +168,13 @@ function genMeta(ast) { } return { title, + id, parameters, decorators, }; } -function getExports(node, counter) { +function getExports(node, counter, options) { const { value, type } = node; if (type === 'jsx') { if (STORY_REGEX.exec(value)) { @@ -165,19 +191,19 @@ function getExports(node, counter) { if (META_REGEX.exec(value)) { // Preview, possibly containing multiple stories const ast = parser.parseExpression(value, { plugins: ['jsx'] }); - return { meta: genMeta(ast) }; + return { meta: genMeta(ast, 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 = { - container: ({ context, children }) => <DocsContainer context={{...context, mdxStoryNameToId}}>{children}</DocsContainer>, - page: MDXContent, + ...(componentMeta.parameters.docs || {}), + page: () => <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}><MDXContent /></AddContext>, }; `.trim(); @@ -267,7 +293,7 @@ function extractExports(node, options) { storyNameToKey: {}, }; node.children.forEach(n => { - const exports = getExports(n, context); + const exports = getExports(n, context, options); if (exports) { const { stories, meta } = exports; if (stories) { @@ -295,23 +321,12 @@ function extractExports(node, options) { } metaExport.includeStories = JSON.stringify(includeStories); - const { title } = metaExport; - const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce( - (acc, [storyName, storyKey]) => { - if (title) { - acc[storyName] = toId(title, storyNameFromExport(storyKey)); - } - return acc; - }, - {} - ); - const fullJsx = [ - 'import { DocsContainer, makeStoryFn } from "@storybook/addon-docs/blocks";', + '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'); diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.test.js b/addons/docs/src/mdx/mdx-compiler-plugin.test.js index 81d9a4a779be..5945010318bc 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.test.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.test.js @@ -1,8 +1,9 @@ -const path = require('path'); -const fs = require('fs-extra'); -const mdx = require('@mdx-js/mdx'); -const prettier = require('prettier'); -const plugin = require('./mdx-compiler-plugin'); +import 'jest-specific-snapshot'; +import path from 'path'; +import fs from 'fs-extra'; +import mdx from '@mdx-js/mdx'; +import prettier from 'prettier'; +import plugin from './mdx-compiler-plugin'; async function generate(filePath) { const content = await fs.readFile(filePath, 'utf8'); @@ -22,28 +23,20 @@ async function generate(filePath) { }); } +const inputRegExp = /\.mdx$/; + describe('docs-mdx-compiler-plugin', () => { - const fixtures = [ - 'vanilla.mdx', - 'story-definitions.mdx', - 'story-def-text-only.mdx', - 'story-object.mdx', - 'story-references.mdx', - 'story-current.mdx', - 'previews.mdx', - 'decorators.mdx', - 'parameters.mdx', - 'non-story-exports.mdx', - 'story-function.mdx', - 'docs-only.mdx', - 'story-function-var.mdx', - ]; - fixtures.forEach(fixtureFile => { - it(fixtureFile, async () => { - const code = await generate(path.resolve(__dirname, `./__testfixtures__/${fixtureFile}`)); - expect(code).toMatchSnapshot(); + const transformFixturesDir = path.join(__dirname, '__testfixtures__'); + fs.readdirSync(transformFixturesDir) + .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); + expect(code).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.output.snapshot')); + }); }); - }); it('errors on missing story props', async () => { await expect( generate(path.resolve(__dirname, './__testfixtures__/story-missing-props.mdx')) diff --git a/addons/docs/src/mdx/title-generators.js b/addons/docs/src/mdx/title-generators.js new file mode 100644 index 000000000000..6f4c8b526ef2 --- /dev/null +++ b/addons/docs/src/mdx/title-generators.js @@ -0,0 +1 @@ +export const titleFunction = title => `Addons/Docs/${title}`; diff --git a/addons/docs/src/preset.ts b/addons/docs/src/preset.ts new file mode 100644 index 000000000000..f95fd7e214c0 --- /dev/null +++ b/addons/docs/src/preset.ts @@ -0,0 +1,57 @@ +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', + 'managerEntries', + '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 => { + try { + return require.resolve(`./frameworks/${framework}/preset`) as string; + } catch (err) { + // there is no custom config for the user's framework, do nothing + return null; + } +}); diff --git a/addons/docs/src/typings.d.ts b/addons/docs/src/typings.d.ts index 1843064a1887..dc9928da3997 100644 --- a/addons/docs/src/typings.d.ts +++ b/addons/docs/src/typings.d.ts @@ -1,5 +1,8 @@ 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"; diff --git a/addons/docs/vue/README.md b/addons/docs/vue/README.md index bb2748889b97..0f32ae341df9 100644 --- a/addons/docs/vue/README.md +++ b/addons/docs/vue/README.md @@ -4,6 +4,8 @@ # Storybook Docs for Vue +> 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! @@ -22,10 +24,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/presets.js` exports: +Then add the following to your `.storybook/main.js` addons: ```js -module.exports = ['@storybook/addon-docs/vue/preset']; +module.exports = { + addons: ['@storybook/addon-docs'], +}; ``` ## DocsPage @@ -66,16 +70,18 @@ Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you wa yarn add -D react react-is babel-loader ``` -Then update your `.storybook/config.js` to make sure you load MDX files: +Then update your `.storybook/main.js` to make sure you load MDX files: -```ts -configure(require.context('../src/stories', true, /\.stories\.(js|mdx)$/), module); +```js +module.exports = { + stories: ['../src/stories/**/*.stories.(js|mdx)'], +}; ``` Finally, you can create MDX files like this: ```md -import { Meta, Story, Props } from '@storybook/docs/blocks'; +import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; import { InfoButton } from './InfoButton.vue'; <Meta title='InfoButton' component={InfoButton} /> @@ -100,7 +106,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/config.js`: +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`: ```js import { addParameters } from '@storybook/vue'; diff --git a/addons/docs/vue/preset.js b/addons/docs/vue/preset.js index 83a11406ff3c..09cb6c49e5e5 100644 --- a/addons/docs/vue/preset.js +++ b/addons/docs/vue/preset.js @@ -1,16 +1 @@ -const commonExports = require('../dist/frameworks/common/makePreset').default('vue'); - -const webpack = (webpackConfig, options) => { - const config = commonExports.webpack(webpackConfig, options); - config.module.rules.push({ - test: /\.vue$/, - loader: 'storybook-addon-vue-info/loader', - enforce: 'post', - }); - return config; -}; - -module.exports = { - ...commonExports, - webpack, -}; +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 372bcd771cd8..9713f42e1fed 100644 --- a/addons/docs/web-components/README.md +++ b/addons/docs/web-components/README.md @@ -4,7 +4,7 @@ - Be sure to check the [installation section of the general addon-docs page](../README.md) before proceeding. - Be sure to have a [custom-elements.json](./#custom-elementsjson) file. -- Add to your `.storybook/config.js` +- Add to your `.storybook/preview.js` ```js import { setCustomElements } from '@storybook/web-components'; diff --git a/addons/essentials/README.md b/addons/essentials/README.md new file mode 100644 index 000000000000..da8d70fd0619 --- /dev/null +++ b/addons/essentials/README.md @@ -0,0 +1,51 @@ +# Storybook Essentials + +Storybook Essentials is a curated collection of addons to bring out the best of Storybook. + +Each addon is documented and maintained by the core team and will be upgraded alongside Storybook as the platform evolves. We will also do our best to maintain [framework support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) for all of the officially supported frameworks. + +## Contents + +Storybook essentials includes the following addons. Addons can be disabled and re-configured as [described below](#configuration): + +- [Backgrounds](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds) +- [Viewport](https://github.com/storybookjs/storybook/tree/next/addons/viewport) + +## Installation + +You can add Essentials to your project with: + +``` +npm install --save-dev @storybook/addon-essentials +``` + +And then add the following line to your `.storybook/main.js`: + +```js +module.exports = { + addons: ['@storybook/addon-essentials'], +}; +``` + +## Configuration + +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. + +## Disabling addons + +Yuu can disable any of Essential's addons using the following configuration scheme in `.storybook/main.js`: + +```js +module.exports = { + addons: [{ + name: '@storybook/addon-essentials', + options: { + <addon-key>: false, + } + }] +}; +``` + +Valid addon keys include: `backgrounds`, `viewport` diff --git a/addons/essentials/package.json b/addons/essentials/package.json new file mode 100644 index 000000000000..ac8be6fa9dd7 --- /dev/null +++ b/addons/essentials/package.json @@ -0,0 +1,49 @@ +{ + "name": "@storybook/addon-essentials", + "version": "6.0.0-alpha.2", + "description": "Curated addons to bring out the best of Storybook", + "keywords": [ + "addon", + "essentials", + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/addons/essentials", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "addons/essentials" + }, + "license": "MIT", + "files": [ + "dist/**/*", + "README.md" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-viewport": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/node-logger": "6.0.0-alpha.2", + "ts-dedent": "^1.1.0" + }, + "devDependencies": { + "@types/jest": "^24.0.11" + }, + "peerDependencies": { + "babel-loader": "^8.0.0", + "react": "^16.8.0", + "react-is": "^16.8.0" + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" +} diff --git a/addons/essentials/src/index.ts b/addons/essentials/src/index.ts new file mode 100644 index 000000000000..35fc43c9f168 --- /dev/null +++ b/addons/essentials/src/index.ts @@ -0,0 +1,32 @@ +import fs from 'fs'; +import { logger } from '@storybook/node-logger'; + +type PresetOptions = { + backgrounds?: any; + viewport?: any; +}; + +let packageJson: any = {}; +if (fs.existsSync('./package.json')) { + try { + packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); + } catch (err) { + logger.error(`Error reading package.json: ${err.message}`); + } +} + +const isInstalled = (addon: string) => { + const { dependencies, devDependencies } = packageJson; + return (dependencies && dependencies[addon]) || (devDependencies && devDependencies[addon]); +}; + +const makeAddon = (key: string) => `@storybook/addon-${key}`; + +export function managerEntries(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]; +} diff --git a/addons/essentials/src/typings.d.ts b/addons/essentials/src/typings.d.ts new file mode 100644 index 000000000000..8cf1e5fe3055 --- /dev/null +++ b/addons/essentials/src/typings.d.ts @@ -0,0 +1 @@ +declare module 'fs'; diff --git a/addons/essentials/tsconfig.json b/addons/essentials/tsconfig.json new file mode 100644 index 000000000000..6fe48b89c0d8 --- /dev/null +++ b/addons/essentials/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["jest"] + }, + "include": ["src/**/*"], + "exclude": ["src/**.test.ts"] +} diff --git a/addons/events/README.md b/addons/events/README.md index bc975caf06f2..bf2db1622938 100644 --- a/addons/events/README.md +++ b/addons/events/README.md @@ -12,20 +12,17 @@ This [storybook](https://storybooks.js.org) ([source](https://github.com/storybo npm i --save-dev @storybook/addon-events ``` -Then create a file called `addons.js` in your storybook config. - -Add following content to it: +within `.storybook/main.js`: ```js -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-events/register'; +module.exports = { + addons: ['@storybook/addon-events/register'] +} ``` Then write your stories like this: ```js -import { storiesOf } from '@storybook/react'; import withEvents from '@storybook/addon-events'; import EventEmiter from 'event-emiter'; @@ -35,9 +32,9 @@ import * as EVENTS from './events'; const emiter = new EventEmiter(); const emit = emiter.emit.bind(emiter); - -storiesOf('WithEvents', module) - .addDecorator( +export default { + title: 'withEvents', + decorators: [ withEvents({ emit, events: [ @@ -87,7 +84,11 @@ storiesOf('WithEvents', module) ], }, ] - }) - ) - .add('Logger', () => <Logger emiter={emiter} />); + }), + ], +} + +export const defaultView = () => ( + <Logger emiter={emiter} /> +); ``` diff --git a/addons/events/package.json b/addons/events/package.json index b4516196956f..c222a09330ce 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -20,7 +20,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -31,11 +30,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "format-json": "^1.0.3", "lodash": "^4.17.15", @@ -45,10 +44,14 @@ "react-textarea-autosize": "^7.0.4", "util-deprecate": "^1.0.2" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/events/src/components/Panel.tsx b/addons/events/src/components/Panel.tsx index 6c79a5ff2e5d..ed1ff169fde5 100644 --- a/addons/events/src/components/Panel.tsx +++ b/addons/events/src/components/Panel.tsx @@ -11,7 +11,7 @@ import { Event as EventType, OnEmitEvent } from '../index'; const Wrapper = styled.div({ width: '100%', boxSizing: 'border-box', - padding: '10px', + padding: 10, minHeight: '100%', }); diff --git a/addons/google-analytics/README.md b/addons/google-analytics/README.md index 47ad138dcbd1..3026e4c6b69f 100644 --- a/addons/google-analytics/README.md +++ b/addons/google-analytics/README.md @@ -12,14 +12,17 @@ Install: yarn add @storybook/addon-google-analytics --dev ``` -Then, add following content to `.storybook/addons.js` +within `.storybook/main.js`: ```js -import '@storybook/addon-google-analytics/register'; +module.exports = { + addons: ['@storybook/addon-google-analytics/register'] +} ``` Then, set an environment variable ``` window.STORYBOOK_GA_ID = UA-000000-01 +window.STORYBOOK_REACT_GA_OPTIONS = {} ``` diff --git a/addons/google-analytics/package.json b/addons/google-analytics/package.json index f52c7333787f..6661952f94bd 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook addon for google analytics", "keywords": [ "addon", @@ -20,13 +20,14 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react-ga": "^2.5.7" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/google-analytics/src/register.ts b/addons/google-analytics/src/register.ts index 3c381e70b7eb..297d9759de3d 100644 --- a/addons/google-analytics/src/register.ts +++ b/addons/google-analytics/src/register.ts @@ -5,11 +5,11 @@ import { STORY_CHANGED, STORY_ERRORED, STORY_MISSING } from '@storybook/core-eve import ReactGA from 'react-ga'; addons.register('storybook/google-analytics', api => { - ReactGA.initialize(window.STORYBOOK_GA_ID); + ReactGA.initialize(window.STORYBOOK_GA_ID, window.STORYBOOK_REACT_GA_OPTIONS); api.on(STORY_CHANGED, () => { - const { url } = api.getUrlState(); - ReactGA.pageview(url); + const { path } = api.getUrlState(); + ReactGA.pageview(path); }); api.on(STORY_ERRORED, ({ description }: { description: string }) => { ReactGA.exception({ diff --git a/addons/graphql/package.json b/addons/graphql/package.json index f91763dc28da..d61025b15095 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -29,8 +28,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "graphiql": "^0.16.0", @@ -42,5 +41,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/info/README.md b/addons/info/README.md deleted file mode 100644 index 4fd0017833af..000000000000 --- a/addons/info/README.md +++ /dev/null @@ -1,385 +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/config.js. -import { addDecorator } from '@storybook/react'; -import { withInfo } from '@storybook/addon-info'; - -addDecorator(withInfo); -``` - -or - -```js -storiesOf('Component', module) - .addDecorator(withInfo) // At your stories directly. - .add(...); -``` - -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 { storiesOf } from '@storybook/react'; - -import Component from './Component'; - -storiesOf('Component', module) - .addParameters({ - info: { - // Your settings - }, - }) - .add('with some emoji', () => <Component />); -``` - -...or for each story individually: - -```js -import { storiesOf } from '@storybook/react'; - -import Component from './Component'; - -storiesOf('Component', module) - .add( - 'with some emoji', - () => <Component emoji />, - { info: { inline: true, header: false } } // Make your component render inline with the additional info - ) - .add( - 'with no emoji', - () => <Component />, - { info: '☹️ no emojis' } // Add additional info text directly - ); -``` - -...or even together: - -```js -import { storiesOf } from '@storybook/react'; - -import Component from './Component'; - -storiesOf('Component', module) - .addParameters({ - info: { - // Make a default for all stories in this book, - inline: true, // where the components are inlined - styles: { - header: { - h1: { - color: 'red', // and the headers of the sections are red. - }, - }, - }, - }, - }) - .add('green version', () => <Component green />, { - info: { - styles: stylesheet => ({ - // Setting the style with a function - ...stylesheet, - header: { - ...stylesheet.header, - h1: { - ...stylesheet.header.h1, - color: 'green', // Still inlined but with green headers! - }, - }, - }), - }, - }) - .add('something else', () => <Component different />, { - info: 'This story has additional text added to the info!', // Still inlined and with red headers! - }); -``` - -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 -storiesOf('Button', module).add('Button Component', () => <Button />, { - 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/config.js`. - -```js -// config.js -import { withInfo } from '@storybook/addon-info'; - -addDecorator( - withInfo({ - header: false, // Global configuration for the info addon across all of your stories. - }) -); -``` - -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'; - -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> - ); -}; - -storiesOf('Button', module).add('with text', () => <Button>Hello Button</Button>, { - info: { - TableComponent, - }, -}); -``` - -### 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 8066a94f181b..000000000000 --- a/addons/info/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@storybook/addon-info", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", - "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" - } -} 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 8108e71d3a94..000000000000 --- a/addons/info/src/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,9976 +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": "12px", - "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": "12px", - "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": "12px", - "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;", - }, - "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, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - "scc5fi": 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" - > - <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" - > - <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;", - }, - "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, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - "scc5fi": 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" - > - <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" - > - <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;", - }, - "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, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - "scc5fi": 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" - > - <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" - > - <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;", - }, - "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, - "1imo1gr": true, - "1maezg8": true, - "1si67pu": true, - "4zr3vl": true, - "esgpkx": true, - "scc5fi": 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" - > - <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" - > - <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 051d603cae8b..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: '12px', - 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 e92c0da3166b..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: '1px', - borderStyle: 'solid', - borderColor: 'rgb(238, 238, 238)', - borderImage: 'initial', - borderRadius: '3px', - 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 c5845d4a6bc6..fa3b1f2433b8 100644 --- a/addons/jest/README.md +++ b/addons/jest/README.md @@ -69,10 +69,12 @@ You could create a `prebuild:storybook` npm script, which will never fail by app ### Register -Register addon at `.storybook/addons.js` +within `.storybook/main.js`: ```js -import '@storybook/addon-jest/register'; +module.exports = { + addons: ['@storybook/addon-jest/register'] +} ``` ## Usage @@ -85,18 +87,22 @@ In your `story.js` import results from '../.jest-test-results.json'; import { withTests } from '@storybook/addon-jest'; -storiesOf('MyComponent', module) - .addDecorator(withTests({ results })) - .add( - 'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', - () => <div>Jest results in storybook</div>, - { - jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], - } - ); +export default { + title: 'MyComponent', + decorators: [withTests({ results })], +}; + +export const defaultView = () => ( + <div>Jest results in storybook</div> +); +defaultView.story = { + parameters: { + jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], + }, +}; ``` -Or in order to avoid importing `.jest-test-results.json` in each story, add the decorator in your `.storybook/config.js` and results will display for stories that you have set the `jest` parameter on: +Or in order to avoid importing `.jest-test-results.json` in each story, add the decorator in your `.storybook/preview.js` and results will display for stories that you have set the `jest` parameter on: ```js import { addDecorator } from '@storybook/react'; // <- or your view layer @@ -114,13 +120,20 @@ addDecorator( Then in your story: ```js -storiesOf('MyComponent', module) - // Use .addParameters if you want the same tests displayed for all stories of the component - .addParameters({ jest: ['MyComponent', 'MyOtherComponent'] }) - .add( - 'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', - () => <div>Jest results in storybook</div> - ); +import React from 'react'; + +export default { + title: 'MyComponent', +}; + +export const defaultView = () => ( + <div>Jest results in storybook</div> +); +defaultView.story = { + parameters: { + jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'], + }, +}; ``` ### Disabling @@ -128,9 +141,20 @@ storiesOf('MyComponent', module) You can disable the addon for a single story by setting the `jest` parameter to `{disable: true}`: ```js -storiesOf('MyComponent', module).add('Story', () => <div>Jest results disabled here</div>, { - jest: { disable: true }, -}); +import React from 'react'; + +export default { + title: 'MyComponent', +}; + +export const defaultView = () => ( + <div>Jest results in storybook</div> +); +defaultView.story = { + parameters: { + jest: { disable: true }, + }, +}; ``` ### withTests(options) @@ -153,7 +177,7 @@ declare module '*.json' { } ``` -In your `.storybook/config.ts`: +In your `.storybook/preview.ts`: ```ts import { addDecorator } from '@storybook/angular'; @@ -169,17 +193,6 @@ addDecorator( ); ``` -Then in your story: - -```js -storiesOf('MyComponent', module) - .addParameters({ jest: ['my.component', 'my-other.component'] }) - .add( - 'This story shows test results from my.component.spec.ts and my-other.component.spec.ts', - () => <div>Jest results in storybook</div> - ); -``` - ##### Example [here](https://github.com/storybookjs/storybook/tree/master/examples/angular-cli) ## TODO diff --git a/addons/jest/package.json b/addons/jest/package.json index 41cc9024b5eb..5368a03247f7 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -24,7 +24,6 @@ "author": "Renaud Tertrais <renaud.tertrais@gmail.com> (https://github.com/renaudtertrais)", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -35,11 +34,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -47,10 +46,14 @@ "upath": "^1.1.0", "util-deprecate": "^1.0.2" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/jest/src/components/Message.tsx b/addons/jest/src/components/Message.tsx index 2382b4c3116c..b87dccac668a 100644 --- a/addons/jest/src/components/Message.tsx +++ b/addons/jest/src/components/Message.tsx @@ -22,27 +22,27 @@ class TestDetail { } const StackTrace = styled.pre<{}>(({ theme }) => ({ background: theme.color.lighter, - paddingTop: '4px', - paddingBottom: '4px', - paddingLeft: '6px', - borderRadius: '2px', + paddingTop: 4, + paddingBottom: 4, + paddingLeft: 6, + borderRadius: 2, overflow: 'auto', margin: '10px 30px 10px 30px', whiteSpace: 'pre', })); const Results = styled.div({ - paddingTop: '10px', - marginLeft: '31px', - marginRight: '30px', + paddingTop: 10, + marginLeft: 31, + marginRight: 30, }); const Description = styled.div<{}>(({ theme }) => ({ - paddingBottom: '10px', - paddingTop: '10px', + paddingBottom: 10, + paddingTop: 10, borderBottom: theme.appBorderColor, - marginLeft: '31px', - marginRight: '30px', + marginLeft: 31, + marginRight: 30, overflowWrap: 'break-word', })); diff --git a/addons/jest/src/components/Panel.tsx b/addons/jest/src/components/Panel.tsx index de8f6fe60718..e085d93d4cf6 100644 --- a/addons/jest/src/components/Panel.tsx +++ b/addons/jest/src/components/Panel.tsx @@ -26,10 +26,10 @@ const Item = styled.li({ const ProgressWrapper = styled.div({ position: 'relative', - height: '10px', - width: '30px', + height: 10, + width: 30, display: 'flex', - top: '-2px', + top: -2, }); const SuiteHead = styled.div({ @@ -37,8 +37,8 @@ const SuiteHead = styled.div({ alignItems: 'baseline', position: 'absolute', zIndex: 2, - right: '20px', - marginTop: '15px', + right: 20, + marginTop: 15, }); const SuiteTotals = styled(({ result, className, width }) => ( @@ -62,7 +62,7 @@ const SuiteTotals = styled(({ result, className, width }) => ( alignItems: 'center', color: theme.color.dark, fontSize: '14px', - marginTop: '-5px', + marginTop: -5, '& > *': { marginRight: 10, }, @@ -70,8 +70,8 @@ const SuiteTotals = styled(({ result, className, width }) => ( const SuiteProgressPortion = styled.div<{ color: any; progressPercent: number }>( ({ theme, color, progressPercent }) => ({ - height: '6px', - top: '3px', + height: 6, + top: 3, width: `${progressPercent}%`, backgroundColor: color, }) diff --git a/addons/jest/src/components/Result.tsx b/addons/jest/src/components/Result.tsx index f0eb3e9e015a..14cbe8d50c16 100644 --- a/addons/jest/src/components/Result.tsx +++ b/addons/jest/src/components/Result.tsx @@ -34,7 +34,7 @@ const Icon = styled<any, any>(Icons)(({ theme }) => ({ width: 10, minWidth: 10, color: theme.color.mediumdark, - marginRight: '10px', + marginRight: 10, transition: 'transform 0.1s ease-in-out', alignSelf: 'center', display: 'inline-flex', diff --git a/addons/jest/src/styles.js b/addons/jest/src/styles.js deleted file mode 100644 index 19a9ea6943f6..000000000000 --- a/addons/jest/src/styles.js +++ /dev/null @@ -1,573 +0,0 @@ -import { document } from 'global'; - -const styles = ` - @font-face { - font-family: octicons-link; - src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); - } - .markdown-body { - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - line-height: 1.5; - color: #333; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 16px; - line-height: 1.5; - word-wrap: break-word; - } - .markdown-body .pl-c { - color: #969896; - } - .markdown-body .pl-c1, - .markdown-body .pl-s .pl-v { - color: #0086b3; - } - .markdown-body .pl-e, - .markdown-body .pl-en { - color: #795da3; - } - .markdown-body .pl-smi, - .markdown-body .pl-s .pl-s1 { - color: #333; - } - .markdown-body .pl-ent { - color: #63a35c; - } - .markdown-body .pl-k { - color: #a71d5d; - } - .markdown-body .pl-s, - .markdown-body .pl-pds, - .markdown-body .pl-s .pl-pse .pl-s1, - .markdown-body .pl-sr, - .markdown-body .pl-sr .pl-cce, - .markdown-body .pl-sr .pl-sre, - .markdown-body .pl-sr .pl-sra { - color: #183691; - } - .markdown-body .pl-v { - color: #ed6a43; - } - .markdown-body .pl-id { - color: #b52a1d; - } - .markdown-body .pl-ii { - color: #f8f8f8; - background-color: #b52a1d; - } - .markdown-body .pl-sr .pl-cce { - font-weight: bold; - color: #63a35c; - } - .markdown-body .pl-ml { - color: #693a17; - } - .markdown-body .pl-mh, - .markdown-body .pl-mh .pl-en, - .markdown-body .pl-ms { - font-weight: bold; - color: #1d3e81; - } - .markdown-body .pl-mq { - color: #008080; - } - .markdown-body .pl-mi { - font-style: italic; - color: #333; - } - .markdown-body .pl-mb { - font-weight: bold; - color: #333; - } - .markdown-body .pl-md { - color: #bd2c00; - background-color: #ffecec; - } - .markdown-body .pl-mi1 { - color: #55a532; - background-color: #eaffea; - } - .markdown-body .pl-mdr { - font-weight: bold; - color: #795da3; - } - .markdown-body .pl-mo { - color: #1d3e81; - } - .markdown-body .octicon { - display: inline-block; - vertical-align: text-top; - fill: currentColor; - } - .markdown-body a { - background-color: transparent; - -webkit-text-decoration-skip: objects; - } - .markdown-body a:active, - .markdown-body a:hover { - outline-width: 0; - } - .markdown-body strong { - font-weight: inherit; - } - .markdown-body strong { - font-weight: bolder; - } - .markdown-body h1 { - font-size: 2em; - margin: 0.67em 0; - } - .markdown-body img { - border-style: none; - } - .markdown-body svg:not(:root) { - overflow: hidden; - } - .markdown-body code, - .markdown-body kbd, - .markdown-body pre { - font-family: monospace, monospace; - font-size: 1em; - } - .markdown-body hr { - box-sizing: content-box; - height: 0; - overflow: visible; - } - .markdown-body input { - font: inherit; - margin: 0; - } - .markdown-body input { - overflow: visible; - } - .markdown-body [type="checkbox"] { - box-sizing: border-box; - padding: 0; - } - .markdown-body * { - box-sizing: border-box; - } - .markdown-body input { - font-family: inherit; - font-size: inherit; - line-height: inherit; - } - .markdown-body a { - color: #4078c0; - text-decoration: none; - } - .markdown-body a:hover, - .markdown-body a:active { - text-decoration: underline; - } - .markdown-body strong { - font-weight: 600; - } - .markdown-body hr { - height: 0; - margin: 15px 0; - overflow: hidden; - background: transparent; - border: 0; - border-bottom: 1px solid #ddd; - } - .markdown-body hr::before { - display: table; - content: ""; - } - .markdown-body hr::after { - display: table; - clear: both; - content: ""; - } - .markdown-body table { - border-spacing: 0; - border-collapse: collapse; - } - .markdown-body td, - .markdown-body th { - padding: 0; - } - .markdown-body h1, - .markdown-body h2, - .markdown-body h3, - .markdown-body h4, - .markdown-body h5, - .markdown-body h6 { - margin-top: 0; - margin-bottom: 0; - } - .markdown-body h1 { - font-size: 32px; - font-weight: 600; - } - .markdown-body h2 { - font-size: 24px; - font-weight: 600; - } - .markdown-body h3 { - font-size: 20px; - font-weight: 600; - } - .markdown-body h4 { - font-size: 16px; - font-weight: 600; - } - .markdown-body h5 { - font-size: 14px; - font-weight: 600; - } - .markdown-body h6 { - font-size: 12px; - font-weight: 600; - } - .markdown-body p { - margin-top: 0; - margin-bottom: 10px; - } - .markdown-body blockquote { - margin: 0; - } - .markdown-body ul, - .markdown-body ol { - padding-left: 0; - margin-top: 0; - margin-bottom: 0; - } - .markdown-body ol ol, - .markdown-body ul ol { - list-style-type: lower-roman; - } - .markdown-body ul ul ol, - .markdown-body ul ol ol, - .markdown-body ol ul ol, - .markdown-body ol ol ol { - list-style-type: lower-alpha; - } - .markdown-body dd { - margin-left: 0; - } - .markdown-body code { - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - } - .markdown-body pre { - margin-top: 0; - margin-bottom: 0; - font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; - } - .markdown-body .octicon { - vertical-align: text-bottom; - } - .markdown-body input { - -webkit-font-feature-settings: "liga" 0; - font-feature-settings: "liga" 0; - } - .markdown-body::before { - display: table; - content: ""; - } - .markdown-body::after { - display: table; - clear: both; - content: ""; - } - .markdown-body>*:first-child { - margin-top: 0 !important; - } - .markdown-body>*:last-child { - margin-bottom: 0 !important; - } - .markdown-body a:not([href]) { - color: inherit; - text-decoration: none; - } - .markdown-body .anchor { - float: left; - padding-right: 4px; - margin-left: -20px; - line-height: 1; - } - .markdown-body .anchor:focus { - outline: none; - } - .markdown-body p, - .markdown-body blockquote, - .markdown-body ul, - .markdown-body ol, - .markdown-body dl, - .markdown-body table, - .markdown-body pre { - margin-top: 0; - margin-bottom: 16px; - } - .markdown-body hr { - height: 0.25em; - padding: 0; - margin: 24px 0; - background-color: #e7e7e7; - border: 0; - } - .markdown-body blockquote { - padding: 0 1em; - color: #777; - border-left: 0.25em solid #ddd; - } - .markdown-body blockquote>:first-child { - margin-top: 0; - } - .markdown-body blockquote>:last-child { - margin-bottom: 0; - } - .markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font-size: 11px; - line-height: 10px; - color: #555; - vertical-align: middle; - background-color: #fcfcfc; - border: solid 1px #ccc; - border-bottom-color: #bbb; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #bbb; - } - .markdown-body h1, - .markdown-body h2, - .markdown-body h3, - .markdown-body h4, - .markdown-body h5, - .markdown-body h6 { - margin-top: 24px; - margin-bottom: 16px; - font-weight: 600; - line-height: 1.25; - } - .markdown-body h1 .octicon-link, - .markdown-body h2 .octicon-link, - .markdown-body h3 .octicon-link, - .markdown-body h4 .octicon-link, - .markdown-body h5 .octicon-link, - .markdown-body h6 .octicon-link { - color: #000; - vertical-align: middle; - visibility: hidden; - } - .markdown-body h1:hover .anchor, - .markdown-body h2:hover .anchor, - .markdown-body h3:hover .anchor, - .markdown-body h4:hover .anchor, - .markdown-body h5:hover .anchor, - .markdown-body h6:hover .anchor { - text-decoration: none; - } - .markdown-body h1:hover .anchor .octicon-link, - .markdown-body h2:hover .anchor .octicon-link, - .markdown-body h3:hover .anchor .octicon-link, - .markdown-body h4:hover .anchor .octicon-link, - .markdown-body h5:hover .anchor .octicon-link, - .markdown-body h6:hover .anchor .octicon-link { - visibility: visible; - } - .markdown-body h1 { - padding-bottom: 0.3em; - font-size: 2em; - border-bottom: 1px solid #eee; - } - .markdown-body h2 { - padding-bottom: 0.3em; - font-size: 1.5em; - border-bottom: 1px solid #eee; - } - .markdown-body h3 { - font-size: 1.25em; - } - .markdown-body h4 { - font-size: 1em; - } - .markdown-body h5 { - font-size: 0.875em; - } - .markdown-body h6 { - font-size: 0.85em; - color: #777; - } - .markdown-body ul, - .markdown-body ol { - padding-left: 2em; - } - .markdown-body ul ul, - .markdown-body ul ol, - .markdown-body ol ol, - .markdown-body ol ul { - margin-top: 0; - margin-bottom: 0; - } - .markdown-body li>p { - margin-top: 16px; - } - .markdown-body li+li { - margin-top: 0.25em; - } - .markdown-body dl { - padding: 0; - } - .markdown-body dl dt { - padding: 0; - margin-top: 16px; - font-size: 1em; - font-style: italic; - font-weight: bold; - } - .markdown-body dl dd { - padding: 0 16px; - margin-bottom: 16px; - } - .markdown-body table { - display: block; - width: 100%; - overflow: auto; - } - .markdown-body table th { - font-weight: bold; - } - .markdown-body table th, - .markdown-body table td { - padding: 6px 13px; - border: 1px solid #ddd; - } - .markdown-body table tr { - background-color: #fff; - border-top: 1px solid #ccc; - } - .markdown-body table tr:nth-child(2n) { - background-color: #f8f8f8; - } - .markdown-body img { - max-width: 100%; - box-sizing: content-box; - background-color: #fff; - } - .markdown-body code { - padding: 0; - padding-top: 0.2em; - padding-bottom: 0.2em; - margin: 0; - font-size: 85%; - background-color: rgba(0,0,0,0.04); - border-radius: 3px; - } - .markdown-body code::before, - .markdown-body code::after { - letter-spacing: -0.2em; - content: " "; - } - .markdown-body pre { - word-wrap: normal; - } - .markdown-body pre>code { - padding: 0; - margin: 0; - font-size: 100%; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; - } - .markdown-body .highlight { - margin-bottom: 16px; - } - .markdown-body .highlight pre { - margin-bottom: 0; - word-break: normal; - } - .markdown-body .highlight pre, - .markdown-body pre { - padding: 16px; - overflow: auto; - font-size: 85%; - line-height: 1.45; - background-color: #f7f7f7; - border-radius: 3px; - } - .markdown-body pre code { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; - } - .markdown-body pre code::before, - .markdown-body pre code::after { - content: normal; - } - .markdown-body .pl-0 { - padding-left: 0 !important; - } - .markdown-body .pl-1 { - padding-left: 3px !important; - } - .markdown-body .pl-2 { - padding-left: 6px !important; - } - .markdown-body .pl-3 { - padding-left: 12px !important; - } - .markdown-body .pl-4 { - padding-left: 24px !important; - } - .markdown-body .pl-5 { - padding-left: 36px !important; - } - .markdown-body .pl-6 { - padding-left: 48px !important; - } - .markdown-body .full-commit .btn-outline:not(:disabled):hover { - color: #4078c0; - border: 1px solid #4078c0; - } - .markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; - line-height: 10px; - color: #555; - vertical-align: middle; - background-color: #fcfcfc; - border: solid 1px #ccc; - border-bottom-color: #bbb; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #bbb; - } - .markdown-body :checked+.radio-label { - position: relative; - z-index: 1; - border-color: #4078c0; - } - .markdown-body .task-list-item { - list-style-type: none; - } - .markdown-body .task-list-item+.task-list-item { - margin-top: 3px; - } - .markdown-body .task-list-item input { - margin: 0 0.2em 0.25em -1.6em; - vertical-align: middle; - } - .markdown-body hr { - border-bottom-color: #eee; - } -`; - -if (document && !document.getElementById('github-markdown-css')) { - const styleNode = document.createElement('style'); - styleNode.id = 'github-markdown-css'; - styleNode.innerHTML = styles; - - document.head.appendChild(styleNode); -} diff --git a/addons/knobs/README.md b/addons/knobs/README.md index 2da7666da27d..14e5e033b323 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -19,10 +19,12 @@ First of all, you need to install Knobs into your project as a dev dependency. yarn add @storybook/addon-knobs --dev ``` -Then, configure it as an addon by adding it to your `addons.js` file (located in the Storybook config directory). +within `.storybook/main.js`: ```js -import '@storybook/addon-knobs/register'; +module.exports = { + addons: ['@storybook/addon-knobs/register'] +} ``` Now, write your stories with Knobs. @@ -48,10 +50,10 @@ export const withAButton = () => ( // Knobs as dynamic variables. export const asDynamicVariables = () => { - const name = text("Name", "Arunoda Susiripala"); - const age = number("Age", 89); - + 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>; }; ``` @@ -64,18 +66,17 @@ import { withKnobs, text, boolean } from '@storybook/addon-knobs'; import MyButton from './MyButton.vue'; -const stories = storiesOf('Storybook Knobs', module); - -// Add the `withKnobs` decorator to add knobs support to your stories. -// You can also configure `withKnobs` as a global decorator. -stories.addDecorator(withKnobs); +export default { + title: "Storybook Knobs", + decorators: [withKnobs] +}; // Assign `props` to the story's component, calling // knob methods within the `default` property of each prop, // 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. -stories.add('with a button', () => ({ +export const exampleWithKnobs = () => ({ components: { MyButton }, props: { isDisabled: { @@ -86,7 +87,7 @@ stories.add('with a button', () => ({ } }, template: `<MyButton :isDisabled="isDisabled">{{ text }}</MyButton>` -})); +}); ``` MyButton.vue: @@ -116,31 +117,59 @@ import { boolean, number, text, withKnobs } from '@storybook/addon-knobs'; import { Button } from '@storybook/angular/demo'; -const stories = storiesOf('Storybook Knobs', module); - -// "withKnobs" decorator should be applied before the stories using knobs -stories.addDecorator(withKnobs); +export default { + title: "Storybook Knobs", + decorators: [withKnobs] +}; -// Knobs for Angular props -stories.add('with a button', () => ({ +export const withKnobs = () => ({ component: Button, props: { - text: text('text', 'Hello Storybook'), // The first param of the knob function has to be exactly the same as the component input. + text: text('text', 'Hello Storybook'), // The first param of the knob function has to be exactly the same as the component input. }, -})); +}); +``` + +### With Ember +```js +import { withKnobs, text, boolean } from '@storybook/addon-knobs'; +import { hbs } from 'ember-cli-htmlbars'; +export default { + title: 'StoryBook with Knobs', + decorators: [withKnobs], +}; + +export const button = () => ({ + template: hbs` + <button disabled={{disabled}}>{{label}}</button> + `, + context: { + label: text('label', 'Hello Storybook'), + disabled: boolean('disabled', false), + }, +}); ``` +## Categorization + Categorize your Knobs by assigning them a `groupId`. When a `groupId` exists, tabs will appear in the Knobs storybook panel to filter between the groups. Knobs without a `groupId` are automatically categorized into the `ALL` group. + ```js -// Knob assigned a groupId. -stories.add('as dynamic variables', () => { - const groupId = 'GROUP-ID1' - const name = text('Name', 'Arunoda Susiripala', groupId); +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 content = ` + I am ${name} and I'm ${age} years old. + ${message} + `; - const content = `My name is ${name}.`; - return (<div>{content}</div>); -}); + return <div>{content}</div>; +}; ``` You can see your Knobs in a Storybook panel as shown below. @@ -166,7 +195,7 @@ Allows you to get some text from the user. import { text } from '@storybook/addon-knobs'; const label = 'Your Name'; -const defaultValue = 'Arunoda Susiripala'; +const defaultValue = 'James'; const groupId = 'GROUP-ID1'; const value = text(label, defaultValue, groupId); @@ -185,6 +214,7 @@ const groupId = 'GROUP-ID1'; const value = boolean(label, defaultValue, groupId); ``` + ### number Allows you to get a number from the user. @@ -200,9 +230,11 @@ const value = number(label, defaultValue); ``` For use with `groupId`, pass the default `options` as the third argument. -``` + +```js const value = number(label, defaultValue, {}, groupId); ``` + ### number bound by range Allows you to get a number from the user using a range slider. @@ -246,7 +278,7 @@ import { object } from '@storybook/addon-knobs'; const label = 'Styles'; const defaultValue = { - backgroundColor: 'red' + backgroundColor: 'red', }; const groupId = 'GROUP-ID1'; @@ -281,8 +313,9 @@ const value = array(label, defaultValue); > const value = array(label, defaultValue, separator); > ``` -For use with `groupId`, pass the default `separator` as the third argument -``` +For use with `groupId`, pass the default `separator` as the third argument. + +```js const value = array(label, defaultValue, ',', groupId); ``` @@ -307,8 +340,37 @@ const groupId = 'GROUP-ID1'; const value = select(label, options, defaultValue, groupId); ``` -> You can also provide options as an array like this: `['red', 'blue', 'yellow']`. +Options can also be an array: +```js +import { select } from '@storybook/addon-knobs'; +const label = 'Cats'; +const options = ['linus', 'eleanor', 'lover'] +const defaultValue = 'eleanor'; +const groupId = 'GROUP-ID2'; +const value = select(label, options, defaultValue, groupId); +``` + +Options can also be an array OF objects: + +```js +const label = 'Dogs'; +const arrayOfObjects = [ + { + label: 'Sparky', + dogParent: 'Matthew', + location: 'Austin', + }, + { + label: 'Juniper', + dogParent: 'Joshua', + location: 'Austin', + }, +]; +const defaultValue = arrayOfObjects[0]; +const groupId = 'GROUP-ID3'; +const value = select(label, options, defaultValue, groupId); +``` ### radio buttons @@ -319,9 +381,9 @@ import { radios } from '@storybook/addon-knobs'; const label = 'Fruits'; const options = { - Kiwi: 'kiwi', - Guava: 'guava', - Watermelon: 'watermelon', + Kiwi: 'kiwi', + Guava: 'guava', + Watermelon: 'watermelon', }; const defaultValue = 'kiwi'; const groupId = 'GROUP-ID1'; @@ -425,30 +487,38 @@ withKnobs also accepts two optional options as story parameters. Usage: ```js -import { storiesOf } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; -const stories = storiesOf('Storybook Knobs', module); +export default { + title: 'Storybook Knobs', + decorators: [withKnobs], +}; -stories.addDecorator(withKnobs) -stories.add('story name', () => ..., { - knobs: { - timestamps: true, // Doesn't emit events while user is typing. - escapeHTML: 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 unrecommendend in cases when you host your storybook on some route of your main site or web app. +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, + } } -}); +}; ``` ## Typescript If you are using Typescript, make sure you have the type definitions installed for the following libs: -- node -- react +- 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/package.json b/addons/knobs/package.json index 1244eb2b2ed9..2ba6aff48e90 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-knobs", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook Addon Prop Editor Component", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -29,12 +28,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "@types/react-color": "^3.0.1", "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", @@ -51,12 +50,14 @@ "devDependencies": { "@types/escape-html": "0.0.20", "@types/react-lifecycles-compat": "^3.0.1", - "@types/react-select": "^3.0.4" + "@types/react-select": "^2.0.19", + "@types/webpack-env": "^1.15.0" }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/knobs/preset.js b/addons/knobs/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/addons/knobs/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); 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..4e39bec821b1 100644 --- a/addons/knobs/src/components/Panel.tsx +++ b/addons/knobs/src/components/Panel.tsx @@ -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> diff --git a/addons/knobs/src/components/types/Number.tsx b/addons/knobs/src/components/types/Number.tsx index 949ed9a3e67c..7778976f0e24 100644 --- a/addons/knobs/src/components/types/Number.tsx +++ b/addons/knobs/src/components/types/Number.tsx @@ -27,12 +27,12 @@ interface NumberTypeProps extends KnobControlProps<NumberTypeKnobValue | null> { const RangeInput = styled.input( { boxSizing: 'border-box', - height: '25px', + height: 25, outline: 'none', border: '1px solid #f7f4f4', borderRadius: 2, fontSize: 11, - padding: '5px', + padding: 5, color: '#444', }, { diff --git a/addons/knobs/src/components/types/Select.tsx b/addons/knobs/src/components/types/Select.tsx index 8a7f387859f4..a81b734be6c4 100644 --- a/addons/knobs/src/components/types/Select.tsx +++ b/addons/knobs/src/components/types/Select.tsx @@ -30,15 +30,23 @@ const SelectType: FunctionComponent<SelectTypeProps> & { deserialize: typeof deserialize; } = ({ knob, onChange }) => { const { options } = knob; - const entries = Array.isArray(options) - ? options.reduce<Record<PropertyKey, SelectTypeKnobValue>>((acc, k) => ({ ...acc, [k]: k }), {}) - : (options as Record<PropertyKey, SelectTypeKnobValue>); - const selectedKey = Object.keys(entries).find(k => { - if (Array.isArray(knob.value)) { - return JSON.stringify(entries[k]) === JSON.stringify(knob.value); + const callbackReduceArrayOptions = (acc: any, option: any, i: number) => { + 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 { value: knobVal } = knob; + const entryVal = entries[key]; + + if (Array.isArray(knobVal)) { + return JSON.stringify(entryVal) === JSON.stringify(knobVal); } - return entries[k] === knob.value; + return entryVal === knobVal; }); return ( diff --git a/addons/knobs/src/preset/addDecorator.ts b/addons/knobs/src/preset/addDecorator.ts new file mode 100644 index 000000000000..08d985c0e190 --- /dev/null +++ b/addons/knobs/src/preset/addDecorator.ts @@ -0,0 +1,4 @@ +import { addDecorator } from '@storybook/client-api'; +import { withKnobs } from '../index'; + +addDecorator(withKnobs); diff --git a/addons/knobs/src/preset/index.ts b/addons/knobs/src/preset/index.ts new file mode 100644 index 000000000000..835b0fffa16c --- /dev/null +++ b/addons/knobs/src/preset/index.ts @@ -0,0 +1,15 @@ +type KnobsOptions = { + addDecorator?: boolean; +}; + +export function managerEntries(entry: any[] = [], options: any) { + return [...entry, require.resolve('../register')]; +} + +export function config(entry: any[] = [], { addDecorator = true }: KnobsOptions = {}) { + const knobsConfig = []; + if (addDecorator) { + knobsConfig.push(require.resolve('./addDecorator')); + } + return [...entry, ...knobsConfig]; +} diff --git a/addons/links/README.md b/addons/links/README.md index dcf74a2d1a0c..22a7c0cf2a65 100644 --- a/addons/links/README.md +++ b/addons/links/README.md @@ -12,25 +12,29 @@ Install this addon by adding the `@storybook/addon-links` dependency: yarn add -D @storybook/addon-links ``` -First configure it as an addon by adding it to your addons.js file (located in the Storybook config directory). +within `.storybook/main.js`: ```js -import '@storybook/addon-links/register'; +module.exports = { + addons: ['@storybook/addon-links/register'] +} ``` Then you can import `linkTo` in your stories and use like this: ```js -import { storiesOf } from '@storybook/react' import { linkTo } from '@storybook/addon-links' -storiesOf('Button', module) - .add('First', () => ( - <button onClick={linkTo('Button', 'Second')}>Go to "Second"</button> - )) - .add('Second', () => ( - <button onClick={linkTo('Button', 'First')}>Go to "First"</button> - )); +export default { + title: 'Button', +}; + +export const first = () => ( + <button onClick={linkTo('Button', 'second')}>Go to "Second"</button> +); +export const second = () => ( + <button onClick={linkTo('Button', 'first')}>Go to "First"</button> +); ``` Have a look at the linkTo function: @@ -45,28 +49,36 @@ linkTo('Toggle') // Links to the first story in the 'Toggle' kind With that, you can link an event in a component to any story in the Storybook. -- First parameter is the story kind name (what you named with `storiesOf`). -- Second (optional) parameter is the story name (what you named with `.add`). - If the second parameter is omitted, the link will point to the first story in the given kind. +- First parameter is the story kind name (what you named with `title`). +- Second (optional) parameter is the story name (what you named with `exported name`). + If the second parameter is omitted, the link will point to the first story in the given kind. You can also pass a function instead for any of above parameter. That function accepts arguments emitted by the event and it should return a string: ```js -import { storiesOf } from '@storybook/react'; import { LinkTo, linkTo } from '@storybook/addon-links'; -storiesOf('Select', module) - .add('Index', () => ( - <select value="Index" onChange={linkTo('Select', e => e.currentTarget.value)}> - <option>Index</option> - <option>First</option> - <option>Second</option> - <option>Third</option> - </select> - )) - .add('First', () => <LinkTo story="Index">Go back</LinkTo>) - .add('Second', () => <LinkTo story="Index">Go back</LinkTo>) - .add('Third', () => <LinkTo story="Index">Go back</LinkTo>); +export default { + title: 'Select', +}; + +export const index = () => ( + <select value="Index" onChange={linkTo('Select', e => e.currentTarget.value)}> + <option>index</option> + <option>first</option> + <option>second</option> + <option>third</option> + </select> +); +export const first = () => ( + <LinkTo story="index">Go back</LinkTo> +); +export const second = () => ( + <LinkTo story="index">Go back</LinkTo> +); +export const third = () => ( + <LinkTo story="index">Go back</LinkTo> +); ``` ## hrefTo function @@ -74,16 +86,18 @@ storiesOf('Select', module) If you want to get an URL for a particular story, you may use `hrefTo` function. It returns a promise, which resolves to string containing a relative URL: ```js -import { storiesOf } from '@storybook/react'; import { hrefTo } from '@storybook/addon-links'; import { action } from '@storybook/addon-actions'; -storiesOf('Href', module) - .add('log', () => { - hrefTo('Href', 'log').then(action('URL of this story')); +export default { + title: 'Href', +}; + +export const log = () => { + hrefTo('Href', 'log').then(action('URL of this story')); - return <span>See action logger</span>; - }); + return <span>See action logger</span>; +}; ``` ## withLinks decorator @@ -92,14 +106,16 @@ storiesOf('Href', module) Here is an example in React, but it works with any framework: ```js -import { storiesOf } from '@storybook/react' import { withLinks } from '@storybook/addon-links' -storiesOf('Button', module) - .addDecorator(withLinks) - .add('First', () => ( - <button data-sb-kind="OtherKind" data-sb-story="OtherStory">Go to "OtherStory"</button> - )) +export default { + title: 'Button', + decorators: [withLinks], +}; + +export const first = () => ( + <button data-sb-kind="OtherKind" data-sb-story="otherStory">Go to "OtherStory"</button> +); ``` ## LinkTo component (React only) @@ -108,16 +124,18 @@ One possible way of using `hrefTo` is to create a component that uses native `a` A React implementation of such a component can be imported from `@storybook/addon-links` package: ```js -import { storiesOf } from '@storybook/react'; import LinkTo from '@storybook/addon-links/react'; -storiesOf('Link', module) - .add('First', () => ( - <LinkTo story="Second">Go to Second</LinkTo> - )) - .add('Second', () => ( - <LinkTo story="First">Go to First</LinkTo> - )); +export default { + title: 'Link', +}; + +export const first = () => ( + <LinkTo story="second">Go to Second</LinkTo> +); +export const second = () => ( + <LinkTo story="first">Go to First</LinkTo> +); ``` It accepts all the props the `a` element does, plus `story` and `kind`. It the `kind` prop is omitted, the current kind will be preserved. diff --git a/addons/links/package.json b/addons/links/package.json index 7210ec7801e0..cba15e8a7956 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Story Links addon for storybook", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -29,20 +28,26 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/csf": "0.0.1", + "@storybook/router": "6.0.0-alpha.2", + "@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" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/links/src/preview.test.js b/addons/links/src/preview.test.js index 49fa1bba24c6..46647a36a37e 100644 --- a/addons/links/src/preview.test.js +++ b/addons/links/src/preview.test.js @@ -31,14 +31,6 @@ jest.mock('global', () => ({ }, })); -export const mockChannel = () => { - return { - emit: jest.fn(), - on: jest.fn(), - once: jest.fn(), - }; -}; - describe('preview', () => { describe('linkTo()', () => { it('should select the kind and story provided', () => { @@ -116,7 +108,10 @@ describe('preview', () => { addons.getChannel.mockReturnValue(channel); __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => null); - const handler = linkTo((a, b) => a + b, (a, b) => b + a); + const handler = linkTo( + (a, b) => a + b, + (a, b) => b + a + ); handler('kind', 'name'); expect(channel.emit.mock.calls).toContainEqual([ diff --git a/addons/links/src/preview.ts b/addons/links/src/preview.ts index 5500891af631..0aeb0ebd9ed8 100644 --- a/addons/links/src/preview.ts +++ b/addons/links/src/preview.ts @@ -7,7 +7,7 @@ import { import qs from 'qs'; import addons from '@storybook/addons'; import { STORY_CHANGED, SELECT_STORY } from '@storybook/core-events'; -import { toId } from '@storybook/router/utils'; +import { toId } from '@storybook/csf'; import { logger } from '@storybook/client-logger'; interface ParamsId { @@ -36,7 +36,7 @@ const valueOrCall = (args: string[]) => (value: string | ((...args: string[]) => export const linkTo = ( idOrKindInput: string, storyInput?: string | ((...args: any[]) => string) -) => (...args: string[]) => { +) => (...args: any[]) => { const resolver = valueOrCall(args); const { storyId } = storyStore.getSelection(); const current = storyStore.fromId(storyId) || {}; diff --git a/addons/links/src/react/components/__snapshots__/link.test.js.snap b/addons/links/src/react/components/__snapshots__/link.test.js.snap index a977ec574aa2..3ed69931959a 100644 --- a/addons/links/src/react/components/__snapshots__/link.test.js.snap +++ b/addons/links/src/react/components/__snapshots__/link.test.js.snap @@ -2,7 +2,7 @@ exports[`LinkTo render should render a link 1`] = ` <a - href="http://localhost/?id=foo--bar" + href="originpathname?search=&id=foo--bar" onClick={[Function]} /> `; diff --git a/addons/links/src/react/components/link.test.js b/addons/links/src/react/components/link.test.js index 804dad07e130..b38b6bf2bc9d 100644 --- a/addons/links/src/react/components/link.test.js +++ b/addons/links/src/react/components/link.test.js @@ -3,10 +3,30 @@ import React from 'react'; import addons from '@storybook/addons'; import { SELECT_STORY } from '@storybook/core-events'; -import { mockChannel } from '../../preview.test'; import LinkTo from './link'; jest.mock('@storybook/addons'); +jest.mock('global', () => ({ + document: { + location: { + origin: 'origin', + pathname: 'pathname', + search: 'search', + }, + }, + __STORYBOOK_STORY_STORE__: { + getSelection: jest.fn(() => ({ id: 1 })), + fromId: jest.fn(() => ({})), + }, +})); + +const mockChannel = () => { + return { + emit: jest.fn(), + on: jest.fn(), + once: jest.fn(), + }; +}; describe('LinkTo', () => { describe('render', () => { diff --git a/addons/notes/README.md b/addons/notes/README.md deleted file mode 100644 index 3debb6c863a4..000000000000 --- a/addons/notes/README.md +++ /dev/null @@ -1,119 +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 -``` - -Then create a file called `addons.js` in your Storybook config. - -Add following content to it: - -```js -// register the notes addon as a tab -import '@storybook/addon-notes/register'; -// or register the notes addon as a panel. Only one can be used! -import '@storybook/addon-notes/register-panel'; -``` - -Now, you can use the `notes` parameter to add a note to each story. - -### With React - -```js -import { storiesOf } from '@storybook/react'; - -import Component from './Component'; - -storiesOf('Component', module).add('with some emoji', () => <Component />, { - notes: 'An example of addon notes', -}); -``` - -### With Vue - -```js -import { storiesOf } from '@storybook/vue'; - -import MyButton from './MyButton.vue'; - -storiesOf('MyButton', module).add( - 'with some emoji', - () => ({ - components: { MyButton }, - template: '<my-button>😀 😎 👍 💯</my-button>', - }), - { - notes: 'An example of addon notes', - } -); -``` - -### With Angular - -```js -import { storiesOf } from '@storybook/vue'; - -import { ButtonComponent } from './button.component'; - -storiesOf('Button', module).add( - 'with some emoji', - () => ({ - component: ButtonComponent, - props: { - text: '😀 😎 👍 💯' - } - }), - { - notes: 'An example of addon notes', - } -); -``` - -## Using Markdown - -Using Markdown in your notes is supported, Storybook will load Markdown as raw by default. - -```js -import { storiesOf } from '@storybook/react'; -import Component from './Component'; -import markdownNotes from './someMarkdownText.md'; - -storiesOf('Component', module).add('With Markdown', () => <Component />, { - notes: { markdown: markdownNotes }, -}); -``` - -## 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'; - -storiesOf('Component', module).add('With Markdown', () => <Component />, { - 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 579484dfb832..000000000000 --- a/addons/notes/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@storybook/addon-notes", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", - "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" - } -} 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 02655175fe57..000000000000 --- a/addons/notes/src/Panel.tsx +++ /dev/null @@ -1,216 +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); - if (isAbsoluteUrl) { - 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/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 5e90c0c7a2fe..000000000000 --- a/addons/ondevice-actions/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-actions", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "core-js": "^3.0.1", - "fast-deep-equal": "^2.0.1" - }, - "devDependencies": { - "@storybook/addon-actions": "5.3.0-alpha.41" - }, - "peerDependencies": { - "@storybook/addon-actions": "*", - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - } -} 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-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 b67ac3b235d9..000000000000 --- a/addons/ondevice-backgrounds/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-backgrounds", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "core-js": "^3.0.1", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - } -} 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-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 a71209b77890..000000000000 --- a/addons/ondevice-knobs/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-knobs", - "version": "5.3.0-alpha.41", - "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": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "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.2.0-alpha.34", - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - } -} 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 b1e38d7ff192..000000000000 --- a/addons/ondevice-knobs/src/GroupTabs.js +++ /dev/null @@ -1,73 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { ScrollView, Text, TouchableOpacity } from 'react-native'; - -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)} - > - <Text - style={{ - color: selectedGroup === name ? 'black' : '#ccc', - fontSize: 17, - }} - > - {title} - </Text> - </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 3b053b5a1808..000000000000 --- a/addons/ondevice-knobs/src/PropField.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import { View, Text } from 'react-native'; -import React from 'react'; -import TypeMap from './types'; - -const InvalidType = () => <Text style={{ margin: 10 }}>Invalid Type</Text>; - -const PropField = ({ onChange, onPress, knob }) => { - const InputType = TypeMap[knob.type] || InvalidType; - - return ( - <View> - {!knob.hideLabel ? ( - <Text - style={{ - marginLeft: 10, - fontSize: 14, - color: 'rgb(68, 68, 68)', - fontWeight: 'bold', - }} - > - {`${knob.label || knob.name}`} - </Text> - ) : 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 dceafd9e4146..000000000000 --- a/addons/ondevice-knobs/src/panel.js +++ /dev/null @@ -1,193 +0,0 @@ -import React from 'react'; -import { View, Text, TouchableOpacity } 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 GroupTabs from './GroupTabs'; -import PropForm from './PropForm'; - -const getTimestamp = () => +new Date(); - -const DEFAULT_GROUP_ID = 'Other'; - -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 }}> - {groupIds.length > 0 && ( - <GroupTabs groups={groups} onGroupSelect={this.onGroupSelect} selectedGroup={groupId} /> - )} - <View> - <PropForm - knobs={knobsArray} - onFieldChange={this.handleChange} - onFieldClick={this.handleClick} - /> - </View> - <TouchableOpacity - style={{ - borderRadius: 2, - borderWidth: 1, - borderColor: '#f7f4f4', - padding: 4, - margin: 10, - justifyContent: 'center', - alignItems: 'center', - }} - onPress={this.reset} - > - <Text>RESET</Text> - </TouchableOpacity> - </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 8234353f6420..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 { TextInput } from 'react-native'; - -function formatArray(value, separator) { - if (value === '') { - return []; - } - return value.split(separator); -} - -const ArrayType = ({ knob, onChange }) => ( - <TextInput - id={knob.name} - underlineColorAndroid="transparent" - autoCapitalize="none" - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - color: '#555', - }} - 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 f6f05d05109f..000000000000 --- a/addons/ondevice-knobs/src/types/Button.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { TouchableOpacity, Text } from 'react-native'; - -const ButtonType = ({ knob, onPress }) => ( - <TouchableOpacity style={{ margin: 10 }} onPress={() => onPress(knob)}> - <Text style={{ fontSize: 17, color: '#007aff' }}>{knob.name}</Text> - </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 ebee0bbbe27b..000000000000 --- a/addons/ondevice-knobs/src/types/Color.js +++ /dev/null @@ -1,101 +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'; - -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; - const colorStyle = { - borderColor: 'rgb(247, 244, 244)', - width: 30, - height: 20, - borderRadius: 2, - margin: 10, - backgroundColor: knob.value, - }; - return ( - <View> - <TouchableOpacity style={colorStyle} 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 d0eec1466250..000000000000 --- a/addons/ondevice-knobs/src/types/Date.js +++ /dev/null @@ -1,103 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { PureComponent } from 'react'; -import { TouchableOpacity, Text, View } from 'react-native'; -import DateTimePicker from 'react-native-modal-datetime-picker'; - -// 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' }}> - <TouchableOpacity - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - padding: 5, - }} - onPress={this.showDatePicker} - > - <Text style={{ fontSize: 13, color: '#555' }}>{dateString}</Text> - </TouchableOpacity> - <TouchableOpacity - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - padding: 5, - marginLeft: 5, - }} - onPress={this.showTimePicker} - > - <Text style={{ fontSize: 13, color: '#555' }}>{timeString}</Text> - </TouchableOpacity> - </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 00adaf2ad37f..000000000000 --- a/addons/ondevice-knobs/src/types/Number.js +++ /dev/null @@ -1,94 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { TextInput, View, Slider } from 'react-native'; - -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 ( - <TextInput - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - fontSize: 13, - padding: 5, - color: '#555', - }} - 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 2fef52a20e59..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 { TextInput } from 'react-native'; -import deepEqual from 'deep-equal'; - -const styles = { - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - color: '#555', -}; - -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 ( - <TextInput - id={knob.name} - style={{ ...styles, ...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 512ee1863988..000000000000 --- a/addons/ondevice-knobs/src/types/Select.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ - -import PropTypes from 'prop-types'; -import { View, TextInput } from 'react-native'; -import React from 'react'; -import ModalPicker from 'react-native-modal-selector'; - -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" - > - <TextInput - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - padding: 5, - color: '#555', - margin: 10, - }} - 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 5c96f27310e1..000000000000 --- a/addons/ondevice-knobs/src/types/Text.js +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { TextInput } from 'react-native'; - -const TextType = ({ knob, onChange }) => ( - <TextInput - style={{ - borderWidth: 1, - borderColor: '#f7f4f4', - borderRadius: 2, - fontSize: 13, - padding: 5, - margin: 10, - color: '#555', - }} - 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 23b4c0b2657b..000000000000 --- a/addons/ondevice-notes/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@storybook/addon-ondevice-notes", - "version": "5.3.0-alpha.41", - "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": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "core-js": "^3.0.1", - "prop-types": "^15.7.2", - "react-native-simple-markdown": "^1.1.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - }, - "publishConfig": { - "access": "public" - } -} 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 4878a7c02865..000000000000 --- a/addons/ondevice-notes/src/components/Notes.tsx +++ /dev/null @@ -1,36 +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'; - -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 }}> - <Markdown>{textAfterFormatted}</Markdown> - </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/options/README.md b/addons/options/README.md index ef028e9f18dd..854cc8b45c54 100644 --- a/addons/options/README.md +++ b/addons/options/README.md @@ -1,149 +1,3 @@ -#NOTE: Options Addon is deprecated as of Storybook 5.0 +# Options Addon is deprecated as of Storybook 5.0 -Options are now configured using the [`options` parameter](../../docs/src/pages/configurations/options-parameter/index.md) which is built into Storybook. - -- Global options: `addParameters({ options: { ... }})` and no addon is needed. -- Story options: `storiesOf(...).add('name', storyFn, { options: { ... }})` - -See the [migration docs](../../MIGRATION.md#options-addon-deprecated) for what's changed. - -# Storybook Options Addon - -The Options addon can be used to (re-)configure the [Storybook](https://storybook.js.org) UI at runtime. - -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/options/docs/screenshot.png) - -## Getting Started - -First, install the addon - -```sh -yarn add @storybook/addon-options --dev -``` - -Add this line to your `addons.js` file (create this file inside your storybook config directory if needed). - -```js -import '@storybook/addon-options/register'; -``` - -### Set options globally - -Import and use the `addParameters` + `options`-key in your `config.js` file. - -```js -import { addParameters, configure } from '@storybook/react'; - -// Option defaults: -addParameters({ - options: { - /** - * name to display in the top left corner - * @type {String} - */ - name: 'Storybook', - /** - * URL for name in top left corner to link to - * @type {String} - */ - url: '#', - /** - * show story component as full screen - * @type {Boolean} - */ - goFullScreen: false, - /** - * display panel that shows a list of stories - * @type {Boolean} - */ - showStoriesPanel: true, - /** - * display panel that shows addon configurations - * @type {Boolean} - */ - showAddonPanel: true, - /** - * display floating search box to search through stories - * @type {Boolean} - */ - showSearchBox: false, - /** - * show addon panel as a vertical panel on the right - * @type {Boolean} - */ - addonPanelInRight: false, - /** - * regex for finding the hierarchy separator - * @example: - * null - turn off hierarchy - * /\// - split by `/` - * /\./ - split by `.` - * /\/|\./ - split by `/` or `.` - * @type {Regex} - */ - hierarchySeparator: null, - /** - * regex for finding the hierarchy root separator - * @example: - * null - turn off multiple hierarchy roots - * /\|/ - split by `|` - * @type {Regex} - */ - hierarchyRootSeparator: null, - /** - * sidebar tree animations - * @type {Boolean} - */ - sidebarAnimations: true, - /** - * id to select an addon panel - * @type {String} - */ - selectedPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook - /** - * enable/disable shortcuts - * @type {Boolean} - */ - enableShortcuts: false, // true by default - /** - * show/hide tool bar - * @type {Boolean} - */ - isToolshown: false, // true by default - }, -}); - -configure(() => require('./stories'), module); -``` - -### Using per-story options - -The options-addon accepts story parameters on the `options` key: - -```js -import { storiesOf } from '@storybook/react'; -import MyComponent from './my-component'; - -storiesOf('Addons|Custom options', module) - // If you want to set the option for all stories in of this kind - .addParameters({ options: { addonPanelInRight: true } }) - .add( - 'Story for MyComponent', - () => <MyComponent />, - // If you want to set the options for a specific story - { options: { addonPanelInRight: false } } - ); -``` - -## TypeScript - -To install type definitions: `yarn add @types/storybook__addon-options --dev` - -Make sure you also have the type definitions installed for the following libs: - -- Node -- React - -You can install them using `yarn add @types/node @types/react --dev`, assuming you are using TypeScript >2.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/package.json b/addons/options/package.json index b5ce4fa3f094..4c544819f001 100644 --- a/addons/options/package.json +++ b/addons/options/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-options", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Options addon for storybook", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -29,14 +28,18 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", "core-js": "^3.0.1", "util-deprecate": "^1.0.2" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "peerDependencies": { "react": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/options/src/register.ts b/addons/options/src/register.ts index d9d09ac29e40..8cec35efa859 100644 --- a/addons/options/src/register.ts +++ b/addons/options/src/register.ts @@ -1,10 +1,14 @@ import addons from '@storybook/addons'; +import deprecate from 'util-deprecate'; import EVENTS, { ADDON_ID } from './constants'; -addons.register(ADDON_ID, api => { - const channel = addons.getChannel(); +addons.register( + ADDON_ID, + deprecate(api => { + const channel = addons.getChannel(); - channel.on(EVENTS.SET, data => { - api.setOptions(data.options); - }); -}); + channel.on(EVENTS.SET, data => { + api.setOptions(data.options); + }); + }, 'storybook-addon-options is deprecated and will stop working soon') +); diff --git a/addons/queryparams/package.json b/addons/queryparams/package.json index e4733a010c05..db60b1742e07 100644 --- a/addons/queryparams/package.json +++ b/addons/queryparams/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-queryparams", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -19,7 +19,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -30,19 +29,23 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "qs": "^6.6.0", "react": "^16.8.3", "ts-dedent": "^1.1.0" }, + "devDependencies": { + "@types/webpack-env": "^1.15.0" + }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/storyshots/README.md b/addons/storyshots/README.md index c6d792f1cd7b..474bf391921a 100644 --- a/addons/storyshots/README.md +++ b/addons/storyshots/README.md @@ -1,4 +1,4 @@ # StoryShots - [addon-storyshots](storyshots-core) - Basic StoryShots api -- [addon-storyshots-puppeteer](storyshots-puppeteer) - Image Snapshots addition to StoryShots based on [puppeteer](https://github.com/GoogleChrome/puppeteer) +- [addon-storyshots-puppeteer](storyshots-puppeteer) - Integration of StoryShots with [puppeteer](https://github.com/GoogleChrome/puppeteer) 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/presets.js b/addons/storyshots/storyshots-core/.storybook/presets.js index b1d463490f6d..a04174c7a331 100644 --- a/addons/storyshots/storyshots-core/.storybook/presets.js +++ b/addons/storyshots/storyshots-core/.storybook/presets.js @@ -1 +1 @@ -module.exports = ['@storybook/addon-docs/react/preset']; +module.exports = ['@storybook/addon-docs/preset']; diff --git a/addons/storyshots/storyshots-core/README.md b/addons/storyshots/storyshots-core/README.md index 0ad031893871..f6d603b3791e 100644 --- a/addons/storyshots/storyshots-core/README.md +++ b/addons/storyshots/storyshots-core/README.md @@ -16,6 +16,24 @@ 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`. @@ -33,6 +51,8 @@ If you still need to configure jest you can use the resources mentioned below: ### Configure Jest to work with Webpack's [require.context()](https://webpack.js.org/guides/dependency-management/#require-context) +**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: @@ -208,24 +228,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 @@ -385,7 +387,7 @@ Whenever you change you're data requirements by adding (and rendering) or (accid ### `config` -The `config` parameter must be a function that helps to configure storybook like the `config.js` does. +The `config` parameter must be a function that helps to configure storybook like the `preview.js` does. If it's not specified, storyshots will try to use [configPath](#configPath) parameter. ```js @@ -425,8 +427,8 @@ import initStoryshots from '@storybook/addon-storyshots'; initStoryshots({ configPath: path.resolve(__dirname, '../../.storybook') }); ``` -`configPath` can also specify path to the `config.js` itself. In this case, config directory will be -a base directory of the `configPath`. It may be useful when the `config.js` for test should differ from the +`configPath` can also specify path to the `preview.js` itself. In this case, config directory will be +a base directory of the `configPath`. It may be useful when the `preview.js` for test should differ from the original one. It also may be useful for separating tests to different test configs: ```js @@ -653,13 +655,15 @@ This is a class that generates snapshot's name based on the story (kind, story & Let's say we wanted to create a test function for shallow && multi-file snapshots: ```js -import initStoryshots, { getSnapshotFileName } from '@storybook/addon-storyshots'; +import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots'; import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; +const converter = new Stories2SnapsConverter(); + initStoryshots({ test: ({ story, context }) => { - const snapshotFileName = getSnapshotFileName(context); + const snapshotFileName = converter.getSnapshotFileName(context); const storyElement = story.render(); const shallowTree = shallow(storyElement); diff --git a/addons/storyshots/storyshots-core/injectFileName.js b/addons/storyshots/storyshots-core/injectFileName.js index 2b2cd5ca7799..c8f946bab571 100644 --- a/addons/storyshots/storyshots-core/injectFileName.js +++ b/addons/storyshots/storyshots-core/injectFileName.js @@ -16,7 +16,7 @@ module.exports = { return `${code}; if(exports.default != null) { exports.default.parameters = exports.default.parameters || {}; - exports.default.parameters.fileName = '${fileName}'; + exports.default.parameters.fileName = '${fileName.replace(/\\/g, '\\\\')}'; } `; }, diff --git a/addons/storyshots/storyshots-core/package.json b/addons/storyshots/storyshots-core/package.json index 274a9876d8de..f047bd3e3966 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -33,11 +32,13 @@ }, "dependencies": { "@jest/transform": "^24.9.0", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", "@types/glob": "^7.1.1", "@types/jest": "^24.0.16", "@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", @@ -47,8 +48,8 @@ "ts-dedent": "^1.1.0" }, "devDependencies": { - "@storybook/addon-docs": "5.3.0-alpha.41", - "@storybook/react": "5.3.0-alpha.41", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/react": "6.0.0-alpha.2", "babel-loader": "^8.0.6", "enzyme-to-json": "^3.4.1", "jest-emotion": "^10.0.17", @@ -56,5 +57,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts b/addons/storyshots/storyshots-core/src/Stories2SnapsConverter.ts index a2514260446b..656290f30ce6 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', } ` ); diff --git a/addons/storyshots/storyshots-core/src/api/index.ts b/addons/storyshots/storyshots-core/src/api/index.ts index 855a157c8241..e77be5ede88f 100644 --- a/addons/storyshots/storyshots-core/src/api/index.ts +++ b/addons/storyshots/storyshots-core/src/api/index.ts @@ -12,11 +12,11 @@ type TestMethod = 'beforeAll' | 'beforeEach' | 'afterEach' | 'afterAll'; const methods: TestMethod[] = ['beforeAll', 'beforeEach', 'afterEach', 'afterAll']; function callTestMethodGlobals( - testMethod: { [key in TestMethod]?: Function } & { [key in string]: any } + testMethod: { [key in TestMethod]?: Function & { timeout?: number } } & { [key in string]: any } ) { methods.forEach(method => { if (typeof testMethod[method] === 'function') { - global[method](testMethod[method]); + global[method](testMethod[method], testMethod[method].timeout); } }); } diff --git a/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts b/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts index 6a3a9afcfb5c..4f3538d9a275 100644 --- a/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts +++ b/addons/storyshots/storyshots-core/src/api/integrityTestTemplate.ts @@ -1,6 +1,53 @@ +/* eslint-disable jest/no-export */ import fs from 'fs'; 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) => { + const possibleStoriesFiles = stories2snapsConverter.getPossibleStoriesFiles(fileName); + return !possibleStoriesFiles.some(fs.existsSync); + }); + + if (abandonedStoryshots.length === 0) { + return { pass: true, message: () => '' }; + } + + const formattedList = abandonedStoryshots.join('\n '); + + // See https://github.com/facebook/jest/issues/8732#issuecomment-516445064 + // eslint-disable-next-line no-underscore-dangle + const isUpdate = expect.getState().snapshotState._updateSnapshot === 'all'; + if (isUpdate) { + abandonedStoryshots.forEach((file: string) => fs.unlinkSync(file)); + // eslint-disable-next-line no-console + console.log(dedent` + Removed abandoned storyshots: + ${formattedList} + `); + return { pass: true, message: () => '' }; + } + + return { + pass: false, + message: () => dedent` + Found abandoned storyshots. Run jest with -u to remove them: + ${formattedList} + `, + }; + }, +}); function integrityTest(integrityOptions: any, stories2snapsConverter: any) { if (integrityOptions === false) { @@ -12,12 +59,7 @@ function integrityTest(integrityOptions: any, stories2snapsConverter: any) { const snapshotExtension = stories2snapsConverter.getSnapshotExtension(); const storyshots = glob.sync(`**/*${snapshotExtension}`, integrityOptions); - const abandonedStoryshots = storyshots.filter(fileName => { - const possibleStoriesFiles = stories2snapsConverter.getPossibleStoriesFiles(fileName); - return !possibleStoriesFiles.some(fs.existsSync); - }); - - expect(abandonedStoryshots).toEqual([]); + 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 2dbdf03861b4..55f197980b50 100644 --- a/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts +++ b/addons/storyshots/storyshots-core/src/api/snapshotsTestsTemplate.ts @@ -1,3 +1,5 @@ +/* eslint-disable jest/no-export */ +/* eslint-disable jest/expect-expect */ import { describe, it } from 'global'; import { addSerializer } from 'jest-specific-snapshot'; @@ -6,30 +8,36 @@ function snapshotTest({ item, asyncJest, framework, testMethod, testMethodParams const context = { ...item, framework }; if (asyncJest === true) { - it(name, (done: jest.DoneCallback) => - testMethod({ - done, - story: item, - context, - ...testMethodParams, - }) + it( + name, + () => + new Promise(done => + testMethod({ + done, + story: item, + context, + ...testMethodParams, + }) + ), + testMethod.timeout ); } else { - it(name, () => - testMethod({ - story: item, - context, - ...testMethodParams, - }) + it( + name, + () => + testMethod({ + story: item, + context, + ...testMethodParams, + }), + testMethod.timeout ); } } function snapshotTestSuite({ item, suite, ...restParams }: any) { const { kind, children } = item; - // eslint-disable-next-line jest/valid-describe describe(suite, () => { - // eslint-disable-next-line jest/valid-describe describe(kind, () => { children.forEach((c: any) => { snapshotTest({ item: c, ...restParams }); diff --git a/addons/storyshots/storyshots-core/src/frameworks/Loader.ts b/addons/storyshots/storyshots-core/src/frameworks/Loader.ts index cade940022e5..501f0fbac20a 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/Loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/Loader.ts @@ -1,9 +1,19 @@ -import { ClientApi } from '@storybook/client-api'; +import { ClientStoryApi, Loadable } from '@storybook/addons'; +import { ClientApi as ClientApiThing } from '@storybook/client-api'; import { StoryshotsOptions } from '../api/StoryshotsOptions'; import { SupportedFramework } from './SupportedFramework'; export type RenderTree = (story: any, context?: any, options?: any) => any; +export interface ClientApi extends ClientStoryApi<unknown> { + configure(loader: Loadable, module: NodeModule | false): void; + forceReRender(): void; + clearDecorators: ClientApiThing['clearDecorators']; + getStorybook: ClientApiThing['getStorybook']; + setAddon: ClientApiThing['setAddon']; + raw: ClientApiThing['raw']; +} + export interface Loader { load: ( options: StoryshotsOptions 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/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts index 3a0c78bf643f..ea3a5fbfbaa6 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/angular/loader.ts @@ -29,10 +29,9 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { setupAngularJestPreset(); - const { configPath, config } = options; const storybook = require.requireActual('@storybook/angular'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'angular' as const, diff --git a/addons/storyshots/storyshots-core/src/frameworks/configure.ts b/addons/storyshots/storyshots-core/src/frameworks/configure.ts index 9151a46562a0..fd2689eb27d2 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/configure.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/configure.ts @@ -1,17 +1,98 @@ import fs from 'fs'; import path from 'path'; +import { toRequireContext } from '@storybook/core/server'; +import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; +import global from 'global'; +import { ClientApi } from './Loader'; +import { StoryshotsOptions } from '../api/StoryshotsOptions'; -function getConfigPathParts(configPath: string): string { - const resolvedConfigPath = path.resolve(configPath); +registerRequireContextHook(); - if (fs.lstatSync(resolvedConfigPath).isDirectory()) { - return path.join(resolvedConfigPath, 'config'); +const isFile = (file: string): boolean => { + try { + return fs.lstatSync(file).isFile(); + } catch (e) { + return false; } +}; - return resolvedConfigPath; +interface Output { + stories: string[]; + files: string[]; } -function configure(options: { configPath?: string; config: any; storybook: any }): void { +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; + } + + return false; +}; + +const getMainFile = (configDir: string): string | false => { + const main = path.join(configDir, 'main.js'); + + if (isFile(main)) { + return main; + } + + return false; +}; + +function getConfigPathParts(input: string): Output { + const configDir = path.resolve(input); + + if (fs.lstatSync(configDir).isDirectory()) { + const output: Output = { files: [], stories: [] }; + + const preview = getPreviewFile(configDir); + const main = getMainFile(configDir); + + if (preview) { + output.files.push(preview); + } + if (main) { + const { stories = [] } = require.requireActual(main); + + output.stories = stories.map( + (pattern: string | { path: string; recursive: boolean; match: string }) => { + const { path: basePath, recursive, match } = toRequireContext(pattern); + // eslint-disable-next-line no-underscore-dangle + return global.__requireContext( + configDir, + basePath, + recursive, + new RegExp(match.slice(1, -1)) + ); + } + ); + } + + return output; + } + + return { files: [configDir], stories: [] }; +} + +function configure( + options: { + storybook: ClientApi; + } & StoryshotsOptions +): void { const { configPath = '.storybook', config, storybook } = options; if (config && typeof config === 'function') { @@ -19,9 +100,15 @@ function configure(options: { configPath?: string; config: any; storybook: any } return; } - const resolvedConfigPath = getConfigPathParts(configPath); + const { files, stories } = getConfigPathParts(configPath); - require.requireActual(resolvedConfigPath); + files.forEach(f => { + require.requireActual(f); + }); + + if (stories && stories.length) { + storybook.configure(stories, false); + } } export default configure; diff --git a/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts b/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts index 10ea02cf33b4..2a09368902c3 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/frameworkLoader.ts @@ -4,7 +4,7 @@ import path from 'path'; import { Loader } from './Loader'; import { StoryshotsOptions } from '../api/StoryshotsOptions'; -const loaderScriptName = 'loader'; +const loaderScriptName = 'loader.js'; const isDirectory = (source: string) => fs.lstatSync(source).isDirectory(); @@ -13,16 +13,7 @@ function getLoaders(): Loader[] { .readdirSync(__dirname) .map(name => path.join(__dirname, name)) .filter(isDirectory) - .reduce( - (acc, framework) => { - const filename = path.join(framework, loaderScriptName); - const jsFile = `${filename}.js`; - const tsFile = `${filename}.ts`; - - return acc.concat([jsFile, tsFile]); - }, - [] as string[] - ) + .map(framework => path.join(framework, loaderScriptName)) .filter(fs.existsSync) .map(loader => require(loader).default); } diff --git a/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts index 08108195547b..5a720e978d3a 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/html/loader.ts @@ -10,10 +10,9 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'html'; - const { configPath, config } = options; const storybook = require.requireActual('@storybook/html'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'html' as const, diff --git a/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts index 7b579fc26c22..09eaa79a0698 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/preact/loader.ts @@ -15,10 +15,9 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'preact'; - const { configPath, config } = options; const storybook = require.requireActual('@storybook/preact'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'preact' as const, 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..82423e377ebe --- /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 = require.requireActual('@storybook/rax'); + + configure({ ...options, storybook }); + + return { + framework: 'rax' as const, + renderTree: require.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/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts index 0d6cf164f64f..54383dce00f0 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react/loader.ts @@ -8,10 +8,9 @@ function test(options: StoryshotsOptions): boolean { } function load(options: StoryshotsOptions) { - const { configPath, config } = options; const storybook = require.requireActual('@storybook/react'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'react' as const, diff --git a/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts index abbf19a54de5..3223c5cf3895 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/riot/loader.ts @@ -16,10 +16,9 @@ function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'riot'; mockRiotToIncludeCompiler(); - const { configPath, config } = options; const storybook = require.requireActual('@storybook/riot'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'riot' as const, diff --git a/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts index a1b9203e7b5b..4ae5b02acfd3 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/svelte/loader.ts @@ -13,10 +13,9 @@ function test(options: StoryshotsOptions): boolean { function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'svelte'; - const { configPath, config } = options; const storybook = require.requireActual('@storybook/svelte'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'svelte' as const, diff --git a/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts index abba41352e9b..2433026458a5 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/vue/loader.ts @@ -16,10 +16,9 @@ function load(options: StoryshotsOptions) { global.STORYBOOK_ENV = 'vue'; mockVueToIncludeCompiler(); - const { configPath, config } = options; const storybook = require.requireActual('@storybook/vue'); - configure({ configPath, config, storybook }); + configure({ ...options, storybook }); return { framework: 'vue' as const, diff --git a/addons/storyshots/storyshots-core/src/typings.d.ts b/addons/storyshots/storyshots-core/src/typings.d.ts index 46e3fd86ed8f..3f7c7d2cc952 100644 --- a/addons/storyshots/storyshots-core/src/typings.d.ts +++ b/addons/storyshots/storyshots-core/src/typings.d.ts @@ -2,3 +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 4bd1f88f130b..75ec281cdcdc 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 @@ -53,13 +53,13 @@ exports[`Storyshots Another Button with text 1`] = ` </Button> `; -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <AsyncTestComponent> <h1 /> </AsyncTestComponent> `; -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <Button onClick={[Function]} > @@ -78,17 +78,12 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> </Button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <Button onClick={[Function]} > @@ -107,12 +102,17 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> </Button> `; -exports[`Storyshots Welcome MDX To Storybook 1`] = ` +exports[`Storyshots Welcome MDX to Storybook 1`] = ` <Welcome showApp={[Function]} > @@ -316,7 +316,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = ` </Welcome> `; -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <Welcome showApp={[Function]} > 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 a0c27ed2b410..445c7ba9a4fa 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 @@ -45,13 +45,13 @@ exports[`Storyshots Another Button with text 1`] = ` </button> `; -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <h1> </h1> `; -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <button onClick={[Function]} style={ @@ -67,16 +67,11 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <button onClick={[Function]} style={ @@ -92,11 +87,16 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> `; -exports[`Storyshots Welcome MDX To Storybook 1`] = ` +exports[`Storyshots Welcome MDX to Storybook 1`] = ` <Main> <Title> Welcome to storybook @@ -170,7 +170,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = ` </Main> `; -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <Main> <Title> Welcome to storybook 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 a0c27ed2b410..445c7ba9a4fa 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 @@ -45,13 +45,13 @@ exports[`Storyshots Another Button with text 1`] = ` </button> `; -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <h1> </h1> `; -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <button onClick={[Function]} style={ @@ -67,16 +67,11 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <button onClick={[Function]} style={ @@ -92,11 +87,16 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> `; -exports[`Storyshots Welcome MDX To Storybook 1`] = ` +exports[`Storyshots Welcome MDX to Storybook 1`] = ` <Main> <Title> Welcome to storybook @@ -170,7 +170,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = ` </Main> `; -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <Main> <Title> Welcome to storybook 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 15dea97c439f..30257648479e 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 @@ -45,13 +45,13 @@ exports[`Storyshots Another Button with text 1`] = ` </button> `; -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <h1> </h1> `; -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <button onClick={[Function]} style={ @@ -67,16 +67,11 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <button onClick={[Function]} style={ @@ -92,11 +87,16 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> `; -exports[`Storyshots Welcome MDX To Storybook 1`] = ` +exports[`Storyshots Welcome MDX to Storybook 1`] = ` <article style={ Object { @@ -270,7 +270,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = ` </article> `; -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <article style={ Object { diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.async.storyshot b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.async.storyshot index 2b0ad426c6cd..bb2f1c611268 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.async.storyshot +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.async.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <AsyncTestComponent> <h1> THIS IS SO DONE diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.foo b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.foo index 63102b6aa4f4..d4459afef802 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.foo +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.foo @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <h1> </h1> diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.storyshot b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.storyshot index 63102b6aa4f4..d4459afef802 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.storyshot +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Async.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Async With Timeout 1`] = ` +exports[`Storyshots Async with 5ms timeout simulating async operation 1`] = ` <h1> </h1> diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.foo b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.foo index 0c782e5eb37f..47dabe9763f5 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.foo +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.foo @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <button onClick={[Function]} style={ @@ -16,16 +16,11 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <button onClick={[Function]} style={ @@ -41,6 +36,11 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> `; diff --git a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.storyshot b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.storyshot index 0c782e5eb37f..47dabe9763f5 100644 --- a/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.storyshot +++ b/addons/storyshots/storyshots-core/stories/required_with_context/__snapshots__/Button.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Button With Some Emoji 1`] = ` +exports[`Storyshots Button With Text 1`] = ` <button onClick={[Function]} style={ @@ -16,16 +16,11 @@ exports[`Storyshots Button With Some Emoji 1`] = ` } type="button" > - <span - aria-label="so cool" - role="img" - > - 😀 😎 👍 💯 - </span> + Hello Button </button> `; -exports[`Storyshots Button With Text 1`] = ` +exports[`Storyshots Button with some emoji 1`] = ` <button onClick={[Function]} style={ @@ -41,6 +36,11 @@ exports[`Storyshots Button With Text 1`] = ` } type="button" > - Hello Button + <span + aria-label="so cool" + role="img" + > + 😀 😎 👍 💯 + </span> </button> `; 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 7cb2b61c5aee..4662f49c8761 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 @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <article style={ Object { 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 9084215a29cd..97712dfd14e0 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 @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Welcome MDX To Storybook 1`] = ` +exports[`Storyshots Welcome MDX to Storybook 1`] = ` <article style={ Object { @@ -174,7 +174,7 @@ exports[`Storyshots Welcome MDX To Storybook 1`] = ` </article> `; -exports[`Storyshots Welcome To Storybook 1`] = ` +exports[`Storyshots Welcome to Storybook 1`] = ` <article style={ Object { diff --git a/addons/storyshots/storyshots-core/stories/storyshot.async.test.js b/addons/storyshots/storyshots-core/stories/storyshot.async.test.js index 6075c381b928..61d449f06c74 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.async.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.async.test.js @@ -1,7 +1,7 @@ import path from 'path'; import { mount } from 'enzyme'; import toJson from 'enzyme-to-json'; -import initStoryshots, { Stories2SnapsConverter } from '../src'; +import initStoryshots, { Stories2SnapsConverter } from '../dist'; import { TIMEOUT, EXPECTED_VALUE } from './required_with_context/Async.stories'; initStoryshots({ diff --git a/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js b/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js index 5e26c0b29f4e..d5e9116c05e6 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.configFunc.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { multiSnapshotWithOptions, Stories2SnapsConverter } from '../src'; +import initStoryshots, { multiSnapshotWithOptions, Stories2SnapsConverter } from '../dist'; class AnotherStories2SnapsConverter extends Stories2SnapsConverter { getSnapshotFileName(context) { diff --git a/addons/storyshots/storyshots-core/stories/storyshot.enzyme.test.js b/addons/storyshots/storyshots-core/stories/storyshot.enzyme.test.js index 26afc22fd796..034a85bc5e2e 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.enzyme.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.enzyme.test.js @@ -2,7 +2,7 @@ import path from 'path'; import { mount } from 'enzyme'; import { createSerializer as enzymeSerializer } from 'enzyme-to-json'; import { createSerializer as emotionSerializer } from 'jest-emotion'; -import initStoryshots from '../src'; +import initStoryshots from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.renderOnly.test.js b/addons/storyshots/storyshots-core/stories/storyshot.renderOnly.test.js index e620764ed585..90d551878bfb 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.renderOnly.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.renderOnly.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { renderOnly } from '../src'; +import initStoryshots, { renderOnly } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.renderWithOptions.test.js b/addons/storyshots/storyshots-core/stories/storyshot.renderWithOptions.test.js index d2444b054b75..e9102a2df547 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.renderWithOptions.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.renderWithOptions.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { renderWithOptions } from '../src'; +import initStoryshots, { renderWithOptions } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.shallow.test.js b/addons/storyshots/storyshots-core/stories/storyshot.shallow.test.js index ade88a49a696..c51d0698fdd3 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.shallow.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.shallow.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { shallowSnapshot } from '../src'; +import initStoryshots, { shallowSnapshot } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js b/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js index 45bbf1e23a40..6cd8a9a684e4 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.shallowWithOptions.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { shallowSnapshot } from '../src'; +import initStoryshots, { shallowSnapshot } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.snapshotWithOptionsFunction.test.js b/addons/storyshots/storyshots-core/stories/storyshot.snapshotWithOptionsFunction.test.js index da0bfe8f8628..a75c443e65ab 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.snapshotWithOptionsFunction.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.snapshotWithOptionsFunction.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { snapshotWithOptions } from '../src'; +import initStoryshots, { snapshotWithOptions } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.specificConfig.test.js b/addons/storyshots/storyshots-core/stories/storyshot.specificConfig.test.js index 15744bd86fca..62e315b097c8 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.specificConfig.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.specificConfig.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { multiSnapshotWithOptions, Stories2SnapsConverter } from '../src'; +import initStoryshots, { multiSnapshotWithOptions, Stories2SnapsConverter } from '../dist'; initStoryshots({ framework: 'react', diff --git a/addons/storyshots/storyshots-core/stories/storyshot.test.js b/addons/storyshots/storyshots-core/stories/storyshot.test.js index 8cfdf6c7a357..638cc13a898a 100644 --- a/addons/storyshots/storyshots-core/stories/storyshot.test.js +++ b/addons/storyshots/storyshots-core/stories/storyshot.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import initStoryshots, { multiSnapshotWithOptions } from '../src'; +import initStoryshots, { multiSnapshotWithOptions } from '../dist'; jest.mock('@storybook/node-logger'); diff --git a/addons/storyshots/storyshots-puppeteer/README.md b/addons/storyshots/storyshots-puppeteer/README.md index 6a8d87d530d9..cb4feceddb20 100644 --- a/addons/storyshots/storyshots-puppeteer/README.md +++ b/addons/storyshots/storyshots-puppeteer/README.md @@ -1,33 +1,52 @@ +# StoryShots + [Puppeteer](https://github.com/GoogleChrome/puppeteer) + ## Getting Started -Add the following module into your app. +Add the following modules into your app. ```sh -npm install @storybook/addon-storyshots-puppeteer --save-dev +npm install @storybook/addon-storyshots-puppeteer puppeteer --save-dev ``` -## Configure Storyshots for image snapshots +⚠️ As of Storybook 5.3 `puppeteer` is no more included in addon dependencies and must be added to your project directly. -/\*\ **React-native** is **not supported** by this test function. +## Configure Storyshots for Puppeteeer tests -Internally, it uses [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot). +⚠️ **React-native** is **not supported** by this test function. -When willing to generate and compare image snapshots for your stories, you have two options: +When willing to run Puppeteer tests for your stories, you have two options: - Have a storybook running (ie. accessible via http(s), for instance using `npm run storybook`) - Have a static build of the storybook (for instance, using `npm run build-storybook`) Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served) -### Using default values for _imageSnapshots_ +## _puppeteerTest_ +Allows to define arbitrary Puppeteer tests as `story.parameters.puppeteerTest` function. -Then you can either create a new Storyshots instance or edit the one you previously used: +You can either create a new Storyshots instance or edit the one you previously used: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; -initStoryshots({ suite: 'Image storyshots', test: imageSnapshot() }); +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); + }, + }, +}; ``` This will assume you have a storybook running on at _<http://localhost:6006>_. @@ -35,7 +54,7 @@ Internally here are the steps: - Launches a Chrome headless using [puppeteer](https://github.com/GoogleChrome/puppeteer) - Browses each stories (calling _<http://localhost:6006/iframe.html?...>_ URL), -- Take screenshots & save all images under \_\_image_snapshots\_\_ folder. +- Runs the `parameters.puppeteerTest` function if it's defined. ### Specifying the storybook URL @@ -43,59 +62,29 @@ If you want to set specific storybook URL, you can specify via the `storybookUrl ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://my-specific-domain.com:9010' }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://my-specific-domain.com:9010' }), }); ``` -The above config will use _<https://my-specific-domain.com:9010>_ for screenshots. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`). +The above config will use _<https://my-specific-domain.com:9010>_ for tests. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`). You may also use a local static build of storybook if you do not want to run the webpack dev-server: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; - -initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'file:///path/to/my/storybook-static' }), -}); -``` - -### Specifying options to _jest-image-snapshots_ +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; -If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured. - -```js -import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -const getMatchOptions = ({ context: { kind, story }, url }) => { - return { - failureThreshold: 0.2, - failureThresholdType: 'percent', - }; -}; -const beforeScreenshot = (page, { context: { kind, story }, url }) => { - return new Promise(resolve => - setTimeout(() => { - resolve(); - }, 600) - ); -}; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'file:///path/to/my/storybook-static' }), }); ``` -`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. - -`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations. - -### Specifying options to _goto()_ (puppeteer API) +### Specifying options to _goto()_ (Puppeteer API) You might use `getGotoOptions` to specify options when the storybook is navigating to a story (using the `goto` method). Will be passed to [Puppeteer .goto() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options) @@ -108,63 +97,42 @@ const getGotoOptions = ({ context, url }) => { }; }; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getGotoOptions }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', getGotoOptions }), }); ``` -### Specifying options to _screenshot()_ (puppeteer API) - -You might use `getScreenshotOptions` to specify options for screenshot. Will be passed to [Puppeteer .screenshot() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) - -```js -import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -const getScreenshotOptions = ({ context, url }) => { - return { - encoding: 'base64', // encoding: 'base64' is a property required by puppeteer - fullPage: false, // Do not take the full page screenshot. Default is 'true' in Storyshots., - }; -}; -initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getScreenshotOptions }), -}); -``` - -`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. - -### Specifying custom Chrome executable path (puppeteer API) +### Specifying custom Chrome executable path (Puppeteer API) You might use `chromeExecutablePath` to specify the path to a different version of Chrome, without downloading Chromium. Will be passed to [Runs a bundled version of Chromium](https://github.com/GoogleChrome/puppeteer#default-runtime-settings) ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; const chromeExecutablePath = '/usr/local/bin/chrome'; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', chromeExecutablePath }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', chromeExecutablePath }), }); ``` -### Specifying a custom puppeteer `browser` instance +### Specifying a custom Puppeteer `browser` instance -You might use the async `getCustomBrowser` function to obtain a custom instance of a puppeteer `browser` object. This will prevent `storyshots-puppeteer` from creating its own `browser`. It will create and close pages within the `browser`, and it is your responsibility to manage the lifecycle of the `browser` itself. +You might use the async `getCustomBrowser` function to obtain a custom instance of a Puppeteer `browser` object. This will prevent `storyshots-puppeteer` from creating its own `browser`. It will create and close pages within the `browser`, and it is your responsibility to manage the lifecycle of the `browser` itself. ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; import puppeteer from 'puppeteer'; (async function() { initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', - getCustomBrowser: async () => puppeteer.connect({ browserWSEndpoint: 'ws://yourUrl' }), + getCustomBrowser: () => puppeteer.connect({ browserWSEndpoint: 'ws://yourUrl' }), }), }); })(); @@ -178,7 +146,7 @@ An example of device emulation: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; const devices = require('puppeteer/DeviceDescriptors'); const iPhone = devices['iPhone 6']; @@ -188,20 +156,25 @@ function customizePage(page) { } initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', customizePage, }), }); ``` -### Integrate image storyshots with regular app +### Specifying setup and tests timeout -You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served. +By default, `@storybook/addon-storyshots-puppeteer` uses 15 second timeouts for browser setup and test functions. +Those can be customized with `setupTimeout` and `testTimeout` parameters. + +### Integrate Puppeteer storyshots with regular app + +You may want to use another Jest project to run your Puppeteer storyshots as they require more resources: Chrome and Storybook built/served. You can find a working example of this in the [official-storybook](https://github.com/storybookjs/storybook/tree/master/examples/official-storybook) example. -### Integrate image storyshots with [Create React App](https://github.com/facebookincubator/create-react-app) +### Integrate Puppeteer storyshots with [Create React App](https://github.com/facebookincubator/create-react-app) You have two options here, you can either: @@ -209,30 +182,114 @@ You have two options here, you can either: - Create a custom test file using Jest outside of the CRA scope: - A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run storyshots with image snapshots. - This use case can be achieved by using a custom name for the test file, ie something like `image-storyshots.runner.js`. This file will contains the `initStoryshots` call with image snapshots configuration. + A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run Puppeteer storyshots. + This use case can be achieved by using a custom name for the test file, ie something like `puppeteer-storyshots.runner.js`. This file will contain the `initStoryshots` call with Puppeteer storyshots configuration. Then you will create a separate script entry in your package.json, for instance ```json { "scripts": { - "image-snapshots": "jest image-storyshots.runner.js --config path/to/custom/jest.config.json" + "puppeteer-storyshots": "jest puppeteer-storyshots.runner.js --config path/to/custom/jest.config.json" } } ``` Note that you will certainly need a custom config file for Jest as you run it outside of the CRA scope and thus you do not have the built-in config. - Once that's setup, you can run `npm run image-snapshots`. + Once that's setup, you can run `npm run puppeteer-storyshots`. ### Reminder -An image snapshot is a screenshot taken by a web browser (in our case, Chrome). +Puppeteer launches a web browser (Chrome) internally. The browser opens a page (either using the static build of storybook or a running instance of Storybook) If you run your test without either the static build or a running instance, this wont work. -To make sure your screenshots are taken from latest changes of your Storybook, you must keep your static build or running Storybook up-to-date. +To make sure your tests run against the latest changes of your Storybook, you must keep your static build or running Storybook up-to-date. This can be achieved by adding a step before running the test ie: `npm run build-storybook && npm run image-snapshots`. -If you run the image snapshots against a running Storybook in dev mode, you don't have to worry about the snapshots being up-to-date because the dev-server is watching changes and rebuilds automatically. +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() }); +``` + +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 +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; + +initStoryshots({ suite: 'Image storyshots', test: imageSnapshot() }); +``` + +It saves all images under \_\_image_snapshots\_\_ folder. + +### Specifying options to _jest-image-snapshots_ + +If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured and a `afterScreenshot` handler which is called after the screenshot and receives the just created image. + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +const getMatchOptions = ({ context: { kind, story }, url }) => { + return { + failureThreshold: 0.2, + failureThresholdType: 'percent', + }; +}; +const beforeScreenshot = (page, { context: { kind, story }, url }) => { + return new Promise(resolve => + setTimeout(() => { + resolve(); + }, 600) + ); +}; +const afterScreenshot = ({ image, context }) => { + return new Promise(resolve => + setTimeout(() => { + resolve(); + }, 600) + ); +}; +initStoryshots({ + suite: 'Image storyshots', + test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot, afterScreenshot }), +}); +``` + +`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. + +`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations. + +`afterScreenshot` receives the created image from puppeteer. + +### Specifying options to _screenshot()_ (Puppeteer API) + +You might use `getScreenshotOptions` to specify options for screenshot. Will be passed to [Puppeteer .screenshot() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +const getScreenshotOptions = ({ context, url }) => { + return { + encoding: 'base64', // encoding: 'base64' is a property required by puppeteer + fullPage: false, // Do not take the full page screenshot. Default is 'true' in Storyshots., + }; +}; +initStoryshots({ + suite: 'Image storyshots', + test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getScreenshotOptions }), +}); +``` + +`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index 7903744d1fad..80ec3e71f51b 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "Image snapshots addition to StoryShots based on puppeteer", "keywords": [ "addon", @@ -18,7 +18,6 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" @@ -29,19 +28,29 @@ "prepare": "node ../../../scripts/prepare.js" }, "dependencies": { - "@storybook/node-logger": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", + "@storybook/csf": "0.0.1", + "@storybook/node-logger": "6.0.0-alpha.2", "@types/jest-image-snapshot": "^2.8.0", - "@types/puppeteer-core": "^1.9.0", + "@wordpress/jest-puppeteer-axe": "^1.5.0", "core-js": "^3.0.1", "jest-image-snapshot": "^2.8.2", - "puppeteer-core": "^2.0.0", "regenerator-runtime": "^0.13.3" }, + "devDependencies": { + "@storybook/csf": "0.0.1", + "@types/puppeteer": "^2.0.0" + }, "peerDependencies": { - "@storybook/addon-storyshots": "5.3.0-alpha.13" + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "puppeteer": "^1.12.2 || ^2.0.0" + }, + "peerDependenciesMeta": { + "puppeteer": { + "optional": true + } }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/storyshots/storyshots-puppeteer/src/ImageSnapshotConfig.ts b/addons/storyshots/storyshots-puppeteer/src/ImageSnapshotConfig.ts deleted file mode 100644 index 47962b66ae21..000000000000 --- a/addons/storyshots/storyshots-puppeteer/src/ImageSnapshotConfig.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MatchImageSnapshotOptions } from 'jest-image-snapshot'; -import { Base64ScreenShotOptions, Browser, DirectNavigationOptions, Page } from 'puppeteer-core'; - -export interface Context { - kind: string; - story: string; -} - -export interface ImageSnapshotConfig { - storybookUrl: string; - chromeExecutablePath: string; - getMatchOptions: (options: { context: Context; url: string }) => MatchImageSnapshotOptions; - getScreenshotOptions: (options: { context: Context; url: string }) => Base64ScreenShotOptions; - beforeScreenshot: (page: Page, options: { context: Context; url: string }) => void; - getGotoOptions: (options: { context: Context; url: string }) => DirectNavigationOptions; - customizePage: (page: Page) => Promise<void>; - getCustomBrowser: () => Promise<Browser>; -} 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 new file mode 100644 index 000000000000..02ad85001608 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts @@ -0,0 +1,23 @@ +import '@wordpress/jest-puppeteer-axe'; +import { defaultCommonConfig, CommonConfig } from './config'; +import { puppeteerTest } from './puppeteerTest'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace,no-redeclare + namespace jest { + interface Matchers<R, T> { + toPassAxeTests(parameters: any): R; + } + } +} + +export const axeTest = (customConfig: Partial<CommonConfig> = {}) => + puppeteerTest({ + ...defaultCommonConfig, + ...customConfig, + async testBody(page, options) { + const parameters = options.context.parameters.a11y; + const include = parameters?.element ?? '#root'; + await expect(page).toPassAxeTests({ ...parameters, include }); + }, + }); diff --git a/addons/storyshots/storyshots-puppeteer/src/config.ts b/addons/storyshots/storyshots-puppeteer/src/config.ts new file mode 100644 index 000000000000..a421ea029992 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/config.ts @@ -0,0 +1,78 @@ +import { MatchImageSnapshotOptions } from 'jest-image-snapshot'; +import { Base64ScreenShotOptions, Browser, DirectNavigationOptions, Page } from 'puppeteer'; + +export interface Context { + kind: string; + story: string; + parameters: { + [key: string]: any; + }; +} + +interface Options { + context: Context; + url: string; +} + +export interface CommonConfig { + storybookUrl: string; + chromeExecutablePath: string; + getGotoOptions: (options: Options) => DirectNavigationOptions; + customizePage: (page: Page) => Promise<void>; + getCustomBrowser: () => Promise<Browser>; + setupTimeout: number; + testTimeout: number; +} + +export interface PuppeteerTestConfig extends CommonConfig { + testBody: ((page: Page, options: Options) => void | Promise<void>) & { + filter?: (options: Options) => boolean; + }; +} + +export interface ImageSnapshotConfig extends CommonConfig { + getMatchOptions: (options: Options) => MatchImageSnapshotOptions; + getScreenshotOptions: (options: Options) => Base64ScreenShotOptions; + beforeScreenshot: (page: Page, options: Options) => void; + afterScreenshot: (options: { image: string; context: Context }) => void; +} + +const noop: () => undefined = () => undefined; +const asyncNoop: () => Promise<undefined> = async () => undefined; + +export const defaultCommonConfig: CommonConfig = { + storybookUrl: 'http://localhost:6006', + chromeExecutablePath: undefined, + getGotoOptions: noop, + customizePage: asyncNoop, + getCustomBrowser: undefined, + setupTimeout: 15000, + testTimeout: 15000, +}; + +const getTestBody = (options: Options) => options.context.parameters.puppeteerTest; + +function defaultTestBody(page: Page, options: Options) { + const testBody = getTestBody(options); + if (testBody != null) { + return testBody(page, options); + } + return null; +} + +defaultTestBody.filter = (options: Options) => getTestBody(options) != null; + +export const defaultPuppeteerTestConfig: PuppeteerTestConfig = { + ...defaultCommonConfig, + testBody: defaultTestBody, +}; + +// We consider taking the full page is a reasonable default. +const defaultScreenshotOptions = () => ({ fullPage: true, encoding: 'base64' } as const); +export const defaultImageSnapshotConfig: ImageSnapshotConfig = { + ...defaultCommonConfig, + getMatchOptions: noop, + getScreenshotOptions: defaultScreenshotOptions, + beforeScreenshot: noop, + afterScreenshot: noop, +}; diff --git a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts index 4bee56d4ce4a..e8cceac61523 100644 --- a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts +++ b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts @@ -1,102 +1,21 @@ -import puppeteer, { Browser, Page } from 'puppeteer-core'; import { toMatchImageSnapshot } from 'jest-image-snapshot'; -import { logger } from '@storybook/node-logger'; -import { constructUrl } from './url'; -import { ImageSnapshotConfig } from './ImageSnapshotConfig'; +import { defaultImageSnapshotConfig, ImageSnapshotConfig } from './config'; +import { puppeteerTest } from './puppeteerTest'; expect.extend({ toMatchImageSnapshot }); -// We consider taking the full page is a reasonable default. -const defaultScreenshotOptions = () => ({ fullPage: true, encoding: 'base64' } as const); - -const noop: () => undefined = () => undefined; -const asyncNoop: () => Promise<undefined> = async () => undefined; - -const defaultConfig: ImageSnapshotConfig = { - storybookUrl: 'http://localhost:6006', - chromeExecutablePath: undefined, - getMatchOptions: noop, - getScreenshotOptions: defaultScreenshotOptions, - beforeScreenshot: noop, - getGotoOptions: noop, - customizePage: asyncNoop, - getCustomBrowser: undefined, -}; - export const imageSnapshot = (customConfig: Partial<ImageSnapshotConfig> = {}) => { - const { - storybookUrl, - chromeExecutablePath, - getMatchOptions, - getScreenshotOptions, - beforeScreenshot, - getGotoOptions, - customizePage, - getCustomBrowser, - } = { ...defaultConfig, ...customConfig }; - - let browser: Browser; // holds ref to browser. (ie. Chrome) - let page: Page; // Hold ref to the page to screenshot. - - const testFn = async ({ context }: any) => { - const { kind, framework, name } = context; - if (framework === 'react-native') { - // Skip tests since we de not support RN image snapshots. - logger.error( - "It seems you are running imageSnapshot on RN app and it's not supported. Skipping test." - ); - - return; - } - const url = constructUrl(storybookUrl, kind, name); - - if (!browser || !page) { - logger.error( - `Error when generating image snapshot for test ${kind} - ${name} : It seems the headless browser is not running.` - ); - - throw new Error('no-headless-browser-running'); - } - - expect.assertions(1); - - let image; - try { - await customizePage(page); - await page.goto(url, getGotoOptions({ context, url })); - await beforeScreenshot(page, { context, url }); - image = await page.screenshot(getScreenshotOptions({ context, url })); - } catch (e) { - logger.error( - `Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using image snapshot feature.` - ); - throw e; - } - - expect(image).toMatchImageSnapshot(getMatchOptions({ context, url })); - }; - - testFn.afterAll = () => { - if (getCustomBrowser && page) { - return page.close(); - } - - return browser.close(); - }; - - testFn.beforeAll = async () => { - if (getCustomBrowser) { - browser = await getCustomBrowser(); - } else { - // add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507 - browser = await puppeteer.launch({ - args: ['--no-sandbox ', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], - executablePath: chromeExecutablePath, - }); - } - - page = await browser.newPage(); - }; - - return testFn; + const config = { ...defaultImageSnapshotConfig, ...customConfig }; + const { getMatchOptions, getScreenshotOptions, beforeScreenshot, afterScreenshot } = config; + + return puppeteerTest({ + ...config, + async testBody(page, options) { + expect.assertions(1); + await beforeScreenshot(page, options); + const image = await page.screenshot(getScreenshotOptions(options)); + await afterScreenshot({ image, context: options.context }); + expect(image).toMatchImageSnapshot(getMatchOptions(options)); + }, + }); }; diff --git a/addons/storyshots/storyshots-puppeteer/src/index.ts b/addons/storyshots/storyshots-puppeteer/src/index.ts index f5c722b4283c..dd2e0b715fee 100644 --- a/addons/storyshots/storyshots-puppeteer/src/index.ts +++ b/addons/storyshots/storyshots-puppeteer/src/index.ts @@ -1,2 +1,4 @@ -export * from './ImageSnapshotConfig'; +export * from './config'; +export * from './puppeteerTest'; +export * from './axeTest'; export * from './imageSnapshot'; diff --git a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts new file mode 100644 index 000000000000..fbdd5233e5c1 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts @@ -0,0 +1,92 @@ +import { Browser, Page } from 'puppeteer'; +import { logger } from '@storybook/node-logger'; +import { constructUrl } from './url'; +import { defaultPuppeteerTestConfig, PuppeteerTestConfig } from './config'; + +export const puppeteerTest = (customConfig: Partial<PuppeteerTestConfig> = {}) => { + const { + storybookUrl, + chromeExecutablePath, + getGotoOptions, + customizePage, + getCustomBrowser, + testBody, + setupTimeout, + testTimeout, + } = { ...defaultPuppeteerTestConfig, ...customConfig }; + + let browser: Browser; // holds ref to browser. (ie. Chrome) + let page: Page; // Hold ref to the page to screenshot. + + const testFn = async ({ context }: any) => { + const { kind, framework, name, id } = context; + if (framework === 'react-native') { + // Skip tests since RN is not a browser environment. + logger.error( + "It seems you are running puppeteer test on RN app and it's not supported. Skipping test." + ); + + return; + } + + const url = constructUrl(storybookUrl, id); + const options = { context, url }; + if (testBody.filter != null && !testBody.filter(options)) { + return; + } + + if (!browser || !page) { + logger.error( + `Error when running puppeteer test for ${kind} - ${name} : It seems the headless browser is not running.` + ); + + throw new Error('no-headless-browser-running'); + } + + try { + await customizePage(page); + await page.goto(url, getGotoOptions(options)); + } catch (e) { + logger.error( + `Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using puppeteer test feature.` + ); + throw e; + } + await testBody(page, options); + }; + testFn.timeout = testTimeout; + + const cleanup = async () => { + if (getCustomBrowser && page) { + await page.close(); + } else if (browser) { + await browser.close(); + } + }; + + process.on('SIGINT', async () => { + await cleanup(); + process.exit(); + }); + testFn.afterAll = cleanup; + + const beforeAll = async () => { + if (getCustomBrowser) { + browser = await getCustomBrowser(); + } else { + // eslint-disable-next-line global-require + const puppeteer = require('puppeteer'); + // add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507 + browser = await puppeteer.launch({ + args: ['--no-sandbox ', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + executablePath: chromeExecutablePath, + }); + } + + page = await browser.newPage(); + }; + beforeAll.timeout = setupTimeout; + testFn.beforeAll = beforeAll; + + return testFn; +}; diff --git a/addons/storyshots/storyshots-puppeteer/src/url.ts b/addons/storyshots/storyshots-puppeteer/src/url.ts index 1104bb80bec1..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/router/utils'; - 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/package.json b/addons/storysource/package.json index ebc6be7ce0c1..5be417945a4f 100644 --- a/addons/storysource/package.json +++ b/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Stories addon for storybook", "keywords": [ "addon", @@ -18,35 +18,43 @@ "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-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/router": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "estraverse": "^4.2.0", "loader-utils": "^1.2.3", "prettier": "^1.16.4", "prop-types": "^15.7.2", + "react": "^16.9.17", "react-syntax-highlighter": "^11.0.2", "regenerator-runtime": "^0.13.3", "util-deprecate": "^1.0.2" }, + "devDependencies": { + "@types/react": "^16.9.17", + "@types/react-syntax-highlighter": "^11.0.4" + }, "peerDependencies": { "@storybook/source-loader": "*", "react": "*" }, "publishConfig": { "access": "public" - } + }, + "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..00435f113517 --- /dev/null +++ b/addons/storysource/src/StoryPanel.tsx @@ -0,0 +1,191 @@ +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'; + +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 interface StoryData { + id: string; + kind?: string; + parameters?: { + storySource?: SourceParams; + mdxSource?: string; + }; +} +export const StoryPanel: React.FC<StoryPanelProps> = ({ api }) => { + const [state, setState] = React.useState<SourceParams & { currentLocation?: SourceBlock }>({ + source: 'loading source...', + locationsMap: {}, + }); + + const story: StoryData | undefined = api.getCurrentStoryData(); + const selectedStoryRef = React.useRef<HTMLDivElement>(null); + React.useEffect(() => { + if (story) { + const { + parameters: { + mdxSource = '', + 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: source || mdxSource, 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, + }: SyntaxHighlighterRendererProps & { location: SourceBlock; id: 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 (location && currentLocation && areLocationsEqual(location, currentLocation)) { + return ( + <SelectedStoryHighlight key={storyKey} ref={selectedStoryRef}> + {storySource} + </SelectedStoryHighlight> + ); + } + return ( + <StyledStoryLink to={`/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 } = story; + // source loader ids are differnet 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 }); + + 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.ts similarity index 69% rename from addons/storysource/src/events.js rename to addons/storysource/src/events.ts index 80572092f96e..9436b8a75114 100644 --- a/addons/storysource/src/events.js +++ b/addons/storysource/src/events.ts @@ -1,3 +1,2 @@ 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/index.js b/addons/storysource/src/index.ts similarity index 51% rename from addons/storysource/src/index.js rename to addons/storysource/src/index.ts index 0bd2bb285dc3..6f62c1dcf725 100644 --- a/addons/storysource/src/index.js +++ b/addons/storysource/src/index.ts @@ -1,7 +1,7 @@ -import { ADDON_ID, PANEL_ID, EVENT_ID } from './events'; +import { ADDON_ID, PANEL_ID } from './events'; import { withStorySource } from './preview'; -export { ADDON_ID, PANEL_ID, EVENT_ID, withStorySource }; +export { ADDON_ID, PANEL_ID, withStorySource }; if (module && module.hot && module.hot.decline) { module.hot.decline(); diff --git a/addons/storysource/src/manager.js b/addons/storysource/src/manager.tsx similarity index 62% rename from addons/storysource/src/manager.js rename to addons/storysource/src/manager.tsx index 4ca5c8414056..c5c28e28ee39 100644 --- a/addons/storysource/src/manager.js +++ b/addons/storysource/src/manager.tsx @@ -1,15 +1,14 @@ -/* eslint-disable react/prop-types */ import React from 'react'; import addons from '@storybook/addons'; -import StoryPanel from './StoryPanel'; +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} />, + 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/src/preview.ts b/addons/storysource/src/preview.ts new file mode 100644 index 000000000000..e36004385aec --- /dev/null +++ b/addons/storysource/src/preview.ts @@ -0,0 +1,11 @@ +import { logger } from '@storybook/client-logger'; + +export function withStorySource(source: string, locationsMap = {}) { + logger.error( + '@storybook/addon-storysource/withStorySource is deprecated, please use paramaters instead.' + ); + + return (storyFn: (context: any) => React.ReactNode, context: any) => { + return storyFn(context); + }; +} 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/notes/tsconfig.json b/addons/storysource/tsconfig.json similarity index 100% rename from addons/notes/tsconfig.json rename to addons/storysource/tsconfig.json diff --git a/addons/viewport/README.md b/addons/viewport/README.md index 2d5b19392635..853c64491b53 100644 --- a/addons/viewport/README.md +++ b/addons/viewport/README.md @@ -20,17 +20,19 @@ or with yarn: yarn add -D @storybook/addon-viewport ``` -Then, add following content to .storybook/addons.js +within `.storybook/main.js`: ```js -import '@storybook/addon-viewport/register'; +module.exports = { + addons: ['@storybook/addon-viewport/register'], +}; ``` You should now be able to see the viewport addon icon in the the toolbar at the top of the screen. ## Configuration -The viewport addon is configured by story parameters with the `viewport` key. To configure globally, import `addParameters` from your app layer in your `config.js` file. +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'; @@ -91,19 +93,27 @@ A key-value pair of viewport's key and properties (see `Viewport` definition bel Parameters can be configured for a whole set of stories or a single story via the standard parameter API: ```js -import addStories from '@storybook/react'; -addStories('Stories', module) - // To set a default viewport for all the stories for this component - .addParameters({ viewport: { defaultViewport: 'iphone6' }}) - .add('story', () => </>, { viewport: { defaultViewport: 'iphonex' }}); +export default { + title: 'Stories', + parameters: { + viewport: { defaultViewport: 'iphone6' }, + }; +}; + +export const myStory = () => <div />; +myStory.story = { + parameters: { + viewport: { defaultViewport: 'iphonex' }, + }, +}; ``` ## Examples ### Use Detailed Set of Devices -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 `config.js` file in your `.storybook` directory. +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'; @@ -118,12 +128,12 @@ addParameters({ ### Use Custom Set of Devices -This will replace all previous devices with `Kindle Fire 2` and `Kindle Fire HD` by calling `addParameters` with the two devices as `viewports` in `config.js` file in your `.storybook` directory. +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'; -const newViewports = { +const customViewports = { kindleFire2: { name: 'Kindle Fire 2', styles: { @@ -141,19 +151,22 @@ const newViewports = { }; addParameters({ - viewport: { viewports: newViewports }, + viewport: { viewports: customViewports }, }); ``` ### Add New Device -This will add both `Kindle Fire 2` and `Kindle Fire HD` to the list of devices. This is achieved by making use of the exported [`INITIAL_VIEWPORTS`](src/defaults.ts) property, by merging it with the new viewports and pass the result as `viewports` to `configureViewport` function +This will add both `Kindle Fire 2` and `Kindle Fire HD` to the list of devices. This is achieved by making use of the exported [`INITIAL_VIEWPORTS`](src/defaults.ts) or [`MINIMAL_VIEWPORTS`](src/defaults.ts) property, by merging it with the new viewports and pass the result as `viewports` to `configureViewport` function ```js import { addParameters } from '@storybook/react'; -import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; +import { + INITIAL_VIEWPORTS, + // or MINIMAL_VIEWPORTS, +} from '@storybook/addon-viewport'; -const newViewports = { +const customViewports = { kindleFire2: { name: 'Kindle Fire 2', styles: { @@ -174,7 +187,8 @@ addParameters({ viewport: { viewports: { ...INITIAL_VIEWPORTS, - ...newViewports, + // or ...MINIMAL_VIEWPORTS, + ...customViewports, }, }, }); diff --git a/addons/viewport/package.json b/addons/viewport/package.json index 621cef2ce18d..8be92aa0acd8 100644 --- a/addons/viewport/package.json +++ b/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook addon to change the viewport size to mobile", "keywords": [ "addon", @@ -18,22 +18,22 @@ "license": "MIT", "files": [ "dist/**/*", - "docs/**/*", "README.md", "*.js", "*.d.ts" ], - "main": "preview.js", + "main": "dist/preview.js", + "types": "dist/preview.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "memoizerific": "^1.11.3", @@ -48,5 +48,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/addons/viewport/preview.js b/addons/viewport/preview.js deleted file mode 100644 index dc47e9c63cba..000000000000 --- a/addons/viewport/preview.js +++ /dev/null @@ -1,5 +0,0 @@ -const preview = require('./dist/legacy_preview/index'); - -exports.configureViewport = preview.configureViewport; -exports.DEFAULT_VIEWPORT = preview.DEFAULT_VIEWPORT; -exports.INITIAL_VIEWPORTS = preview.INITIAL_VIEWPORTS; diff --git a/addons/viewport/src/Tool.tsx b/addons/viewport/src/Tool.tsx index 2813d119e7b5..7a277853edf2 100644 --- a/addons/viewport/src/Tool.tsx +++ b/addons/viewport/src/Tool.tsx @@ -84,10 +84,10 @@ const ActiveViewportSize = styled.div(() => ({ const ActiveViewportLabel = styled.div<{}>(({ theme }) => ({ display: 'inline-block', textDecoration: 'none', - padding: '10px', + padding: 10, fontWeight: theme.typography.weight.bold, fontSize: theme.typography.size.s2 - 1, - lineHeight: 1, + lineHeight: '1', height: 40, border: 'none', borderTop: '3px solid transparent', @@ -102,7 +102,7 @@ const IconButtonWithLabel = styled(IconButton)(() => ({ const IconButtonLabel = styled.div<{}>(({ theme }) => ({ fontSize: theme.typography.size.s2 - 1, - marginLeft: '10px', + marginLeft: 10, })); interface ViewportToolState { @@ -124,19 +124,23 @@ const getStyles = ( export const ViewportTool: FunctionComponent = memo( withTheme(({ theme }: { theme: Theme }) => { - const { viewports, defaultViewport, disable } = useParameter<ViewportAddonParameter>( - PARAM_KEY, - { - viewports: MINIMAL_VIEWPORTS, - defaultViewport: responsiveViewport.id, - } - ); + const { + viewports = MINIMAL_VIEWPORTS, + defaultViewport = responsiveViewport.id, + disable, + } = useParameter<ViewportAddonParameter>(PARAM_KEY, {}); const [state, setState] = useAddonState<ViewportToolState>(ADDON_ID, { - selected: defaultViewport || responsiveViewport.id, + selected: defaultViewport, isRotated: false, }); const list = toList(viewports); + 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.` + ); + } + useEffect(() => { setState({ selected: @@ -199,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/legacy_preview/index.ts b/addons/viewport/src/legacy_preview/index.ts index 0e4f8e090b98..de7b43e50a02 100644 --- a/addons/viewport/src/legacy_preview/index.ts +++ b/addons/viewport/src/legacy_preview/index.ts @@ -1,6 +1,6 @@ import deprecate from 'util-deprecate'; -export { INITIAL_VIEWPORTS, DEFAULT_VIEWPORT } from '../defaults'; +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/ViewportAddonParameter.ts b/addons/viewport/src/models/ViewportAddonParameter.ts index 7a6b5b27d39c..e0f826f9586b 100644 --- a/addons/viewport/src/models/ViewportAddonParameter.ts +++ b/addons/viewport/src/models/ViewportAddonParameter.ts @@ -3,7 +3,7 @@ import { ViewportMap } from './Viewport'; export interface ViewportAddonParameter { disable?: boolean; defaultViewport?: string; - viewports: ViewportMap; + viewports?: ViewportMap; /* * @deprecated * The viewport parameter `onViewportChange` is no longer supported diff --git a/addons/viewport/src/preview.ts b/addons/viewport/src/preview.ts new file mode 100644 index 000000000000..9f198c8b135f --- /dev/null +++ b/addons/viewport/src/preview.ts @@ -0,0 +1,6 @@ +export { + configureViewport, + DEFAULT_VIEWPORT, + INITIAL_VIEWPORTS, + MINIMAL_VIEWPORTS, +} from './legacy_preview'; diff --git a/app/angular/package.json b/app/angular/package.json index 5156ee851934..f935a5a7c3ab 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,9 +33,10 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@storybook/node-logger": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@storybook/node-logger": "6.0.0-alpha.2", + "@types/webpack-env": "^1.15.0", "core-js": "^3.0.1", "fork-ts-checker-webpack-plugin": "^3.0.1", "global": "^4.3.2", @@ -47,7 +48,6 @@ }, "devDependencies": { "@types/autoprefixer": "^9.4.0", - "@types/webpack-env": "^1.14.0", "webpack": "^4.33.0" }, "peerDependencies": { @@ -71,5 +71,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } 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 60b2d89139c9..7856bf8f8947 100644 --- a/app/angular/src/client/preview/angular/components/app.component.ts +++ b/app/angular/src/client/preview/angular/components/app.component.ts @@ -77,7 +77,7 @@ export class AppComponent implements OnInit, OnDestroy { const value = props[key]; const instanceProperty = instance[key]; - if (!(instanceProperty instanceof EventEmitter) && (value !== undefined && value !== null)) { + if (!(instanceProperty instanceof EventEmitter) && value !== undefined && value !== null) { // eslint-disable-next-line no-param-reassign instance[key] = value; if (hasNgOnChangesHook) { 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 3bdf80d27bb4..25c22a7e3ea9 100644 --- a/app/angular/src/server/__tests__/angular-cli_config.test.ts +++ b/app/angular/src/server/__tests__/angular-cli_config.test.ts @@ -51,12 +51,32 @@ describe('angular-cli_config', () => { }); }); + it('should return null if `architect.build` option are not exists.', () => { + const angularJson = fs.readFileSync(path.resolve(__dirname, 'angular.json'), 'utf8'); + const angularJsonWithNoBuildOptions = JSON.parse(stripJsonComments(angularJson)); + angularJsonWithNoBuildOptions.projects['angular-cli'].architect.build = undefined; + + getLeadingAngularCliProject(angularJsonWithNoBuildOptions); + + const config = getAngularCliWebpackConfigOptions('/'); + expect(config).toBeNull(); + }); + it('should return baseConfig if no angular.json was found', () => { const baseConfig = { test: 'config' }; const projectConfig = getAngularCliWebpackConfigOptions('test-path' as Path); const config = applyAngularCliWebpackConfig(baseConfig, projectConfig); - expect(projectConfig).toBe(undefined); + 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__/create-fork-ts-checker-plugin.test.ts b/app/angular/src/server/__tests__/create-fork-ts-checker-plugin.test.ts index 2b60aa49a4ba..02d82e734f25 100644 --- a/app/angular/src/server/__tests__/create-fork-ts-checker-plugin.test.ts +++ b/app/angular/src/server/__tests__/create-fork-ts-checker-plugin.test.ts @@ -2,7 +2,7 @@ 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 +// eslint-disable-next-line global-require, jest/no-mocks-import jest.mock('fs', () => require('../../../../../__mocks__/fs')); jest.mock('path', () => ({ resolve: () => 'tsconfig.json', diff --git a/app/angular/src/server/__tests__/ts_config.test.ts b/app/angular/src/server/__tests__/ts_config.test.ts index 383eb7ccc18a..046001787ad8 100644 --- a/app/angular/src/server/__tests__/ts_config.test.ts +++ b/app/angular/src/server/__tests__/ts_config.test.ts @@ -1,6 +1,6 @@ import getTsLoaderOptions from '../ts_config'; -// eslint-disable-next-line global-require +// eslint-disable-next-line global-require, jest/no-mocks-import jest.mock('fs', () => require('../../../../../__mocks__/fs')); jest.mock('path', () => ({ resolve: () => 'tsconfig.json', diff --git a/app/angular/src/server/angular-cli_config.ts b/app/angular/src/server/angular-cli_config.ts index ec0c7a9a22de..b027dd65ce61 100644 --- a/app/angular/src/server/angular-cli_config.ts +++ b/app/angular/src/server/angular-cli_config.ts @@ -50,6 +50,7 @@ export function getAngularCliConfig(dirToSearch: string) { const fname = path.join(dirToSearch, 'angular.json'); if (!fs.existsSync(fname)) { + logger.error(`Could not find angular.json using ${fname}`); return undefined; } @@ -57,35 +58,59 @@ export function getAngularCliConfig(dirToSearch: string) { } export function getLeadingAngularCliProject(ngCliConfig: any) { + if (!ngCliConfig) { + return null; + } + const { defaultProject } = ngCliConfig; const { projects } = ngCliConfig; if (!projects || !Object.keys(projects).length) { 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.architect.build) { + logger.error(`architect.build is not defined for project '${projectName}'.`); + } + return project; } export function getAngularCliWebpackConfigOptions(dirToSearch: Path) { const angularCliConfig = getAngularCliConfig(dirToSearch); - if (!angularCliConfig) { - return undefined; + const project = getLeadingAngularCliProject(angularCliConfig); + + if (!angularCliConfig || !project.architect.build) { + return null; } - const project = getLeadingAngularCliProject(angularCliConfig); const { options: projectOptions } = project.architect.build; - const normalizedAssets = normalizeAssetPatterns( projectOptions.assets, dirToSearch, project.sourceRoot ); - 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, @@ -98,6 +123,10 @@ export function getAngularCliWebpackConfigOptions(dirToSearch: Path) { optimization: {}, ...projectOptions, assets: normalizedAssets, + budgets, + scripts, + styles, + outputPath, }, }; } diff --git a/app/angular/src/server/options.ts b/app/angular/src/server/options.ts index 75a84f17d3f4..4d703cf796d2 100644 --- a/app/angular/src/server/options.ts +++ b/app/angular/src/server/options.ts @@ -3,6 +3,7 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'angular', frameworkPresets: [ require.resolve('./framework-preset-angular.js'), require.resolve('./framework-preset-angular-cli.js'), 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/ember/package.json b/app/ember/package.json index 7d50ad987fdf..8ef8e6ef42ee 100644 --- a/app/ember/package.json +++ b/app/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/master/app/ember", "bugs": { @@ -30,8 +30,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@ember/test-helpers": "^1.5.0", - "@storybook/core": "5.3.0-alpha.41", + "@ember/test-helpers": "^1.7.0", + "@storybook/core": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -39,14 +39,14 @@ }, "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-plugin-ember-modules-api-polyfill": "^2.12.0", + "ember-source": "^3.16.0" }, "engines": { "node": ">=8.0.0" }, "publishConfig": { "access": "public" - } + }, + "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.ts similarity index 65% rename from app/ember/src/client/preview/index.js rename to app/ember/src/client/preview/index.ts index df20b1c1352a..1b159f531b64 100644 --- a/app/ember/src/client/preview/index.js +++ b/app/ember/src/client/preview/index.ts @@ -15,7 +15,8 @@ export const { } = clientApi; const framework = 'ember'; -export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); +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.ts similarity index 85% rename from app/ember/src/client/preview/render.js rename to app/ember/src/client/preview/render.ts index 05fad4a429cf..fa8dddd19995 100644 --- a/app/ember/src/client/preview/render.js +++ b/app/ember/src/client/preview/render.ts @@ -1,6 +1,8 @@ -/* eslint-disable no-undef */ import { window, document } from 'global'; import dedent from 'ts-dedent'; +import { RenderMainArgs, ElementArgs, OptionsArgs } from './types'; + +declare let Ember: any; const rootEl = document.getElementById('root'); @@ -14,11 +16,11 @@ const app = window.require(`${window.STORYBOOK_NAME}/app`).default.create({ let lastPromise = app.boot(); let hasRendered = false; -function render(options, el) { +function render(options: OptionsArgs, el: ElementArgs) { const { template, context = {}, element } = options; if (hasRendered) { - lastPromise = lastPromise.then(instance => instance.destroy()); + lastPromise = lastPromise.then((instance: any) => instance.destroy()); } lastPromise = lastPromise @@ -26,7 +28,7 @@ function render(options, el) { const appInstancePrivate = app.buildInstance(); return appInstancePrivate.boot().then(() => appInstancePrivate); }) - .then(instance => { + .then((instance: any) => { instance.register( 'component:story-mode', Ember.Component.extend({ @@ -56,8 +58,7 @@ export default function renderMain({ selectedStory, showMain, showError, - // forceRender, -}) { +}: RenderMainArgs) { const element = storyFn(); if (!element) { diff --git a/app/ember/src/client/preview/types.ts b/app/ember/src/client/preview/types.ts new file mode 100644 index 000000000000..1f12b509bb8d --- /dev/null +++ b/app/ember/src/client/preview/types.ts @@ -0,0 +1,24 @@ +import { StoryFn } from '@storybook/addons'; // eslint-disable-line + +export interface RenderMainArgs { + storyFn: StoryFn<any>; + selectedKind: string; + selectedStory: string; + showMain: () => void; + showError: (args: ShowErrorArgs) => void; +} + +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/ember/src/server/build.js b/app/ember/src/server/build.ts similarity index 100% rename from app/ember/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/ember/src/server/index.js b/app/ember/src/server/index.ts similarity index 100% rename from app/ember/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.ts similarity index 60% rename from app/ember/src/server/options.js rename to app/ember/src/server/options.ts index 919e0202bca0..f268bb9fbde2 100644 --- a/app/ember/src/server/options.js +++ b/app/ember/src/server/options.ts @@ -1,6 +1,7 @@ -import packageJson from '../../package.json'; +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 c05cfd381e52..e301987c1ae3 100644 --- a/app/html/package.json +++ b/app/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,9 +33,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@types/webpack-env": "^1.15.0", "core-js": "^3.0.1", "global": "^4.3.2", "html-loader": "^0.5.5", @@ -50,5 +50,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/html/src/server/options.ts b/app/html/src/server/options.ts index 27701b0063c8..1d349ea837db 100644 --- a/app/html/src/server/options.ts +++ b/app/html/src/server/options.ts @@ -3,5 +3,6 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'html', frameworkPresets: [require.resolve('./framework-preset-html.js')], }; diff --git a/app/polymer/README.md b/app/marionette/README.md similarity index 59% rename from app/polymer/README.md rename to app/marionette/README.md index d2b25e666537..fffc49bd35d5 100644 --- a/app/polymer/README.md +++ b/app/marionette/README.md @@ -1,9 +1,9 @@ -# Storybook for Polymer +# Storybook for Marionette.js -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 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) @@ -13,7 +13,7 @@ So you can develop UI components in isolation without worrying about app specifi ## Getting Started ```sh -cd my-polymer-app +cd my-app npx -p @storybook/cli sb init ``` @@ -22,10 +22,4 @@ 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! +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/polymer/bin/build.js b/app/marionette/bin/build.js similarity index 100% rename from app/polymer/bin/build.js rename to app/marionette/bin/build.js diff --git a/app/polymer/bin/index.js b/app/marionette/bin/index.js similarity index 100% rename from app/polymer/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..6f445c033981 --- /dev/null +++ b/app/marionette/package.json @@ -0,0 +1,48 @@ +{ + "name": "@storybook/marionette", + "version": "6.0.0-alpha.2", + "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-alpha.2", + "common-tags": "^1.8.0", + "core-js": "^3.0.1", + "global": "^4.3.2", + "html-loader": "^0.5.5", + "regenerator-runtime": "^0.12.1" + }, + "devDependencies": { + "backbone.marionette": "*" + }, + "peerDependencies": { + "babel-loader": "^7.0.0 || ^8.0.0", + "backbone.marionette": "*" + }, + "engines": { + "node": ">=8.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/app/svelte/src/client/index.js b/app/marionette/src/client/index.js similarity index 96% rename from app/svelte/src/client/index.js rename to app/marionette/src/client/index.js index 8034a9d6433d..c3f87077b852 100644 --- a/app/svelte/src/client/index.js +++ b/app/marionette/src/client/index.js @@ -7,6 +7,7 @@ export { getStorybook, forceReRender, raw, + load, } from './preview'; if (module && module.hot && 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..1661295377cf --- /dev/null +++ b/app/marionette/src/client/preview/element_check.js @@ -0,0 +1,18 @@ +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/polymer/src/client/preview/index.js b/app/marionette/src/client/preview/index.js similarity index 61% rename from app/polymer/src/client/preview/index.js rename to app/marionette/src/client/preview/index.js index 8ccdb4865d0b..0128a027102c 100644 --- a/app/polymer/src/client/preview/index.js +++ b/app/marionette/src/client/preview/index.js @@ -3,7 +3,7 @@ import { start } from '@storybook/core/client'; import './globals'; import render from './render'; -const { configure: coreConfigure, clientApi, forceReRender } = start(render); +const { load: coreLoad, clientApi, configApi, forceReRender } = start(render); export const { setAddon, @@ -14,8 +14,9 @@ export const { raw, } = clientApi; -const framework = 'polymer'; +const framework = 'marionette'; export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, 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..cab2762d2034 --- /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, selectedKind, selectedStory, showMain, showError }) { + const element = storyFn(); + + if (!element) { + showError({ + title: `Expecting a Marionette View from the story: "${selectedStory}" of "${selectedKind}".`, + description: stripIndents` + Did you forget to return the React element from the story? + Use "() => (<MyComp/>)" or "() => { return <MyComp/>; }" when defining the story. + `, + }); + return; + } + + if (!isMarionetteRenderable(element)) { + showError({ + title: `Expecting a valid Marionette View from the story: "${selectedStory}" of "${selectedKind}".`, + 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/polymer/src/server/build.js b/app/marionette/src/server/build.js similarity index 100% rename from app/polymer/src/server/build.js rename to app/marionette/src/server/build.js 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/polymer/src/server/index.js b/app/marionette/src/server/index.js similarity index 100% rename from app/polymer/src/server/index.js rename to app/marionette/src/server/index.js diff --git a/app/polymer/src/server/options.js b/app/marionette/src/server/options.js similarity index 52% rename from app/polymer/src/server/options.js rename to app/marionette/src/server/options.js index 81879f131cd7..a1ea7a38f6d8 100644 --- a/app/polymer/src/server/options.js +++ b/app/marionette/src/server/options.js @@ -2,5 +2,5 @@ import packageJson from '../../package.json'; export default { packageJson, - frameworkPresets: [require.resolve('./framework-preset-polymer.js')], + frameworkPresets: [require.resolve('./framework-preset-marionette.js')], }; diff --git a/app/polymer/standalone.js b/app/marionette/standalone.js similarity index 100% rename from app/polymer/standalone.js rename to app/marionette/standalone.js diff --git a/app/marko/package.json b/app/marko/package.json index 1b7163e4014b..a01c09c860ed 100644 --- a/app/marko/package.json +++ b/app/marko/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/marko", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Marko: Develop Marko Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,8 +33,8 @@ }, "dependencies": { "@marko/webpack": "^2.0.0", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -49,5 +49,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/marko/src/server/options.js b/app/marko/src/server/options.js index 00159044ab48..75f0bacac6fe 100644 --- a/app/marko/src/server/options.js +++ b/app/marko/src/server/options.js @@ -2,5 +2,6 @@ import packageJson from '../../package.json'; export default { packageJson, + framework: 'marko', frameworkPresets: [require.resolve('./framework-preset-marko.js')], }; diff --git a/app/mithril/package.json b/app/mithril/package.json index 48373a361847..000e9599c24a 100644 --- a/app/mithril/package.json +++ b/app/mithril/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/mithril", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Mithril: Develop Mithril Component in isolation.", "keywords": [ "storybook" @@ -35,9 +35,10 @@ "dependencies": { "@babel/core": "^7.6.2", "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", "@types/mithril": "^2.0.0", + "@types/webpack-env": "^1.15.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -55,5 +56,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/mithril/src/server/options.ts b/app/mithril/src/server/options.ts index 1f6aa7326e1c..2ffb2b00f60d 100644 --- a/app/mithril/src/server/options.ts +++ b/app/mithril/src/server/options.ts @@ -2,5 +2,6 @@ import packageJson from '../../package.json'; export default { packageJson, + framework: 'mithril', frameworkPresets: [require.resolve('./framework-preset-mithril.js')], }; diff --git a/app/polymer/package.json b/app/polymer/package.json deleted file mode 100644 index 19b9a8f24987..000000000000 --- a/app/polymer/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@storybook/polymer", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@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" - } -} 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/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 = '<div id="root-inner"></div>'; - } - 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/preact/package.json b/app/preact/package.json index e8e5dc6956fb..0b5bf41bd579 100644 --- a/app/preact/package.json +++ b/app/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" @@ -34,9 +34,9 @@ }, "dependencies": { "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@types/webpack-env": "^1.15.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -55,5 +55,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/preact/src/client/preview/types.ts b/app/preact/src/client/preview/types.ts index 8e3e08f14094..d961b76f5c5b 100644 --- a/app/preact/src/client/preview/types.ts +++ b/app/preact/src/client/preview/types.ts @@ -1,6 +1,6 @@ import { StoryFn, ClientStoryApi, Loadable } from '@storybook/addons'; -export type StoryFnPreactReturnType = string | Node | JSX.Element; +export type StoryFnPreactReturnType = string | Node | preact.JSX.Element; export interface ShowErrorArgs { title: string; diff --git a/app/preact/src/server/options.ts b/app/preact/src/server/options.ts index 20900ed077f2..73981f5d9098 100644 --- a/app/preact/src/server/options.ts +++ b/app/preact/src/server/options.ts @@ -2,5 +2,6 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'preact', frameworkPresets: [require.resolve('./framework-preset-preact.js')], }; diff --git a/app/rax/package.json b/app/rax/package.json index 765ac5ab0eaf..529c49696ab1 100644 --- a/app/rax/package.json +++ b/app/rax/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/rax", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Rax: Develop Rax Component in isolation.", "keywords": [ "rax", @@ -33,7 +33,7 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-alpha.41", + "@storybook/core": "6.0.0-alpha.2", "babel-preset-rax": "^1.0.0-beta.0", "core-js": "^3.0.1", "driver-dom": "^2.0.0", @@ -42,7 +42,7 @@ "ts-dedent": "^1.1.0" }, "devDependencies": { - "rax": "^1.0.0" + "rax": "^1.1.0" }, "peerDependencies": { "babel-loader": "^7.0.0 || ^8.0.0", @@ -50,5 +50,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/rax/src/server/options.js b/app/rax/src/server/options.js index 1c06efc33f47..42127e24799e 100644 --- a/app/rax/src/server/options.js +++ b/app/rax/src/server/options.js @@ -2,5 +2,6 @@ import packageJson from '../../package.json'; export default { packageJson, + framework: 'rax', frameworkPresets: [require.resolve('./framework-preset-rax.js')], }; diff --git a/app/react-native-server/bin/index.js b/app/react-native-server/bin/index.js deleted file mode 100755 index 2e96258ce63d..000000000000 --- a/app/react-native-server/bin/index.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require('../dist/server'); diff --git a/app/react-native-server/package.json b/app/react-native-server/package.json deleted file mode 100644 index 5b39be49aeaa..000000000000 --- a/app/react-native-server/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@storybook/react-native-server", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/channel-websocket": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/ui": "5.3.0-alpha.41", - "commander": "^3.0.2", - "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" - } -} 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 = () => ( - <div style={styles.main}> - <h1>Welcome to storybook</h1> - <p>This is a UI component dev environment for your app.</p> - <p> - We've added some basic stories inside the {<span style={styles.code}>storybook/stories</span>}{' '} - 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. - </p> - <p> - To see your Storybook stories on the device, you should start your mobile app for the  - <span style={styles.code}><platform></span> 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.) - </p> - <p> - For <span style={styles.code}>create-react-native-app</span> apps: - </p> - <div style={styles.codeBlock}> - <pre style={styles.instructionsCode}>npm run <platform></pre> - </div> - <p> - For <span style={styles.code}>react-native init</span> apps: - </p> - <div style={styles.codeBlock}> - <pre style={styles.instructionsCode}>react-native run-<platform></pre> - </div> - </div> -); - -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 ( - <Consumer filter={mapper} pure> - {({ storiesHash, storyId, api, viewMode }) => { - if (storiesHash[storyId]) { - api.emit(Events.SET_CURRENT_STORY, { storyId }); - } - return viewMode === 'story' ? <PreviewHelp /> : null; - }} - </Consumer> - ); - } - - 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>', 'host to listen on', 'localhost') - .option('-p, --port <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 <ca>', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert <cert>', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key <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 4e3f610f6b3d..000000000000 --- a/app/react-native-server/src/server/options.js +++ /dev/null @@ -1,27 +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, - 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 }) => ( - <View style={style}> - {children} - </View> -); - -storiesOf('CenteredView', module) - .add('default view', () => ( - <CenteredView> - <Text>Hello Storybook</Text> - </CenteredView> - )); -``` - -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 6fe883fef92e..000000000000 --- a/app/react-native/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@storybook/react-native", - "version": "5.3.0-alpha.41", - "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-alpha.41", - "@storybook/channel-websocket": "5.3.0-alpha.41", - "@storybook/channels": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "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" - } -} 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 `<http://localhost:7007>` 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 `<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 of your browser window after your device has connected to this storybook server.) - -For CRNA apps: - -```sh -npm run <platform> -``` - -For RN apps: -```sh -react-native run-<platform> -``` - -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> - host to listen on --p, --port <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<Props> { - 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 ( - <View style={{ flex: 1 }} onLayout={this.onLayoutHandler}> - <View - style={ - previewWidth === 0 - ? { flex: 1 } - : { position: 'absolute', width: previewWidth, height: previewHeight } - } - > - {children} - </View> - </View> - ); - } -} 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 ( - <SafeAreaView style={{ flex: 1 }}> - <NoAddonContainer> - <Label>No addons loaded.</Label> - </NoAddonContainer> - </SafeAreaView> - ); - } - - return ( - <Container> - <SafeAreaView style={{ flex: 1 }}> - <AddonsList - onPressAddon={this.onPressAddon} - panels={this.panels} - addonSelected={addonSelected} - /> - <AddonWrapper addonSelected={addonSelected} panels={this.panels} /> - </SafeAreaView> - </Container> - ); - } -} 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<Props> { - renderTab = (id: string, title: string) => { - const { addonSelected, onPressAddon } = this.props; - - return ( - <Button active={id === addonSelected} key={id} id={id} onPress={() => onPressAddon(id)}> - {title.toUpperCase()} - </Button> - ); - }; - - render() { - const { panels } = this.props; - const addonKeys = Object.keys(panels); - - return ( - <Container> - <ScrollView showsHorizontalScrollIndicator={false} horizontal> - {addonKeys.map(id => this.renderTab(id, panels[id].title))} - </ScrollView> - </Container> - ); - } -} 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<Props> { - static defaultProps = { - addonSelected: '', - }; - - render() { - const { panels, addonSelected } = this.props; - - const addonKeys = Object.keys(panels); - - return addonKeys.map(id => { - const selected = addonSelected === id; - - return ( - <View key={id} style={selected ? style.flex : style.invisible}> - <ScrollView>{panels[id].render({ active: selected, key: id })}</ScrollView> - </View> - ); - }); - } -} 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<OnDeviceUIProps, OnDeviceUIState> { - 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 ( - <SafeAreaView style={flex}> - <KeyboardAvoidingView - enabled={!shouldDisableKeyboardAvoidingView || tabOpen !== PREVIEW} - behavior={IS_IOS ? 'padding' : null} - keyboardVerticalOffset={keyboardAvoidingViewVerticalOffset} - style={flex} - > - <AbsolutePositionedKeyboardAwareView - onLayout={this.onLayout} - previewHeight={previewHeight} - previewWidth={previewWidth} - > - <Animated.View style={previewWrapperStyles}> - <Animated.View style={previewStyles}> - <Preview disabled={tabOpen === PREVIEW}> - <StoryView url={url} onDevice stories={stories} /> - </Preview> - {tabOpen !== PREVIEW ? ( - <TouchableOpacity style={absolutePosition} onPress={this.handleOpenPreview} /> - ) : null} - </Animated.View> - </Animated.View> - <Panel style={getNavigatorPanelPosition(this.animatedValue, previewWidth)}> - <StoryListView stories={stories} /> - </Panel> - <Panel style={getAddonPanelPosition(this.animatedValue, previewWidth)}> - <Addons /> - </Panel> - </AbsolutePositionedKeyboardAwareView> - <Navigation - tabOpen={tabOpen} - onChangeTab={this.handleToggleTab} - initialUiVisible={!isUIHidden} - /> - </KeyboardAvoidingView> - </SafeAreaView> - ); - } -} 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<Props> { - render() { - const { index, onPress } = this.props; - return ( - <Container> - <Button onPress={onPress} id={NAVIGATOR} active={index === NAVIGATOR}> - NAVIGATOR - </Button> - <Button onPress={onPress} id={PREVIEW} active={index === PREVIEW}> - PREVIEW - </Button> - <Button onPress={onPress} id={ADDONS} active={index === ADDONS}> - ADDONS - </Button> - </Container> - ); - } -} 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<Props> { - onPress = () => { - const { onPress, id } = this.props; - onPress(id); - }; - - render() { - const { active, children } = this.props; - - return ( - <TouchableOpacity onPress={this.onPress} activeOpacity={0.8}> - <ButtonText active={active}>{children}</ButtonText> - <ActiveBorder active={active} /> - </TouchableOpacity> - ); - } -} 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<Props> { - 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 ( - <View> - <SafeAreaView> - {isUIVisible && ( - <GestureRecognizer - onSwipeLeft={this.handleSwipeLeft} - onSwipeRight={this.handleSwipeRight} - config={SWIPE_CONFIG} - > - <Bar index={tabOpen} onPress={onChangeTab} /> - </GestureRecognizer> - )} - <View> - <VisibilityButton onPress={this.handleToggleUI} /> - </View> - </SafeAreaView> - </View> - ); - } -} 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<Props> { - render() { - const { onPress } = this.props; - return ( - <Touchable - onPress={onPress} - testID="Storybook.OnDeviceUI.toggleUI" - accessibilityLabel="Storybook.OnDeviceUI.toggleUI" - hitSlop={{ top: 5, left: 5, bottom: 5, right: 5 }} - > - <HideIcon>□</HideIcon> - </Touchable> - ); - } -} 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<Props> { - render() { - const { children, style } = this.props; - return <Container style={[StyleSheet.absoluteFillObject, ...style]}>{children}</Container>; - } -} 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<SectionProps> = ({ title, selected }: SectionProps) => ( - <HeaderContainer key={title}> - <Header selected={selected}>{title}</Header> - </HeaderContainer> -); - -interface ListItemProps { - title: string; - kind: string; - selected: boolean; - onPress: () => void; -} - -const ItemTouchable = styled.TouchableOpacity({ - paddingHorizontal: 16, - paddingVertical: 5, -}); - -const ListItem: FunctionComponent<ListItemProps> = ({ kind, title, selected, onPress }) => ( - <ItemTouchable - key={title} - onPress={onPress} - activeOpacity={0.8} - testID={`Storybook.ListItem.${kind}.${title}`} - accessibilityLabel={`Storybook.ListItem.${title}`} - > - <Name selected={selected}>{title}</Name> - </ItemTouchable> -); - -interface Props { - stories: any; -} - -interface State { - data: any[]; - originalData: any[]; -} - -const List = styled.SectionList({ - flex: 1, - marginBottom: 40, -}); - -export default class StoryListView extends Component<Props, State> { - 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 ( - <SafeAreaView style={{ flex: 1 }}> - <SearchBar - testID="Storybook.ListView.SearchBar" - clearButtonMode="while-editing" - disableFullscreenUI - onChangeText={this.handleChangeSearchText} - placeholder="Filter" - returnKeyType="search" - /> - <List - testID="Storybook.ListView" - renderItem={({ item }) => ( - <ListItem - title={item.name} - kind={item.kind} - selected={selectedStory && item.id === selectedStory.id} - onPress={() => this.changeStory(item.id)} - /> - )} - renderSectionHeader={({ section: { title } }) => ( - <SectionHeader title={title} selected={selectedStory && title === selectedStory.kind} /> - )} - keyExtractor={(item, index) => item + index} - sections={data} - stickySectionHeadersEnabled={false} - /> - </SafeAreaView> - ); - } -} 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<Props> { - 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 ( - <HelpContainer> - {url && url.length ? ( - <Text> - Please open the Storybook UI ({url}) with a web browser and select a story for preview. - </Text> - ) : ( - <Text> - Please open the Storybook UI with a web browser and select a story for preview. - </Text> - )} - </HelpContainer> - ); - }; - - renderOnDeviceUIHelp = () => ( - <HelpContainer> - <Text>Please open navigator and select a story to preview.</Text> - </HelpContainer> - ); - - render() { - const { onDevice, stories } = this.props; - const { storyId } = stories.getSelection(); - const story = stories.fromId(storyId); - - if (story && story.storyFn) { - const { id, storyFn } = story; - return ( - <View key={id} testID={id} style={{ flex: 1 }}> - {storyFn()} - </View> - ); - } - - 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: <T>(key: string) => Promise<T>; - setItem: <T>(key: string, value: T) => Promise<void>; -} - -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<Params> = {}) => { - 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 ( - <ThemeProvider theme={appliedTheme}> - <OnDeviceUI - stories={preview._stories} - url={webUrl} - isUIHidden={params.isUIHidden} - tabOpen={params.tabOpen} - shouldDisableKeyboardAvoidingView={params.shouldDisableKeyboardAvoidingView} - keyboardAvoidingViewVerticalOffset={params.keyboardAvoidingViewVerticalOffset} - /> - </ThemeProvider> - ); - } - - return ( - <ThemeProvider theme={appliedTheme}> - <StoryView stories={preview._stories} url={webUrl} /> - </ThemeProvider> - ); - } - }; - }; - - _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<string>(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<Theme extends object = MyTheme, ExtraProps = {}> - extends CreateStyled<Theme>, - 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 63426d44391b..c3958dd00321 100644 --- a/app/react/README.md +++ b/app/react/README.md @@ -25,7 +25,7 @@ You can also build a [static version](https://storybook.js.org/basics/exporting- Here are some featured storybooks that you can reference to see how Storybook works: - [Demo of React Dates](http://airbnb.io/react-dates/) - [source](https://github.com/airbnb/react-dates) -- [Demo of React Native Web](http://necolas.github.io/react-native-web/storybook/) - [source](https://github.com/necolas/react-native-web) +- [Demo of React Native Web](https://necolas.github.io/react-native-web/docs/) - [source](https://github.com/necolas/react-native-web) ## Create React App @@ -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 d0b41455894d..d6e6ad397d2a 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,32 +33,32 @@ "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-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@storybook/node-logger": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@storybook/node-logger": "6.0.0-alpha.2", "@svgr/webpack": "^4.0.3", - "@types/webpack-env": "^1.13.7", + "@types/webpack-env": "^1.15.0", "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.0.0", "core-js": "^3.0.1", "global": "^4.3.2", "lodash": "^4.17.15", - "mini-css-extract-plugin": "^0.8.0", + "mini-css-extract-plugin": "^0.9.0", "prop-types": "^15.7.2", - "react-dev-utils": "^9.0.0", + "react-dev-utils": "^10.0.0", "regenerator-runtime": "^0.13.3", "semver": "^6.0.0", "ts-dedent": "^1.1.0", "webpack": "^4.33.0" }, "devDependencies": { - "@types/mini-css-extract-plugin": "^0.8.0", - "@types/node": "^12.7.9", - "@types/webpack": "^4.4.32" + "@types/mini-css-extract-plugin": "^0.9.0", + "@types/node": "^13.1.5", + "@types/webpack": "^4.41.0" }, "peerDependencies": { "@babel/core": "^7.0.1", @@ -71,5 +71,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/react/src/server/__snapshots__/cra-config.test.ts.snap b/app/react/src/server/__snapshots__/cra-config.test.ts.snap index 73d95cb829ec..94715a0f78ef 100644 --- a/app/react/src/server/__snapshots__/cra-config.test.ts.snap +++ b/app/react/src/server/__snapshots__/cra-config.test.ts.snap @@ -41,6 +41,7 @@ Object { "resolve": Object { "alias": Object { "baseAlias": "base-alias", + "react-native": "react-native-web", }, "extensions": Array [ ".js", @@ -109,6 +110,7 @@ Object { "resolve": Object { "alias": Object { "baseAlias": "base-alias", + "react-native": "react-native-web", }, "extensions": Array [ ".js", diff --git a/app/react/src/server/cra-config.test.ts b/app/react/src/server/cra-config.test.ts index 20feeab66037..656f39dcfd2c 100644 --- a/app/react/src/server/cra-config.test.ts +++ b/app/react/src/server/cra-config.test.ts @@ -1,11 +1,7 @@ +/* eslint-disable jest/no-mocks-import */ import fs from 'fs'; import path from 'path'; -import { - applyCRAWebpackConfig, - getModulePath, - getReactScriptsPath, - getTypeScriptRules, -} from './cra-config'; +import { applyCRAWebpackConfig, getReactScriptsPath, getTypeScriptRules } from './cra-config'; import mockRules from './__mocks__/mockRules'; import mockConfig from './__mocks__/mockConfig'; @@ -94,21 +90,6 @@ exit $ret` 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', () => { diff --git a/app/react/src/server/cra-config.ts b/app/react/src/server/cra-config.ts index b38418da6bc0..7f1ba3f27cfe 100644 --- a/app/react/src/server/cra-config.ts +++ b/app/react/src/server/cra-config.ts @@ -6,9 +6,6 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import { normalizeCondition } from 'webpack/lib/RuleSet'; 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']; @@ -71,29 +68,26 @@ export function isReactScriptsInstalled(requiredVersion = '2.0.0') { } 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); - } + 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.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)); - } + // Get any rules contained in rule.rules + if (!rule.test && rule.rules) { + craRules.push(...getRules(extensions)(rule.rules)); + } - return craRules; - }, - [] as RuleSetRule[] - ); + return craRules; + }, [] as RuleSetRule[]); const getStyleRules = getRules(cssExtensions.concat(cssModuleExtensions)); @@ -101,40 +95,20 @@ export const getTypeScriptRules = (webpackConfigRules: RuleSetRule[], configDir: 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; - } + return rules.reduce((accRules, rule) => { + // Resolves an issue where this config is parsed twice (#4903). + if (typeof rule.include !== 'string') { + return [...accRules, rule]; + } - 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; + return [ + ...accRules, + { + ...rule, + include: [rule.include, path.resolve(configDir)], + }, + ]; + }, [] as RuleSetRule[]); }; function mergePlugins(basePlugins: Plugin[], additionalPlugins: Plugin[]) { @@ -177,10 +151,6 @@ export function applyCRAWebpackConfig(baseConfig: Configuration, configDir: stri 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)) @@ -197,6 +167,7 @@ export function applyCRAWebpackConfig(baseConfig: Configuration, configDir: stri // Add css minification for production const plugins = [...baseConfig.plugins]; if (baseConfig.mode === 'production') { + // @ts-ignore plugins.push(new MiniCssExtractPlugin()); } @@ -210,7 +181,13 @@ export function applyCRAWebpackConfig(baseConfig: Configuration, configDir: stri resolve: { ...baseConfig.resolve, extensions: [...baseConfig.resolve.extensions, ...tsExtensions], - modules: baseConfig.resolve.modules.concat(modulePath || []), + modules: Array.from( + new Set([...baseConfig.resolve.modules, ...(craWebpackConfig.resolve.modules || [])]) + ), + alias: { + ...baseConfig.resolve.alias, + ...craWebpackConfig.resolve.alias, + }, }, 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 6c6f56c4d321..c10fac851943 100644 --- a/app/react/src/server/framework-preset-cra.ts +++ b/app/react/src/server/framework-preset-cra.ts @@ -6,18 +6,13 @@ import { applyCRAWebpackConfig, getReactScriptsPath, isReactScriptsInstalled } f type Preset = string | { name: string }; // Disable the built-in preset if the new preset is detected. -const checkForNewPreset = (configDir: string) => { - try { - // eslint-disable-next-line global-require, import/no-dynamic-require - const presets = require(path.resolve(configDir, 'presets.js')); +const checkForNewPreset = (presetsList: Preset[]) => { + const hasNewPreset = presetsList.some((preset: Preset) => { + const presetName = typeof preset === 'string' ? preset : preset.name; + return presetName === '@storybook/preset-create-react-app'; + }); - const hasNewPreset = presets.some((preset: Preset) => { - const presetName = typeof preset === 'string' ? preset : preset.name; - return presetName === '@storybook/preset-create-react-app'; - }); - - return hasNewPreset; - } catch (e) { + 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.' @@ -25,23 +20,29 @@ const checkForNewPreset = (configDir: string) => { logger.warn('The built-in preset will be disabled in Storybook 6.0.'); return false; } + + return true; }; -export function webpackFinal(config: Configuration, { configDir }: { configDir: string }) { - if (checkForNewPreset(configDir)) { - return config; - } +export function webpackFinal( + config: Configuration, + { presetsList, configDir }: { presetsList: Preset[]; configDir: string } +) { if (!isReactScriptsInstalled()) { logger.info('=> Using base config because react-scripts is not installed.'); return config; } + if (checkForNewPreset(presetsList)) { + return config; + } + logger.info('=> Loading create-react-app config.'); return applyCRAWebpackConfig(config, configDir); } -export function managerWebpack(config: Configuration, { configDir }: { configDir: string }) { - if (!isReactScriptsInstalled() || checkForNewPreset(configDir)) { +export function managerWebpack(config: Configuration, { presetsList }: { presetsList: Preset[] }) { + if (!isReactScriptsInstalled() || checkForNewPreset(presetsList)) { return config; } @@ -53,8 +54,8 @@ export function managerWebpack(config: Configuration, { configDir }: { configDir }; } -export function babelDefault(config: Configuration, { configDir }: { configDir: string }) { - if (!isReactScriptsInstalled() || checkForNewPreset(configDir)) { +export function babelDefault(config: Configuration, { presetsList }: { presetsList: Preset[] }) { + if (!isReactScriptsInstalled() || checkForNewPreset(presetsList)) { return config; } diff --git a/app/react/src/server/options.ts b/app/react/src/server/options.ts index 04dc76219b38..5e16a0eaee5b 100644 --- a/app/react/src/server/options.ts +++ b/app/react/src/server/options.ts @@ -2,6 +2,7 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'react', frameworkPresets: [ require.resolve('./framework-preset-react.js'), require.resolve('./framework-preset-cra.js'), diff --git a/app/riot/package.json b/app/riot/package.json index 43f79a266424..536489078716 100644 --- a/app/riot/package.json +++ b/app/riot/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/riot", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for riot.js: View riot snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,7 +33,7 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-alpha.41", + "@storybook/core": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "raw-loader": "^3.1.0", @@ -41,10 +41,10 @@ "ts-dedent": "^1.1.0" }, "devDependencies": { - "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/preset-env": "^7.4.1", + "@babel/plugin-transform-modules-commonjs": "^7.7.0", + "@babel/preset-env": "^7.7.1", "@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", @@ -58,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/riot/src/server/options.js b/app/riot/src/server/options.js index a70e6667624c..fbaea49b8571 100644 --- a/app/riot/src/server/options.js +++ b/app/riot/src/server/options.js @@ -2,5 +2,6 @@ import packageJson from '../../package.json'; export default { packageJson, + framework: 'riot', frameworkPresets: [require.resolve('./framework-preset-riot.js')], }; diff --git a/app/svelte/package.json b/app/svelte/package.json index e0ee7e0221b4..97ecf9df2c51 100644 --- a/app/svelte/package.json +++ b/app/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -23,6 +23,7 @@ "*.d.ts" ], "main": "dist/client/index.js", + "types": "dist/client/index.d.ts", "bin": { "build-storybook": "./bin/build.js", "start-storybook": "./bin/index.js", @@ -32,13 +33,15 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.0" }, "devDependencies": { + "@types/webpack-env": "^1.15.0", "svelte": "^3.4.1", "svelte-loader": "^2.13.4" }, @@ -52,5 +55,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/polymer/src/client/index.js b/app/svelte/src/client/index.ts similarity index 100% rename from app/polymer/src/client/index.js rename to app/svelte/src/client/index.ts diff --git a/app/svelte/src/client/preview/globals.js b/app/svelte/src/client/preview/globals.ts similarity index 100% rename from app/svelte/src/client/preview/globals.js rename to app/svelte/src/client/preview/globals.ts diff --git a/app/svelte/src/client/preview/index.js b/app/svelte/src/client/preview/index.ts similarity index 65% rename from app/svelte/src/client/preview/index.js rename to app/svelte/src/client/preview/index.ts index baf0133c7233..177d45653dae 100644 --- a/app/svelte/src/client/preview/index.js +++ b/app/svelte/src/client/preview/index.ts @@ -15,7 +15,8 @@ export const { } = clientApi; const framework = 'svelte'; -export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); +export const storiesOf = (...args: any) => + clientApi.storiesOf(...args).addParameters({ framework }); +export const configure = (...args: any) => coreConfigure(...args, framework); export { forceReRender }; diff --git a/app/svelte/src/client/preview/render.js b/app/svelte/src/client/preview/render.ts similarity index 91% rename from app/svelte/src/client/preview/render.js rename to app/svelte/src/client/preview/render.ts index c69b7634b631..76f8b005258a 100644 --- a/app/svelte/src/client/preview/render.js +++ b/app/svelte/src/client/preview/render.ts @@ -1,19 +1,21 @@ import { document } from 'global'; import dedent from 'ts-dedent'; +import { MountViewArgs, RenderMainArgs } from './types'; -let previousComponent = null; +type Component = any; + +let previousComponent: Component = null; function cleanUpPreviousStory() { if (!previousComponent) { return; } - previousComponent.$destroy(); previousComponent = null; } -function mountView({ Component, target, props, on, Wrapper, WrapperData }) { - let component; +function mountView({ Component, target, props, on, Wrapper, WrapperData }: MountViewArgs) { + let component: Component; if (Wrapper) { const fragment = document.createDocumentFragment(); @@ -47,8 +49,7 @@ export default function render({ selectedStory, showMain, showError, - // showException, -}) { +}: RenderMainArgs) { const { /** @type {SvelteComponent} */ Component, diff --git a/app/svelte/src/client/preview/types.ts b/app/svelte/src/client/preview/types.ts new file mode 100644 index 000000000000..31bf80ea6267 --- /dev/null +++ b/app/svelte/src/client/preview/types.ts @@ -0,0 +1,33 @@ +import { StoryFn } from '@storybook/addons'; + +export interface ShowErrorArgs { + title: string; + description: string; +} + +export interface RenderMainArgs { + storyFn: StoryFn<any>; + selectedKind: string; + selectedStory: string; + showMain: () => void; + showError: (args: ShowErrorArgs) => void; +} + +export interface MountViewArgs { + Component: any; + target: any; + props: MountProps; + on: any; + Wrapper: any; + WrapperData: any; +} + +interface MountProps { + rounded: boolean; + text: string; +} + +interface WrapperData { + innerStyle: string; + style: string; +} diff --git a/app/svelte/src/server/build.js b/app/svelte/src/server/build.ts similarity index 100% rename from app/svelte/src/server/build.js rename to app/svelte/src/server/build.ts diff --git a/app/svelte/src/server/framework-preset-svelte.js b/app/svelte/src/server/framework-preset-svelte.ts similarity index 78% rename from app/svelte/src/server/framework-preset-svelte.js rename to app/svelte/src/server/framework-preset-svelte.ts index 6fcbfb1f79c6..d29eaf8ec519 100644 --- a/app/svelte/src/server/framework-preset-svelte.js +++ b/app/svelte/src/server/framework-preset-svelte.ts @@ -1,4 +1,6 @@ -export function webpack(config) { +import { Configuration } from 'webpack'; // eslint-disable-line + +export function webpack(config: Configuration) { return { ...config, module: { diff --git a/app/svelte/src/server/index.js b/app/svelte/src/server/index.ts similarity index 100% rename from app/svelte/src/server/index.js rename to app/svelte/src/server/index.ts diff --git a/app/svelte/src/server/options.js b/app/svelte/src/server/options.ts similarity index 59% rename from app/svelte/src/server/options.js rename to app/svelte/src/server/options.ts index 3e4a77d76407..64c8aa3cd589 100644 --- a/app/svelte/src/server/options.js +++ b/app/svelte/src/server/options.ts @@ -1,6 +1,7 @@ -import packageJson from '../../package.json'; +const packageJson = require('../../package.json'); export default { packageJson, + framework: 'svelte', frameworkPresets: [require.resolve('./framework-preset-svelte.js')], }; diff --git a/app/svelte/src/typings.d.ts b/app/svelte/src/typings.d.ts new file mode 100644 index 000000000000..6288cba4b09a --- /dev/null +++ b/app/svelte/src/typings.d.ts @@ -0,0 +1,2 @@ +declare module '@storybook/core/*'; +declare module 'global'; diff --git a/addons/ondevice-backgrounds/tsconfig.json b/app/svelte/tsconfig.json similarity index 64% rename from addons/ondevice-backgrounds/tsconfig.json rename to app/svelte/tsconfig.json index 8876bb6737a1..29fcd6ad6a26 100644 --- a/addons/ondevice-backgrounds/tsconfig.json +++ b/app/svelte/tsconfig.json @@ -2,12 +2,13 @@ "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", - "types": ["webpack-env"] + "types": ["webpack-env"], + "resolveJsonModule": true }, "include": [ "src/**/*" ], "exclude": [ - "src/__tests__/**/*" + "src/**/*.test.*" ] } diff --git a/app/vue/README.md b/app/vue/README.md index 97c17bdc3374..d9fb66ea933f 100644 --- a/app/vue/README.md +++ b/app/vue/README.md @@ -28,7 +28,7 @@ You can also build a [static version](https://storybook.js.org/basics/exporting- ## Vue Notes -- When using global custom components or extension (e.g `Vue.use`). You will need to declare those in the `./storybook/config.js`. +- When using global custom components or extension (e.g `Vue.use`). You will need to declare those in the `./storybook/preview.js`. ## Known Limitations diff --git a/app/vue/package.json b/app/vue/package.json index cd3c783fa399..b806b71cd848 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,9 +33,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@types/webpack-env": "^1.15.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -44,8 +44,8 @@ }, "devDependencies": { "@types/mini-css-extract-plugin": "^0.8.0", - "@types/node": "^12.7.9", - "@types/webpack": "^4.4.32", + "@types/node": "^12.12.11", + "@types/webpack": "^4.41.0", "babel-preset-vue": "^2.0.2", "vue": "^2.6.8", "vue-loader": "^15.7.0", @@ -62,5 +62,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/vue/src/server/options.ts b/app/vue/src/server/options.ts index 1351e17477da..46df1823ce8a 100644 --- a/app/vue/src/server/options.ts +++ b/app/vue/src/server/options.ts @@ -2,5 +2,6 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'vue', frameworkPresets: [require.resolve('./framework-preset-vue.js')], }; diff --git a/app/web-components/README.md b/app/web-components/README.md index bb59107daade..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) @@ -57,17 +57,19 @@ By default storybook only works with precompiled es5 code but as most web compon For example if you have a library called `my-library` which is in es7 then you can add it like so ```js -// .storybook/webpack.config.js - -module.exports = ({ 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 - ); - // add your own `my-library` - webComponentsRule.test.push(new RegExp(`node_modules(\\/|\\\\)my-library(.*)\\.js$`)); - - return config; +// .storybook/main.js + +module.exports = { + 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 + ); + // add your own `my-library` + webComponentsRule.test.push(new RegExp(`node_modules(\\/|\\\\)my-library(.*)\\.js$`)); + + return config; + }, }; ``` diff --git a/app/web-components/package.json b/app/web-components/package.json index 723b0ea54797..2d228d160e2e 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit-html", @@ -37,9 +37,9 @@ "dependencies": { "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@types/webpack-env": "^1.13.9", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@types/webpack-env": "^1.15.0", "babel-plugin-bundled-import-meta": "^0.3.1", "core-js": "^3.0.1", "global": "^4.3.2", @@ -58,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/app/web-components/src/client/preview/types.ts b/app/web-components/src/client/preview/types.ts index 36432e1a4558..f1fff195fe44 100644 --- a/app/web-components/src/client/preview/types.ts +++ b/app/web-components/src/client/preview/types.ts @@ -1,6 +1,8 @@ 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 type StoryFnHtmlReturnType = string | Node | TemplateResult | SVGTemplateResult; export interface IStorybookStory { name: string; diff --git a/app/web-components/src/server/options.ts b/app/web-components/src/server/options.ts index 5217ce3a23b9..6dff94b2b6d2 100644 --- a/app/web-components/src/server/options.ts +++ b/app/web-components/src/server/options.ts @@ -3,5 +3,6 @@ const packageJson = require('../../package.json'); export default { packageJson, + framework: 'web-components', frameworkPresets: [require.resolve('./framework-preset-web-components.js')], }; diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index fcfde0bd5284..000000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Node.js -# Build a general Node.js project with npm. -# Add steps that analyze code, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript - -trigger: -- master - -pool: - vmImage: 'ubuntu-latest' - -steps: -- task: NodeTool@0 - inputs: - versionSpec: '10.x' - displayName: 'Install Node.js' - -- script: | - yarn install - yarn bootstrap --core - yarn build-storybooks - displayName: 'examples' diff --git a/cypress/support/commands.js b/cypress/support/commands.js index d7377c5df89d..ecad1e42571a 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,5 +1,4 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable jest/valid-expect */ +/* 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 diff --git a/dev-kits/addon-decorator/package.json b/dev-kits/addon-decorator/package.json index a2af1940b176..a9d48686f154 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "decorator addon for storybook", "keywords": [ "addon", @@ -24,12 +24,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.4.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/dev-kits/addon-parameter/package.json b/dev-kits/addon-parameter/package.json index 0e58408efae5..df98782ece5c 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -24,12 +24,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -37,5 +37,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/dev-kits/addon-preview-wrapper/package.json b/dev-kits/addon-preview-wrapper/package.json index 9176713035e7..461e3b0d6c28 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "preview wrapper addon for storybook", "keywords": [ "addon", @@ -24,10 +24,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", "react": "^16.8.3" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/dev-kits/addon-preview-wrapper/src/register.tsx b/dev-kits/addon-preview-wrapper/src/register.tsx index 6fe81015ff16..607010e05cae 100644 --- a/dev-kits/addon-preview-wrapper/src/register.tsx +++ b/dev-kits/addon-preview-wrapper/src/register.tsx @@ -3,10 +3,14 @@ import { addons, types } from '@storybook/addons'; import { ADDON_ID } from './constants'; const PreviewWrapper: FunctionComponent<{}> = p => ( - <div className="my-edit-wrapper"> - <button type="button" onClick={() => {}}> - Edit this page - </button> + <div + style={{ + width: '100%', + height: '100%', + boxSizing: 'border-box', + boxShadow: 'inset 0 0 10px black', + }} + > {p.children} </div> ); diff --git a/dev-kits/addon-roundtrip/package.json b/dev-kits/addon-roundtrip/package.json index 1c114a98c3a2..c0e2fc1cdffc 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-alpha.41", + "version": "6.0.0-alpha.2", "description": "roundtrip addon for storybook", "keywords": [ "addon", @@ -24,13 +24,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -38,5 +38,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js index eb5ac5630a33..1cd31b7c1040 100644 --- a/docs/gatsby-config.js +++ b/docs/gatsby-config.js @@ -28,6 +28,7 @@ module.exports = { '/guides/guide-svelte/', ], configurations: [ + '/configurations/overview/', '/configurations/options-parameter/', '/configurations/default-config/', '/configurations/custom-webpack-config/', diff --git a/docs/package.json b/docs/package.json index 7bbb8ab614ea..7502ac066599 100644 --- a/docs/package.json +++ b/docs/package.json @@ -32,23 +32,23 @@ "gatsby-source-filesystem": "^1.5.39", "gatsby-transformer-remark": "^1.7.44", "global": "^4.4.0", - "html-react-parser": "^0.9.1", + "html-react-parser": "^0.10.0", "is-builtin-module": "^3.0.0", "lodash": "^4.17.15", "marked": "^0.7.0", "polished": "^3.4.2", "prop-types": "^15.7.2", - "react": "^16.11.0", + "react": "^16.12.0", "react-document-title": "^2.0.3", - "react-dom": "^16.11.0", + "react-dom": "^16.12.0", "react-helmet": "^5.2.0", "react-popper-tooltip": "^2.10.0", "react-router": "^4.3.1", "react-stack-grid": "^0.7.1", "recompose": "^0.30.0", "sitemap": "^4.1.1", - "styled-components": "^4.4.0", + "styled-components": "^4.4.1", "ts-dedent": "^1.1.0", - "validatorjs": "^3.17.1" + "validatorjs": "^3.18.0" } } diff --git a/docs/src/new-components/basics/shared/site.js b/docs/src/new-components/basics/shared/site.js index 3ab939a7cc35..77fc9996abc3 100644 --- a/docs/src/new-components/basics/shared/site.js +++ b/docs/src/new-components/basics/shared/site.js @@ -10,7 +10,7 @@ export const metadata = { description: `Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular`, ogImage: '/images/social/open-graph.png', googleSiteVerification: '', - latestVersion: 'v5.1', + latestVersion: 'v5.3', }; export const url = { @@ -37,7 +37,7 @@ export const url = { mithril: `${npmApiBase}/@storybook/mithril`, marko: `${npmApiBase}/@storybook/marko`, riot: `${npmApiBase}/@storybook/riot`, - polymer: `${npmApiBase}/@storybook/polymer`, + 'web-components': `${npmApiBase}/@storybook/web-components`, preact: `${npmApiBase}/@storybook/preact`, }, diff --git a/docs/src/new-components/basics/tooltip/TooltipLinkList.js b/docs/src/new-components/basics/tooltip/TooltipLinkList.js index 9c9a8d68b85f..e6b52b8d39d4 100644 --- a/docs/src/new-components/basics/tooltip/TooltipLinkList.js +++ b/docs/src/new-components/basics/tooltip/TooltipLinkList.js @@ -7,7 +7,7 @@ const List = styled.div` min-width: 180px; overflow: hidden; overflow-y: auto; - maxheight: ${10.5 * 32 /* 10.5 items */}; + max-height: ${11.5 * 32 /* 11.5 items */}; `; function TooltipLinkList({ links, LinkWrapper }) { diff --git a/docs/src/new-components/basics/tooltip/TooltipMessage.stories.js b/docs/src/new-components/basics/tooltip/TooltipMessage.stories.js index ae615685aefb..52c5c7e3ff26 100644 --- a/docs/src/new-components/basics/tooltip/TooltipMessage.stories.js +++ b/docs/src/new-components/basics/tooltip/TooltipMessage.stories.js @@ -32,7 +32,10 @@ storiesOf('basics/tooltip/TooltipMessage', module) <TooltipMessage title="Lorem ipsum dolor sit" desc="Amet consectatur vestibulum concet durum politu coret weirom" - links={[{ title: 'Get more tips', href: 'test' }, { title: 'Done', href: 'test' }]} + links={[ + { title: 'Get more tips', href: 'test' }, + { title: 'Done', href: 'test' }, + ]} /> )) .add('minimal message', () => ( diff --git a/docs/src/pages/addons/addon-gallery/index.md b/docs/src/pages/addons/addon-gallery/index.md index 37a856bb68f6..db1d9aefb60b 100644 --- a/docs/src/pages/addons/addon-gallery/index.md +++ b/docs/src/pages/addons/addon-gallery/index.md @@ -90,6 +90,10 @@ You can even run these tests inside a CI box. With this addon, you can showcase multiple components (or varying component states) within 1 story. Break your stories down into smaller categories (chapters) and subcategories (sections) for more organizational goodness. +### [Paddings](https://github.com/rbardini/storybook-addon-paddings) + +Add different paddings to your preview. Useful for checking how components behave when surrounded with white space. + ### [Props Combinations](https://github.com/evgenykochetkov/react-storybook-addon-props-combinations) Given possible values for each prop, renders your component with all combinations of prop values. diff --git a/docs/src/pages/addons/api/index.md b/docs/src/pages/addons/api/index.md index 30fa19f67ed5..f41611d09d75 100644 --- a/docs/src/pages/addons/api/index.md +++ b/docs/src/pages/addons/api/index.md @@ -215,16 +215,18 @@ With this method, you can select a story via an API. This method accepts two par Let's say you've got a story like this: ```jsx -import { storiesOf } from '@storybook/react'; +export default { + title: 'heading', +}; -storiesOf('heading', module).add('with text', () => <h1>Hello world</h1>); +export const withText = () => <h1>Hello world</h1>; ``` This is how you can select the above story: ```jsx addons.register('my-organisation/my-addon', api => { - api.selectStory('heading', 'with text'); + api.selectStory('heading', 'withText'); }); ``` @@ -234,7 +236,7 @@ Same as `selectStory`, but accepts a story inside current kind as the only param ```jsx addons.register('my-organisation/my-addon', api => { - api.selectInCurrentKind('with text'); + api.selectInCurrentKind('withText'); }); ``` @@ -283,14 +285,13 @@ addons.register('my-organisation/my-addon', api => { }).url; }); ``` -### deprecated APIs -#### api.onStory(fn) +### api.on(eventName, fn) This method allows you to register a handler function which will be called whenever the user navigates between stories. ```jsx addons.register('my-organisation/my-addon', api => { - api.onStory((kind, story) => console.log(kind, story)); + api.on('some-event', (eventData) => console.log(eventData)); }); ``` diff --git a/docs/src/pages/addons/introduction/index.md b/docs/src/pages/addons/introduction/index.md index c7828fa51041..26087ed917cb 100644 --- a/docs/src/pages/addons/introduction/index.md +++ b/docs/src/pages/addons/introduction/index.md @@ -25,86 +25,47 @@ const Center = ({ children }) => <div style={styles}>{children}</div>; Then we can use it when writing stories. ```js -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; - import Center from './center'; import Button from './button'; -storiesOf('Button', module).add('with text', () => ( +export default { + title: 'Button', +}; + +export const defaultView = () => ( <Center> - <Button onClick={action('clicked')}>Hello Button</Button> + <Button>Hello Button</Button> </Center> -)); +); ``` ### Storybook Decorators -You can also expose this functionality as a Storybook decorator and use it like this. +You can also expose this functionality as a Storybook decorator and use it like this: ```js -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; - import Button from './button'; +import Center from './center'; -const styles = { - textAlign: 'center', +export default { + title: 'Button', + decorators: [storyFn => <Center>{storyFn()}</Center>], }; -const CenterDecorator = storyFn => <div style={styles}>{storyFn()}</div>; - -storiesOf('Button', module) - .addDecorator(CenterDecorator) - .add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>) - .add('with some emojies', () => ( - <Button onClick={action('clicked')}> - <span role="img" aria-label="so cool"> - 😀 😎 👍 💯 - </span> - </Button> - )); + +export const defaultView = () => ( + <Button>Hello Button</Button> +); ``` You can also add a decorator globally for all stories like this: -```js -import { storiesOf, addDecorator } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { linkTo } from '@storybook/addon-links'; - -import Button from './button'; -import Welcome from './welcome'; - -const styles = { - textAlign: 'center', -}; -const CenterDecorator = storyFn => <div style={styles}>{storyFn()}</div>; -addDecorator(CenterDecorator); - -storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />); - -storiesOf('Button', module) - .add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>) - .add('with some emojies', () => ( - <Button onClick={action('clicked')}> - <span role="img" aria-label="so cool"> - 😀 😎 👍 💯 - </span> - </Button> - )); -``` - -You can call `addDecorator()` inside the story definition file as shown above, but **adding it to the Storybook config file is a much better option**. That would look like this in `config.js`: +in `.storybook/preview.js`: ```js -import React from 'react' import { addDecorator } from '@storybook/react'; +import Center from './center'; -const styles = { - textAlign: 'center', -}; -const CenterDecorator = storyFn => <div style={styles}>{storyFn()}</div>; -addDecorator(CenterDecorator); +addDecorator(storyFn => <Center>{storyFn()}</Center>); ``` ## 2. Native Addons diff --git a/docs/src/pages/addons/using-addons/index.md b/docs/src/pages/addons/using-addons/index.md index 3a058822cae7..8868fbdeb00b 100644 --- a/docs/src/pages/addons/using-addons/index.md +++ b/docs/src/pages/addons/using-addons/index.md @@ -3,6 +3,8 @@ id: 'using-addons' title: 'Using Addons' --- +> 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 comes with a variety of "core" addons developed and maintained alongside Storybook. Most examples in this site use [actions](https://github.com/storybookjs/storybook/tree/master/addons/actions) and [links](https://github.com/storybookjs/storybook/tree/master/addons/links). But you can use any third party addons distributed via NPM. Here's how to do it. @@ -15,12 +17,16 @@ First, we need to install the addons: yarn add -D @storybook/addons @storybook/addon-actions @storybook/addon-knobs @storybook/addon-notes ``` -Then, we need to create a file called `addons.js` inside the storybook config directory and add the following content: +within `.storybook/main.js`: ```js -import '@storybook/addon-actions/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-notes/register'; +module.exports = { + addons: [ + '@storybook/addon-actions/register', + '@storybook/addon-knobs/register', + '@storybook/addon-notes/register', + ], +}; ``` Once created, you'll have to restart storybook to make the underlying webpack aware of the addons file. @@ -31,34 +37,33 @@ This will register all the addons and you'll be able to see the actions and knob ## Addons tab order -The tab order is created by the import order in the `addons.js` file. In the example, the actions addon is the first and thus active tab. Resorting the imports results in the knobs addon tab being placed before the actions tab: - -```js -import '@storybook/addon-actions/register'; -import '@storybook/addon-knobs/register'; -``` +The tab order is created by order in which they appear in the array in the `main.js` file. ## Using the addon Now when you are writing a story, you can import the actions addon to log actions. Also, you can add notes: ```js -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; - import Button from './Button'; -storiesOf('Button', module).add( - 'with some emoji', - () => ( - <Button onClick={action('clicked')}> - <span role="img" aria-label="so cool"> - 😀 😎 👍 💯 - </span> - </Button> - ), - { notes: 'A small component' } +export default { + title: 'Button', + component: Button, +}; + +export const buttonWithEmoji = () => ( + <Button onClick={action('clicked')}> + <span role="img" aria-label="so cool"> + 😀 😎 👍 💯 + </span> + </Button> ); +buttonWithEmoji.story = { + parameters: { + notes: 'A small component', + }, +}; ``` Then you'll be able to see those notes when you are viewing the story. @@ -70,41 +75,38 @@ Then you'll be able to see those notes when you are viewing the story. You can disable an addon panel for a story by adding a `disabled` parameter. ```js -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; - import Button from './Button'; -storiesOf('Button', module).add( - 'with some emoji', - () => ( - <Button onClick={action('clicked')}> - <span role="img" aria-label="so cool"> - 😀 😎 👍 💯 - </span> - </Button> - ), - { notes: { disabled: true } } +export default { + title: 'Button', + component: Button, +}; + +export const buttonWithEmoji = () => ( + <Button onClick={action('clicked')}> + <span role="img" aria-label="so cool"> + 😀 😎 👍 💯 + </span> + </Button> ); +buttonWithEmoji.story = { + parameters: { + notes: { disabled: true } + } +}; ``` ## Global Configuration -Sometimes you might want to configure an addon globally, as in the case of collocating stories with components, or to keep your stories file cleaner. To do that, you can add your decorators to a config file, typically in `.storybook/config.js`. Here's an example of how you might do that. +Sometimes you might want to configure an addon globally, as in the case of collocating stories with components, or to keep your stories file cleaner. To do that, you can add your decorators to a config file, typically in `.storybook/preview.js`. Here's an example of how you might do that. ```js -import { configure, addParameters } from '@storybook/react'; +import { addParameters } from '@storybook/react'; addParameters({ - options: { - name: 'CRA Kitchen Sink', - isFullscreen: false, - showPanel: true, - // more configuration here - }, + notes: 'global notes', }); ``` -Here's an example of a [production-ready config file](https://github.com/storybookjs/storybook/blob/next/examples/cra-kitchen-sink/.storybook/config.js) from the cra-kitchen example. - Just like this, you can install any other addon and use it. Have a look at our [addon gallery](https://storybook.js.org/addons/) to discover more addons. diff --git a/docs/src/pages/addons/writing-addons/index.md b/docs/src/pages/addons/writing-addons/index.md index 8538601e6d00..85c4fedc0c71 100644 --- a/docs/src/pages/addons/writing-addons/index.md +++ b/docs/src/pages/addons/writing-addons/index.md @@ -3,6 +3,8 @@ id: 'writing-addons' title: 'Writing Addons' --- +> 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. + This is a complete guide on how to create addons for Storybook. ## Storybook Basics @@ -42,16 +44,16 @@ We write a story for our addon like this: ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; - import Button from './Button'; -storiesOf('Button', module) - .add('with text', () => <Button>Hello Button</Button>, { +export default { + title: 'Button', + parameters: { myAddon: { data: 'this data is passed to the addon', }, - }); + }, +}; ``` ### Add a panel @@ -62,7 +64,6 @@ We write an addon that responds to a change in story selection like so: // register.js import React from 'react'; -import { STORY_RENDERED } from '@storybook/core-events'; import { addons, types } from '@storybook/addons'; import { useParameter } from '@storybook/api'; import { AddonPanel } from '@storybook/components'; @@ -96,10 +97,12 @@ addons.register(ADDON_ID, api => { ### register the addon -Then create an `addons.js` inside the Storybook config directory and add the following content to it. +within `.storybook/main.js`: ```js -import 'path/to/register.js'; +module.exports = { + addons: ['path/to/register.js'] +} ``` Now restart/rebuild storybook and the addon should show up! @@ -168,7 +171,7 @@ A very convenient way of using the channel in the manager is using the `useChann ```js import React from 'react'; -import addons from '@storybook/addons'; +import { addons } from '@storybook/addons'; import { useChannel } from '@storybook/api'; import { STORY_CHANGED } from '@storybook/core-events'; import { AddonPanel } from '@storybook/components'; @@ -228,7 +231,13 @@ This is also a great way to sync state between multiple components of the same a ### Using the complex addon -Add the `register.js` to your `addons.js` file. +within `.storybook/main.js`: + +```js +module.exports = { + addons: ['path/to/register.js'] +} +``` Then you need to start using the decorator: @@ -239,13 +248,19 @@ import withMyAddon from 'path/to/index.js'; import Button from './Button'; -storiesOf('Button', module) - .addDecorator(withMyAddon) - .add('with text', () => <Button>Hello Button</Button>, { - myParameter: { - data: 'awesome', - }, - }); +export default { + title: 'Button', + decorators: [withMyAddon], +}; + +export const defaultView = () => ( + <Button>Hello Button</Button> +); +defaultView.story = { + parameters: { + myParameter: { data: 'awesome' }, + }, +}; ``` ### Disabling an addon panel @@ -267,14 +282,19 @@ addons.register(ADDON_ID, () => { While adding a story, you can then pass a `disabled` parameter. ```js -storiesOf('Button', module) - .add('with text', () => <Button>Hello Button</Button>, { - myAddon: { - disabled: true, - }, - }); -``` +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import withMyAddon from 'path/to/index.js'; +export default { + title: 'Button', + decorators: [withMyAddon], + parameters: { + myAddon: { disable: true }, + }, +}; + +``` ## Styling your addon diff --git a/docs/src/pages/basics/exporting-storybook/index.md b/docs/src/pages/basics/exporting-storybook/index.md index ea91a2186003..a43f154e1bff 100644 --- a/docs/src/pages/basics/exporting-storybook/index.md +++ b/docs/src/pages/basics/exporting-storybook/index.md @@ -6,7 +6,7 @@ title: 'Exporting Storybook as a Static App' Storybook gives a great developer experience with its dev time features, like instant change updates via Webpack's HMR. But Storybook is also a tool you can use to showcase your components to others. -Demos of [React Native Web](http://necolas.github.io/react-native-web/storybook/) and [React Dates](http://airbnb.io/react-dates/) are a good example for that. +Demos of [React Native Web](https://necolas.github.io/react-native-web/docs/) and [React Dates](http://airbnb.io/react-dates/) are a good example for that. For that, Storybook comes with a tool to export your storybook into a static web app. Then you can deploy it to GitHub pages or any static hosting service. @@ -43,11 +43,15 @@ Or, you can export your storybook into the docs directory and use it as the root - Install the [Now CLI](https://github.com/zeit/now): - `npm i -g now` +```sh +npm i -g now +``` - Configure your `build` script: - `"build": "build-storybook -c .storybook -o public"` +``` +`"build": "build-storybook -c .storybook -o build"` +``` - Execute `now` on your terminal. diff --git a/docs/src/pages/basics/faq/index.md b/docs/src/pages/basics/faq/index.md index 7b7020393443..2ac41d671524 100644 --- a/docs/src/pages/basics/faq/index.md +++ b/docs/src/pages/basics/faq/index.md @@ -22,7 +22,18 @@ Next automatically defines `React` for all of your files via a babel plugin. You ### How do I setup Storybook to share Webpack configuration with Next.js? -You can generally reuse webpack rules by placing them in a file that is `require()`-ed from both your `next.config.js` and your `.storybook/webpack.config.js` files. For example, [this gist](https://gist.github.com/metasean/cadd2becd60cc3b295bf49895a56f9b4) sets both next.js and storybook up with global stylesheets. +You can generally reuse webpack rules by placing them in a file that is `require()`-ed from both your `next.config.js` and your `.storybook/main.js` files. For example: + +```js +module.exports = { + webpackFinal: async (baseConfig) => { + const nextConfig = require('/path/to/next.config.js'); + + // merge whatever from nextConfig into the webpack config storybook will use + return { ...baseConfig }; + }, +}; +``` ### Why is there no addons channel? @@ -42,22 +53,23 @@ A common error is that an addon tries to access the "channel", but the channel i Not directly. If you control the component source, you can do something like this: ```js -import React, { Component} from 'react'; -import { storiesOf } from '@storybook/react'; +import React, { Component } from 'react'; + +export default { + title: 'MyComponent', +}; class MyComponent extends Component { constructor(props) { - super(props) + super(props); this.state = { someVar: 'defaultValue', - ...props.initialState - } + ...props.initialState, + }; } // ... -} +}; -storiesOf('MyComponent', module) - .add('default', () => <MyComponent />) - .add('other', () => <MyComponent initialState={{ someVar: 'otherVal' }} />); +export const defaultView = () => <MyComponent initialState={} />; ``` diff --git a/docs/src/pages/basics/introduction/index.md b/docs/src/pages/basics/introduction/index.md index e2969b0c8996..d3beedd52bbe 100644 --- a/docs/src/pages/basics/introduction/index.md +++ b/docs/src/pages/basics/introduction/index.md @@ -16,6 +16,6 @@ A [Static version](/basics/exporting-storybook) of Storybook can also be built a Here are some featured Storybooks to see how it works: - [Demo of React Dates](http://airbnb.io/react-dates/) - [source](https://github.com/airbnb/react-dates) -- [Demo of React Native Web](http://necolas.github.io/react-native-web/storybook/) - [source](https://github.com/necolas/react-native-web) +- [Demo of React Native Web](https://necolas.github.io/react-native-web/docs/) - [source](https://github.com/necolas/react-native-web) Read the Learn Storybook [tutorial](https://www.learnstorybook.com) for a step by step guide to building an app with Storybook and to see how building components in isolation can supercharge your app development workflow. diff --git a/docs/src/pages/basics/live-examples/index.md b/docs/src/pages/basics/live-examples/index.md index 4f236c045a10..59b7d99b6440 100644 --- a/docs/src/pages/basics/live-examples/index.md +++ b/docs/src/pages/basics/live-examples/index.md @@ -8,7 +8,6 @@ title: 'Live Examples' - [React Official](https://storybookjs-next.now.sh/official-storybook/) - [Vue](https://storybookjs-next.now.sh/vue-kitchen-sink/) - [Angular](https://storybookjs-next.now.sh/angular-cli/) -- [Polymer](https://storybookjs-next.now.sh/polymer-cli/) - [Mithril](https://storybookjs-next.now.sh/mithril-kitchen-sink/) - [Marko](https://storybookjs-next.now.sh/marko-cli/) - [HTML](https://storybookjs-next.now.sh/html-kitchen-sink/) @@ -23,7 +22,6 @@ title: 'Live Examples' - [React Official](https://storybookjs.now.sh/official-storybook/) - [Vue](https://storybookjs.now.sh/vue-kitchen-sink/) - [Angular](https://storybookjs.now.sh/angular-cli/) -- [Polymer](https://storybookjs.now.sh/polymer-cli/) - [Mithril](https://storybookjs.now.sh/mithril-kitchen-sink/) - [Marko](https://storybookjs.now.sh/marko-cli/) - [HTML](https://storybookjs.now.sh/html-kitchen-sink/) diff --git a/docs/src/pages/basics/toolbar-guide/index.md b/docs/src/pages/basics/toolbar-guide/index.md new file mode 100644 index 000000000000..fcaaecc75a9d --- /dev/null +++ b/docs/src/pages/basics/toolbar-guide/index.md @@ -0,0 +1,63 @@ +--- +id: 'toolbar-guide' +title: 'Toolbar' +--- + +Storybook comes with a toolbar which is displayed by default as part of the layout with a default height of `40px`. + +The user can toggle whether to hide or display the toolbar. It is on display by default and set on the canvas tab. This feature can be toggled as one of the [Global options](https://storybook.js.org/docs/configurations/options-parameter/). + +To hide or display the toolbar, the user should toggle the `isToolshown` key in `config.js`, + +```jsx +addParameter({ + options: { + /** + * show/hide tool bar + * @type {Boolean} + */ + isToolshown: true, + }, +}); +``` + +The toolbar is not an addon but provides easy access to them. Both Canvas and docs are tabs within the toolbar. While on the canvas tab users have access to the following default features: + +- Zoom-in, zoom-out, and reset-zoom on the preview +- Change the background of the preview +- Adjust for color blindness emulation +- Make the preview full screen +- Open the canvas in a new tab +- Copy canvas link + +On the other hand, the [DocsPage](https://github.com/storybookjs/storybook/tree/master/addons/docs#docspage) is also not an addon but the successor to addon-info. The user has access to the DocsPage when `Docs` is installed. It is added to the toolbar by default with zero config required. + +A user can [write custom addons](https://storybook.js.org/docs/addons/writing-addons/) and add them to the toolbar. This can be achieved by modifying a the value for `type` when registering an addon. + +For an addon to be displayed in the toolbar, the user must add the `TAB` type to the addon. The user must also add the `route` and `match` key/value pairs. + +```jsx +import React from 'react'; +import { addons, types } from '@storybook/addons'; + +const ADDON_ID = 'myaddon'; +const PARAM_KEY = 'myAddon'; +const PANEL_ID = `${ADDON_ID}/tab`; + +addons.register(ADDON_ID, api => { + const render = ({ active, key }) => ( + <div> + <SomeComponent /> + </div> + ); + const title = 'My Addon'; + + addons.add(PANEL_ID, { + type: types.TAB, + route: ({ storyId }) => `/info/${storyId}`, + match: ({ viewMode }) => viewMode === 'info', + title, + render, + }); +}); +``` diff --git a/docs/src/pages/basics/writing-stories/index.md b/docs/src/pages/basics/writing-stories/index.md index 1764fb9cb922..f1387f7dd4df 100644 --- a/docs/src/pages/basics/writing-stories/index.md +++ b/docs/src/pages/basics/writing-stories/index.md @@ -3,6 +3,8 @@ id: 'writing-stories' title: 'Writing Stories' --- +> 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. + A Storybook is a collection of stories. Each story represents a single visual state of a component. > Technically, a story is a function that returns something that can be rendered to screen. @@ -93,17 +95,26 @@ It's up to you to find a naming/placing scheme that works for your project/team. ## Loading stories -Stories are loaded in the `.storybook/config.js` file. +Stories are loaded in the `.storybook/main.js` file or `.storybook/preview.js` file. The most convenient way to load stories is by filename. For example, if your stories files are located in the `src/components` directory, you can use the following snippet: +```js +// .storybook/main.js +module.exports = { + stories: ['../src/components/**/*.stories.js'], +}; +``` + +Alternatively you can import all your stories in `.storybook/preview.js`: + ```js import { configure } from '@storybook/react'; configure(require.context('../src/components', true, /\.stories\.js$/), module); ``` -> NOTE: The `configure` function should be called only once in `config.js`. +> NOTE: The `configure` function should be called only once in `.storybook/preview.js`. The `configure` function accepts: @@ -116,10 +127,13 @@ If you want to load from multiple locations, you can use an array: ```js import { configure } from '@storybook/react'; -configure([ - require.context('../src/components', true, /\.stories\.js$/), - require.context('../lib', true, /\.stories\.js$/) -], module); +configure( + [ + require.context('../src/components', true, /\.stories\.js$/), + require.context('../lib', true, /\.stories\.js$/), + ], + module +); ``` Or if you want to do some custom loading logic, you can use a loader function. Just remember to return an array of module exports if you want to use Component Story Format. Here's an example that forces files to load in a specific order. @@ -127,12 +141,12 @@ Or if you want to do some custom loading logic, you can use a loader function. J ```js import { configure } from '@storybook/react'; -const loaderFn = () => ([ +const loaderFn = () => [ require('./welcome.stories.js'), require('./prelude.stories.js'), require('./button.stories.js'), require('./input.stories.js'), -]); +]; configure(loaderFn, module); ``` @@ -152,7 +166,7 @@ const loaderFn = () => { configure(loaderFn, module); ``` -Storybook uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#require-context) to load modules dynamically. Take a look at the relevant Webpack [docs](https://webpack.js.org/guides/dependency-management/#require-context) to learn more about how to use `require.context`. +Storybook uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#requirecontext) to load modules dynamically. Take a look at the relevant Webpack [docs](https://webpack.js.org/guides/dependency-management/#requirecontext) to learn more about how to use `require.context`. If you are using the `storiesOf` API directly, or are using `@storybook/react-native` where CSF is unavailable, you should use a loader function with **no return value**: @@ -163,7 +177,7 @@ const loaderFn = () => { // manual loading require('./welcome.stories.js'); require('./button.stories.js'); - + // dynamic loading, unavailable in react-native const req = require.context('../src/components', true, /\.stories\.js$/); req.keys().forEach(fname => req(fname)); @@ -180,17 +194,25 @@ A decorator is a way to wrap a story with a common set of components, for exampl Decorators can be applied globally, at the component level, or individually at the story level. Global decorators are typically applied in the Storybook config files, and component/story decorators are applied in the story file. -Here is an example of a global decorator which centers every story in the storybook: +Here is an example of a global decorator which centers every story in the `.storybook/preview.js`: ```jsx import React from 'react'; -import { load, addDecorator } from '@storybook/react'; +import { addDecorator } from '@storybook/react'; addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>); - -load(require.context('../src/components', true, /\.stories\.js$/), module); ``` +> \* In Vue projects you have to use the special component `<story/>` instead of the function parameter `storyFn` that is used in React projects, even if you are using JSX, for example: +> +> ```jsx +> var decoratorVueJsx = () => ({ render() { return <div style={{ textAlign: 'center' }}><story/></div>} }) +> addDecorator(decoratorVueJsx) +> +> var decoratorVueTemplate = () => { return { template: `<div style="text-align:center"><story/></div>` } +> addDecorator(decoratorVueTemplate) +> ``` + And here's an example of component/local decorators. The component decorator wraps all the stories in a yellow frame, and the story decorator wraps a single story in an additional red frame. ```jsx @@ -221,11 +243,12 @@ Parameters are custom metadata for a story. Like decorators, they can also be hi Here's an example where we are annotating our stories with [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) notes using parameters, to be displayed in the [Notes addon](https://github.com/storybookjs/storybook/tree/next/addons/notes). -We first apply some notes globally in the Storybook config. +We first apply some notes globally in the `.storybook/preview.js`. ```js import { load, addParameters } from '@storybook/react'; import defaultNotes from './instructions.md'; + addParameters({ notes: defaultNotes }); ``` @@ -267,7 +290,7 @@ callout.story = { ## Story hierarchy -Stories can be organized in a nested structure using "/" as a separator, and can be given a top-level heading using a "|" root separator. +Stories can be organized in a nested structure using "/" as a separator. For example the following snippets nest the `Button` and `Checkbox` components within the `Atoms` group, under a top-level heading called `Design System`. @@ -277,7 +300,7 @@ import React from 'react'; import Button from './Button'; export default { - title: 'Design System|Atoms/Button', + title: 'Design System/Atoms/Button', }; export const normal = () => <Button onClick={action('clicked')}>Hello Button</Button>; ``` @@ -288,13 +311,13 @@ import React from 'react'; import Checkbox from './Checkbox'; export default { - title: 'Design System|Atoms/Checkbox', + title: 'Design System/Atoms/Checkbox', }; export const empty = () => <Checkbox label="empty" />; export const checked = () => <Checkbox label="checked" checked />; ``` -If you prefer other characters as separators, you can configure this using the `hierarchySeparator` and `hierarchyRootSeparator` config options. See the +By default the top-level heading will be treated as any other group, but if you'd like it to be given special emphasis as a "root", use the `showRoots` config option. See the [configuration options parameter](/configurations/options-parameter) page to learn more. ## Generating nesting path based on \_\_dirname @@ -309,7 +332,7 @@ import base from 'paths.macro'; import BaseButton from '../components/BaseButton'; export default { - title: `Other|${base}/Dirname Example`, + title: `Other/${base}/Dirname Example`, }; export const story1 = () => <BaseButton label="Story 1" />; export const story2 = () => <BaseButton label="Story 2" />; @@ -329,3 +352,35 @@ Multiple storybooks can be built for different kinds of stories or components in } } ``` + +## Permalinking to stories + +Sometimes you might wish to change the name of a story or its position in the hierarchy, but preserve the link to the story or its documentation. Here's how to do it. + +Consider the following story: + +```js +export default { + title: 'Foo/Bar', +}; + +export const Baz = () => <MyComponent />; +``` + +Storybook's ID-generation logic will give this the ID `foo-bar--baz`, so the link would be `?path=/story/foo-bar--baz`. + +Now suppose you want to change the position in the hierarchy to `OtherFoo/Bar` and the story name to `Moo`. Here's how to do that: + +```js +export default { + title: 'OtherFoo/Bar', + id: 'Foo/Bar', // or 'foo-bar' if you prefer +}; + +export const Baz = () => <MyComponent />; +Baz.story = { + name: 'Moo', +}; +``` + +Storybook will prioritize the `id` over the title for ID generation, if provided, and will prioritize the `story.name` over the export key for display. diff --git a/docs/src/pages/configurations/custom-webpack-config/index.md b/docs/src/pages/configurations/custom-webpack-config/index.md index 3add2bbefc54..e1f61d272aba 100644 --- a/docs/src/pages/configurations/custom-webpack-config/index.md +++ b/docs/src/pages/configurations/custom-webpack-config/index.md @@ -3,7 +3,10 @@ id: 'custom-webpack-config' title: 'Custom Webpack Config' --- -You can customize Storybook's webpack setup by providing a `webpack.config.js` file exporting a **webpack 4** compatible config exported as a **commonjs module**. +> 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. + +You can customize Storybook's webpack setup by providing a `webpack` field in `main.js` file. +The value should be an async function that receives a webpack config and eventually returns a webpack config. Storybook has its own Webpack setup and a dev server. The webpack config [is configurable](/configurations/custom-webpack-config#webpack-customisation-modes/), and the default can depend on which framework you're using and whether you've used a generator like [Create React App](https://github.com/facebookincubator/create-react-app) or Angular CLI etc. @@ -21,7 +24,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config#webpa entry: [ '@storybook/core/dist/server/common/polyfills.js', '@storybook/core/dist/server/preview/globals.js', - '<your-storybook-dir>/config.js', + '<your-storybook-dir>/preview.js', 'webpack-hot-middleware/client.js?reload=true', ], output: { @@ -148,7 +151,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config#webpa ### Debug the default webpack config - <summary>To effectively customise the webpack config, you might need to get the full default config it's using.</summary> + <summary>To effectively customize the webpack config, you might need to get the full default config it's using.</summary> <div></div> @@ -156,7 +159,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config#webpa - Edit its contents: ```js module.exports = { - webpack: (config) => console.dir(config, { depth: null }) || config, + webpackFinal: (config) => console.dir(config, { depth: null }) || config, }; ``` - Then run storybook: @@ -177,7 +180,7 @@ const path = require('path'); // Export a function. Accept the base config as the only param. module.exports = { - webpack: async (config, { configType }) => { + webpackFinal: async (config, { configType }) => { // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' // You can change the configuration based on that. // 'PRODUCTION' is used when building the static version of storybook. @@ -206,7 +209,7 @@ Furthermore, `config` requires the `HtmlWebpackplugin` to generate the preview p ```js module.exports = { - webpack: (config) => { + webpackFinal: (config) => { config.plugins.push(...); return config; }, @@ -233,7 +236,7 @@ const path = require('path'); const custom = require('../webpack.config.js'); module.exports = { - webpack: (config) => { + webpackFinal: (config) => { return { ...config, module: { ...config.module, rules: custom.module.rules } }; }, }; diff --git a/docs/src/pages/configurations/default-config/index.md b/docs/src/pages/configurations/default-config/index.md index 7e87b7dc63ca..dd3673d3dfe0 100644 --- a/docs/src/pages/configurations/default-config/index.md +++ b/docs/src/pages/configurations/default-config/index.md @@ -14,7 +14,7 @@ Here are some key features of Storybook's Babel configurations. We have added ES2016 support with Babel for transpiling your JS code. In addition to that, we've added a few experimental features, like object spreading and async await. -Check out our [source](https://github.com/storybookjs/storybook/blob/master/lib/core/src/server/config/babel.dev.js) to learn more about these plugins. +Check out our [source](https://github.com/storybookjs/storybook/blob/master/lib/core/src/server/common/babel.js) to learn more about these plugins. ### .babelrc support @@ -40,7 +40,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config/), an entry: [ '@storybook/core/dist/server/common/polyfills.js', '@storybook/core/dist/server/preview/globals.js', - '<your-storybook-dir>/config.js', + '<your-storybook-dir>/preview.js', 'webpack-hot-middleware/client.js?reload=true', ], output: { @@ -97,7 +97,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config/), an ], module: { rules: [ - { test: /\.(mjs|jsx?)$/, + { test: /\.(mjs|jsx?)$/, use: [ { loader: 'babel-loader', options: { cacheDirectory: './node_modules/.cache/storybook', @@ -122,7 +122,7 @@ The webpack config [is configurable](/configurations/custom-webpack-config/), an include: [ './' ], exclude: [ './node_modules' ], }, - { test: /\.md$/, + { test: /\.md$/, use: [ { loader: './node_modules/raw-loader/index.js' }, ], diff --git a/docs/src/pages/configurations/options-parameter/index.md b/docs/src/pages/configurations/options-parameter/index.md index 3b0a2654533a..66714b3d0b79 100644 --- a/docs/src/pages/configurations/options-parameter/index.md +++ b/docs/src/pages/configurations/options-parameter/index.md @@ -5,83 +5,98 @@ title: 'Options Parameter' Storybook UI is configurable using an options API that allows you to tweak its appearance globally and for each story. -> NOTE: If you've used older versions of Storybook this is formerly [addon-options](https://github.com/storybookjs/storybook/tree/next/addons/options), which has been deprecated. - ### Global options -Import and use `addParameters` with the `options` key in your `config.js` file. +Import and use `setConfig` in your `manager.js` file. + +```js +import { addons } from '@storybook/addons'; + +addons.setConfig({ + /** + * show story component as full screen + * @type {Boolean} + */ + isFullscreen: false, + + /** + * display panel that shows a list of stories + * @type {Boolean} + */ + showNav: true, + + /** + * display panel that shows addon configurations + * @type {Boolean} + */ + showPanel: true, + + /** + * where to show the addon panel + * @type {('bottom'|'right')} + */ + panelPosition: 'bottom', + + /** + * sidebar tree animations + * @type {Boolean} + */ + sidebarAnimations: true, + + /** + * enable/disable shortcuts + * @type {Boolean} + */ + enableShortcuts: true, + + /** + * show/hide tool bar + * @type {Boolean} + */ + isToolshown: true, + + /** + * theme storybook, see link below + */ + theme: undefined, + + /** + * id to select an addon panel + * @type {String} + */ + selectedPanel: undefined, +}); +``` + +### showRoots + +Import and use `addParameters` with the `options` key in your `preview.js` file. ```js -import { addParameters, configure } from '@storybook/react'; +import { addParameters } from '@storybook/react'; -// Option defaults: addParameters({ options: { /** - * show story component as full screen - * @type {Boolean} - */ - isFullscreen: false, - /** - * display panel that shows a list of stories - * @type {Boolean} - */ - showNav: true, - /** - * display panel that shows addon configurations - * @type {Boolean} - */ - showPanel: true, - /** - * where to show the addon panel - * @type {('bottom'|'right')} - */ - panelPosition: 'bottom', - /** - * regex for finding the hierarchy separator - * @example: - * null - turn off hierarchy - * /\// - split by `/` - * /\./ - split by `.` - * /\/|\./ - split by `/` or `.` - * @type {Regex} - */ - hierarchySeparator: /\/|\./, - /** - * regex for finding the hierarchy root separator - * @example: - * null - turn off multiple hierarchy roots - * /\|/ - split by `|` - * @type {Regex} - */ - hierarchyRootSeparator: /\|/, - /** - * sidebar tree animations + * display the top-level grouping as a "root" in the sidebar * @type {Boolean} */ - sidebarAnimations: true, - /** - * enable/disable shortcuts - * @type {Boolean} - */ - enableShortcuts: true, - /** - * show/hide tool bar - * @type {Boolean} - */ - isToolshown: true, - /** - * theme storybook, see link below - */ - theme: undefined, - /** - * function to sort stories in the tree view - * common use is alphabetical `(a, b) => a[1].id.localeCompare(b[1].id)` - * if left undefined, then the order in which the stories are imported will - * be the order they display - * @type {Function} - */ - storySort: undefined, + showRoots: false, + }, +}); +``` + +### Sorting stories + +Import and use `addParameters` with the `options` key in your `preview.js` file. + +```js +import { addParameters } from '@storybook/react'; + +addParameters({ + options: { + storySort: (a, b) => + a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), }, }); ``` @@ -93,16 +108,12 @@ For more information on configuring the `theme`, see [theming](../theming/). The options-addon accepts story parameters on the `options` key: ```js -import { storiesOf } from '@storybook/react'; import MyComponent from './my-component'; -storiesOf('Addons|Custom options', module) - // If you want to set the option for all stories in of this kind - .addParameters({ options: { panelPosition: 'bottom' } }) - .add( - 'Story for MyComponent', - () => <MyComponent />, - // If you want to set the options for a specific story - { options: { panelPosition: 'right' } } - ); +export default { + title: 'Options', + parameters: { + options: { selectedPanel: 'storybook/a11y/panel' }, + }, +}; ``` diff --git a/docs/src/pages/configurations/overview/index.md b/docs/src/pages/configurations/overview/index.md new file mode 100644 index 000000000000..d30de7d38362 --- /dev/null +++ b/docs/src/pages/configurations/overview/index.md @@ -0,0 +1,81 @@ + +--- +id: 'overview' +title: 'Configuration overview' +--- + +For CLI options see: [here](/docs/cli-options). + +> 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. + +## main configuration + +Storybook has a few files it uses for configuration, and they are grouped together into a directory (default: `.storybook`). + +The most import file is the `main.js` file. This is where general config is declared. + +Here's an minimal example of a that file: + +```js +module.exports = { + stories: ['../src/components/**/*.stories.js'], + addons: [ + '@storybook/addon-essentials', + ], +}; +``` + +The addons field can refer to traditional [addons](docs/addons/introduction), but it can also include [presets](/docs/presets/introduction/) extending the config further. + +## manager & preview + +Storybook works by being split into 2 applications, which communicate with each other over a postmessage channel; called the "manager" and "preview". + +The preview application is essentially just your stories with a framework agnostic 'router'. Making it so when the manager application tells it so, it renders the correct story. + +The manager application renders the UI of [addons](docs/addons/introduction), the navigator and [toolbar](/docs/basics/toolbar-guide/). + +There are 2 extra config files, for doing some special runtime configs for each of those 2 applications. + +In `preview.js` you can add global [decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters): + +```js +// preview.js +import { addDecorator } from '@storybook/svelte'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); +``` + +In `manager.js` you can add [UI options](/docs/configurations/options-parameter/#global-options). + +```js +// manager.js +import { themes } from '@storybook/theming/create'; +import { addons } from '@storybook/addons'; + +addons.setConfig({ + theme: themes.dark, +}); +``` + +## entire main.js config + +The `main.js` file is actually a preset! so if you know how to configure storybook, you know how to write a preset, and vice-versa! +So the API of `main.js` is equal to [that of presets](/docs/presets/writing-presets/#presets-api). + +Here's an overview of the important configuration properties in `main.js`: + +```js +module.exports = { + // and array of glob patterns + stories: ['../src/components/**/*.stories.js'], + + // an array of addons & presets + addons: ['@storybook/addon-essentials'], +}; +``` + +## webpack + +For how to customize webpack, [view the customize webpack section](/docs/configurations/custom-webpack-config/) \ No newline at end of file diff --git a/docs/src/pages/configurations/serving-static-files/index.md b/docs/src/pages/configurations/serving-static-files/index.md index 9cf869e32ad1..881a5bb538f6 100644 --- a/docs/src/pages/configurations/serving-static-files/index.md +++ b/docs/src/pages/configurations/serving-static-files/index.md @@ -13,19 +13,20 @@ You can import any media assets by importing (or requiring) them as shown below. ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; - import imageFile from './static/image.png'; +export default { + title: 'img', +}; + const image = { src: imageFile, alt: 'my image', }; -storiesOf('<img />', module) - .add('with an image', () => ( - <img src={image.src} alt={image.alt} /> - )); +export const withAnImage = () => ( + <img src={image.src} alt={image.alt} /> +); ``` This is enabled with our [default config](/configurations/default-config). But, if you are using a [custom Webpack config](/configurations/custom-webpack-config), you need to add the [file-loader](https://github.com/webpack/file-loader) into your custom Webpack config. @@ -48,15 +49,15 @@ Here `./public` is our static directory. Now you can use static files in the pub ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; -const imageAlt = 'my image'; +export default { + title: 'img', +}; // assume image.png is located in the "public" directory. -storiesOf('<img />', module) - .add('with a image', () => ( - <img src="/image.png" alt={imageAlt} /> - )); +export const withAnImage = () => ( + <img src="/image.png" alt="my image" /> +); ``` > You can also pass a list of directories separated by commas without spaces instead of a single directory. @@ -76,20 +77,23 @@ In this example we're using a placeholder image service. ```js import React from 'react'; -import { storiesOf } from '@storybook/react'; -storiesOf('<img />', module) - .add('with a image', () => ( - <img src="https://placehold.it/350x150" alt="My CDN placeholder" /> - )); +export default { + title: 'img', +}; + +// assume image.png is located in the "public" directory. +export const withAnImage = () => ( + <img src="https://placehold.it/350x150" alt="My CDN placeholder" /> +); ``` ## Absolute versus relative paths Sometimes, you may want to deploy your storybook into a subpath, like `https://example.com/storybook`. -In this case, you need to have all your images and media files with relative paths. Otherwise, Storybook cannot locate those files. +In this case, you need to have all your images and media files with relative paths. Otherwise, the browser cannot locate those files. If you load static content via importing, this is automatic and you do not have to do anything. -If you are using a static directory, then you need to use _relative paths_ to load images. +If you are using a static directory, then you need to use _relative paths_ to load images or use [the base element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base). diff --git a/docs/src/pages/configurations/theming/index.md b/docs/src/pages/configurations/theming/index.md index 728484668f7b..dd185818aa00 100644 --- a/docs/src/pages/configurations/theming/index.md +++ b/docs/src/pages/configurations/theming/index.md @@ -5,11 +5,15 @@ title: 'Theming Storybook' Storybook is theme-able! +> Note that themeing storybook changed recently, to be in `manager.js` (introduced in 5.3). But addon-docs is not yet compatible with this new way of configuring the theme. +> +> See here how to use theme storybook in a way that addon-docs will be themed as well: https://github.com/storybookjs/storybook/tree/master/addons/docs + ## Global theming It's possible to theme Storybook globally. -We've created two basic themes that look good of the box: "normal" (a light theme) and "dark" (a dark theme). Unless you've set your preferred color scheme as dark Storybook will use the light theme as default. +We've created two basic themes that look good out of the box: "normal" (a light theme) and "dark" (a dark theme). Unless you've set your preferred color scheme as dark Storybook will use the light theme as default. As an example, you can tell Storybook to use the "dark" theme by modifying `.storybook/manager.js`: @@ -23,6 +27,8 @@ addons.setConfig({ ``` +> `addParameters` needs to be called before `configure()` method or it won't have any effect. + When setting a theme, set a full theme object. The theme is replaced, not combined. Read on for more on how to create your own theme. diff --git a/docs/src/pages/configurations/typescript-config/index.md b/docs/src/pages/configurations/typescript-config/index.md index cb16b460ac8b..f48927d453ab 100644 --- a/docs/src/pages/configurations/typescript-config/index.md +++ b/docs/src/pages/configurations/typescript-config/index.md @@ -3,17 +3,21 @@ id: 'typescript-config' title: 'TypeScript Config' --- +> 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. + This is a central reference for using Storybook with TypeScript. ## Typescript configuration presets -The easiest way to write and configure your stories in TypeScript is by using [Storybook presets](../../presets/introduction/index.md). +The fastest and easiest way to write and configure your stories in TypeScript is by using a Storybook preset. + +* If you're using Create React App (CRA) and have configured it to work with TS, you should use [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app), which configures Storybook to reuse CRA's TS handling. -If you're using Create React App (CRA) and have configured it to work with TS, you should use the [CRA preset](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app), which configures Storybook to reuse CRA's TS handling. +* If you are not using CRA or have other requirements, then the next best option is [`@storybook/preset-typescript`](https://github.com/storybookjs/presets/tree/master/packages/preset-typescript), which configures `ts-loader` under the hood. -If you're not using CRA, the next best thing is to use the [Typescript preset](https://github.com/storybookjs/presets/tree/master/packages/preset-typescript), which configures `ts-loader` under the hood. +If you need more control than these presets offer, read on for manual configuration instructions. -If you need more control than the TypeScript preset offers, read on for manual configuration instructions. +You can learn more about Storybook presets [over here](../../presets/introduction). > If using TypeScript, some addons require features available in TS version 3.4+. @@ -32,24 +36,26 @@ We have had the best experience using `awesome-typescript-loader`, but other tut ### Setting up TypeScript to work with Storybook -We first have to use the [custom Webpack config in full control mode, extending default configs](/configurations/custom-webpack-config/#full-control-mode--default) by creating a `webpack.config.js` file in our Storybook configuration directory (by default, it’s `.storybook`): +We [configure storybook's webpack](/configurations/custom-webpack-config/#full-control-mode--default) by changing `.storybook/main.js`: ```js -module.exports = ({ config }) => { - config.module.rules.push({ - test: /\.(ts|tsx)$/, - use: [ - { - loader: require.resolve('awesome-typescript-loader'), - }, - // Optional - { - loader: require.resolve('react-docgen-typescript-loader'), - }, - ], - }); - config.resolve.extensions.push('.ts', '.tsx'); - return config; +module.exports = { + webpackFinal: async config => { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + use: [ + { + loader: require.resolve('awesome-typescript-loader'), + }, + // Optional + { + loader: require.resolve('react-docgen-typescript-loader'), + }, + ], + }); + config.resolve.extensions.push('.ts', '.tsx'); + return config; + }, }; ``` @@ -99,19 +105,22 @@ Please use [`@storybook/preset-create-react-app`](https://github.com/storybookjs The following code uses [`babel-preset-react-app`](https://github.com/facebook/create-react-app/tree/master/packages/babel-preset-react-app). -We first have to use the [custom Webpack config in full control mode, extending default configs](/configurations/custom-webpack-config/#full-control-mode--default) by creating a `webpack.config.js` file in our Storybook configuration directory (by default, it’s `.storybook`): +We will create a [custom Webpack config](/configurations/custom-webpack-config/) by creating editing/creating the `.storybook/main.js`: ```js -module.exports = ({ config, mode }) => { - config.module.rules.push({ - test: /\.(ts|tsx)$/, - loader: require.resolve('babel-loader'), - options: { - presets: [['react-app', { flow: false, typescript: true }]], - }, - }); - config.resolve.extensions.push('.ts', '.tsx'); - return config; +module.exports = { + stories: ['../src/**/*.stories.tsx'], + webpackFinal: async config => { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + loader: require.resolve('babel-loader'), + options: { + presets: [['react-app', { flow: false, typescript: true }]], + }, + }); + config.resolve.extensions.push('.ts', '.tsx'); + return config; + }, }; ``` @@ -123,32 +132,23 @@ If your stories are outside the `src` folder, for example the `stories` folder i The default storybook index file is `stories/index.stories.js` -- you'll want to rename this to `stories/index.stories.tsx`. -## Import tsx stories - -Change `config.ts` inside the Storybook config directory (by default, it’s `.storybook`) to import stories made with TypeScript: - -```js -import { configure } from '@storybook/react'; -// automatically import all files ending in *.stories.tsx -configure(require.context('../src', true, /\.stories\.tsx?$/), module); -``` - ## Using TypeScript with the TSDocgen addon The very handy [Storybook Info addon](https://github.com/storybookjs/storybook/tree/master/addons/info) autogenerates prop tables documentation for each component, however it doesn't work with Typescript types. The current solution is to use [react-docgen-typescript-loader](https://github.com/strothj/react-docgen-typescript-loader) to preprocess the TypeScript files to give the Info addon what it needs. The webpack config above does this, and so for the rest of your stories you use it as per normal: ```js import * as React from 'react'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import TicTacToeCell from './TicTacToeCell'; -const stories = storiesOf('Components', module); +export default { + title: 'Components', + parameters: { + info: { inline: true }, + }, +}; -stories.add( - 'TicTacToeCell', - () => <TicTacToeCell value="X" position={{ x: 0, y: 0 }} onClick={action('onClick')} />, - { info: { inline: true } } +export const TicTacToeCell = () => ( + <TicTacToeCell value="X" position={{ x: 0, y: 0 }} />, ); ``` @@ -156,13 +156,12 @@ stories.add( Please refer to the [react-docgen-typescript-loader](https://github.com/strothj/react-docgen-typescript-loader) docs for writing prop descriptions and other annotations to your Typescript interfaces. -Additional annotation can be achieved by setting a default set of info parameters: +Additional annotation can be achieved by setting a default set of info parameters in `.storybook/preview.js`: ```ts import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; -// Globally in your .storybook/config.js, or alternatively, per-chapter addDecorator( withInfo({ styles: { @@ -193,51 +192,6 @@ addDecorator( ); ``` -This can be used like so: - -```js -import * as React from 'react'; - -import { storiesOf } from '@storybook/react'; -import { PrimaryButton } from './Button'; -import { text, select, boolean } from '@storybook/addon-knobs/react'; - -storiesOf('Components/Button', module).addWithJSX( - 'basic PrimaryButton', - () => ( - <PrimaryButton - label={text('label', 'Enroll')} - disabled={boolean('disabled', false)} - onClick={() => alert('hello there')} - /> - ), - { - info: { - text: ` - - ### Notes - - light button seen on <https://zpl.io/aM49ZBd> - - ### Usage - ~~~js - <PrimaryButton - label={text('label', 'Enroll')} - disabled={boolean('disabled',false)} - onClick={() => alert('hello there')} - /> - ~~~ - -`, - }, - } -); -``` - -And this is how it looks: - -![image](https://user-images.githubusercontent.com/35976578/38376038-ac02b432-38c5-11e8-9aed-f4fa2e258f60.png) - Note: Component docgen information can not be generated for components that are only exported as default. You can work around the issue by exporting the component using a named export. ## Setting up Jest tests diff --git a/docs/src/pages/examples/_examples.yml b/docs/src/pages/examples/_examples.yml index 0bbbf651105e..8babbbd354a6 100644 --- a/docs/src/pages/examples/_examples.yml +++ b/docs/src/pages/examples/_examples.yml @@ -69,7 +69,7 @@ necolas: title: React Native Web description: Storybook demo for React Native Web. source: https://github.com/necolas/react-native-web - demo: https://necolas.github.io/react-native-web/storybook/ + demo: https://necolas.github.io/react-native-web/docs/ griddle: thumbnail: griddle.jpg title: Griddle @@ -191,3 +191,9 @@ reactPakistan: description: A series of reuseable React Commons, UI/UX components, icons, logos and more by React Pakistan. demo: https://taimoormk.github.io/react-commons-collection/?path=/docs/react-pakistan-intro--page site: https://www.npmjs.com/package/@react-pakistan/react-commons-collection +bbc: + thumbnail: bbc.jpg + title: BBC Psammead + description: React component library for BBC World Service and more + demo: https://bbc.github.io/psammead/ + source: https://github.com/bbc/psammead diff --git a/docs/src/pages/examples/thumbnails/bbc.jpg b/docs/src/pages/examples/thumbnails/bbc.jpg new file mode 100644 index 000000000000..49e1a72d0c31 Binary files /dev/null and b/docs/src/pages/examples/thumbnails/bbc.jpg differ diff --git a/docs/src/pages/formats/component-story-format/index.md b/docs/src/pages/formats/component-story-format/index.md index 855fa2d5307b..4d4a1011d63f 100644 --- a/docs/src/pages/formats/component-story-format/index.md +++ b/docs/src/pages/formats/component-story-format/index.md @@ -5,7 +5,7 @@ title: 'Component Story Format (CSF)' Storybook's Component Story Format (CSF) is the recommended way to [write stories](../../basics/writing-stories/) since Storybook 5.2. [Read the announcement](https://medium.com/storybookjs/component-story-format-66f4c32366df) to learn more about how it came to be. -In CSF, stories and component metadata are defined as ES6 modules. Every Component story file consists of a required default export and one or more named exports. +In CSF, stories and component metadata are defined as ES Modules. Every component story file consists of a required **default export** and one or more **named exports**. CSF is supported in all frameworks except React Native, where you should use the [storiesOf API](../storiesof-api/) instead. @@ -17,27 +17,50 @@ The default export defines metadata about your component, including the `compone import MyComponent from './MyComponent'; export default { - title: 'Path|To/MyComponent', + title: 'Path/To/MyComponent', component: MyComponent, decorators: [ ... ], parameters: { ... } } ``` -For more examples, see [writing stories](../../basics/writing-stories/) +For more examples, see [writing stories](../../basics/writing-stories/). -## Story exports +## Named story exports -By default every named export in the file represents a story function. +With CSF, every named export in the file represents a story function by default. + +```jsx +import MyComponent from './MyComponent'; + +export default { ... } + +export const Basic = () => <MyComponent />; +export const WithProp = () => <MyComponent prop="value" />; +``` + +The exported identifiers will be converted to "start case" using Lodash's [startCase](https://lodash.com/docs/#startCase) function. For example: + +``` +name -> 'Name' +someName -> 'Some Name' +someNAME -> 'Some NAME' +some_custom_NAME -> 'Some Custom NAME' +someName1234 -> 'Some Name 1234' +someName1_2_3_4 -> 'Some Name 1 2 3 4' +``` + +It's recommended to start export names with a capital letter. Story functions can be annotated with a `story` object to define story-level [decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters), and also to define the `name` of the story. -The `name` is useful if you want to use names with spaces, names that correspond to restricted keywords in Javascript, or names that collide with other variables in the file. If it's not specified, the export name will be used instead. +The `name` is useful if you want to use names with special characters, names that correspond to restricted keywords in Javascript, or names that collide with other variables in the file. If it's not specified, the export name will be used instead. -```js -export const simple = () => <MyComponent prop1={val1} />; -simple.story = { - name: 'default', // can't be used as a named export +```jsx +export const Simple = () => <MyComponent />; + +Simple.story = { + name: 'So simple!', decorators: [ ... ], parameters: { ... } }; @@ -47,7 +70,11 @@ simple.story = { Storybook handles named exports and `story.name` slightly differently. When should you use one vs. the other? -In general, you should use named exports. Storybook passes them through a `storyNameFromExport` function ([#7901](https://github.com/storybookjs/storybook/pull/7901)), which is implemented with `lodash.startCase`: +The named export is always used to determine the story ID / URL. + +If you specify `story.name`, it will be used as the story display name in the UI. + +If you don't specify `story.name`, the named export will be used to generate the display name. Storybook passes the named export through a `storyNameFromExport` function ([#7901](https://github.com/storybookjs/storybook/pull/7901)), which is implemented with `lodash.startCase`: ```js it('should format CSF exports with sensible defaults', () => { @@ -65,22 +92,22 @@ it('should format CSF exports with sensible defaults', () => { }); ``` -When you want to change the name of your story, rename the CSF export. This will change the name of the story and also change the Story's ID / URL. +When you want to change the name of your story, just rename the CSF export. This will change the name of the story and also change the story's ID and URL. You should use the `story.name` option in the following cases: -1. Want the name to show up in the Storybook UI in a way that's not possible with a named export, e.g. reserved keywords like "default", special characters like emoji, spacing/capitalization other than what's provided by `storyNameFromExport` -2. Want to preserve the Story ID independently from changing how it's displayed. Having stable Story ID's is useful for integration with third party tools. +1. You want the name to show up in the Storybook UI in a way that's not possible with a named export, e.g. reserved keywords like "default", special characters like emoji, spacing/capitalization other than what's provided by `storyNameFromExport`. +2. You want to preserve the Story ID independently from changing how it's displayed. Having stable Story ID's is useful for integration with third party tools. ## Non-story exports -In some cases, you may want to export a mixture of story and non-stories. For example., it can be useful to export data that's used in your stories. +In some cases, you may want to export a mixture of story and non-stories. For example, it can be useful to export data that's used in your stories. To make this possible, you can use optional `includeStories` and `excludeStories` configuration fields in the default export, which can be set to either an array of strings, or a regular expression. Consider the following story file: -```js +```jsx import React from 'react'; import MyComponent from './MyComponent'; import someData from './data.json'; @@ -88,19 +115,21 @@ import someData from './data.json'; export default { title: 'MyComponent', component: MyComponent, - includeStories: ['simpleStory', 'complexStory'] + includeStories: ['SimpleStory', 'ComplexStory'] } + export const simpleData = { foo: 1, bar: 'baz' }; export const complexData = { foo: 1, { bar: 'baz', baz: someData }}; -export const simpleStory = () => <MyComponent data={simpleData} />; -export const complexStory = () => <MyComponent data={complexData} />; + +export const SimpleStory = () => <MyComponent data={simpleData} />; +export const ComplexStory = () => <MyComponent data={complexData} />; ``` -When Storybook loads this file, it will see all the exports, but it will ignore the data exports and only treat `simpleStory` and `complexStory` as stories. +When Storybook loads this file, it will see all the exports, but it will ignore the data exports and only treat `SimpleStory` and `ComplexStory` as stories. For this specific example the equivalent result can be achieved in a few ways depending on what's convenient: -- `includeStories: ['simpleStory', 'complexStory']` +- `includeStories: ['SimpleStory', 'ComplexStory']` - `includeStories: /.*Story$/` - `excludeStories: ['simpleData', 'complexData']` - `excludeStories: /.*Data$/` diff --git a/docs/src/pages/formats/mdx-syntax/index.md b/docs/src/pages/formats/mdx-syntax/index.md index 056b49997da3..31e831067b8f 100644 --- a/docs/src/pages/formats/mdx-syntax/index.md +++ b/docs/src/pages/formats/mdx-syntax/index.md @@ -3,6 +3,4 @@ id: 'mdx-syntax' title: 'MDX Syntax' --- -[MDX syntax](https://mdxjs.com/) documentation coming soon! - -For more information, see the [Storybook Docs Technical Preview](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) +Syntax documentation coming soon. In the meantime, please refer to the [`addon-docs` MDX README](https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/mdx.md) for more information. diff --git a/docs/src/pages/formats/storiesof-api/index.md b/docs/src/pages/formats/storiesof-api/index.md index f67826dad836..c0d8e11af1a5 100644 --- a/docs/src/pages/formats/storiesof-api/index.md +++ b/docs/src/pages/formats/storiesof-api/index.md @@ -40,7 +40,7 @@ Each `.add` call takes a story name, a story function that returns a renderable [Decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters) can be specified globally, at the component level, or locally at the story level. -Global decorators are parameters are specified in the Storybook config: +Global decorators and parameters are specified in the Storybook config: ```js addDecorator(storyFn => <blink>{storyFn()}</blink>); @@ -80,7 +80,7 @@ storiesOf('Button', module).add( To make it easier to adopt the new [Component Story Format (CSF)](../component-story-format/), we've created an automatic migration tool to transform `storiesOf` API to Module format. ```sh -sb migrate storiesof-to-csf --glob src/**/*.stories.js +sb migrate storiesof-to-csf --glob=src/**/*.stories.js ``` For more information, see the CLI's [Codemod README](https://github.com/storybookjs/storybook/tree/next/lib/codemod). diff --git a/docs/src/pages/guides/guide-angular/index.md b/docs/src/pages/guides/guide-angular/index.md index 9a2e27d60c52..9cd931db07a3 100644 --- a/docs/src/pages/guides/guide-angular/index.md +++ b/docs/src/pages/guides/guide-angular/index.md @@ -46,16 +46,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: -```ts -import { configure } from '@storybook/angular'; - -configure(require.context('../src', true, /\.stories\.[tj]s$/), module); +```js +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.[tj]sx?`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-ember/index.md b/docs/src/pages/guides/guide-ember/index.md index 30beda3c26f9..13f2209763ab 100644 --- a/docs/src/pages/guides/guide-ember/index.md +++ b/docs/src/pages/guides/guide-ember/index.md @@ -35,14 +35,14 @@ If you don't have `package.json` in your project, you'll need to init it first: npm init ``` -Then add the following NPM script to your package json in order to start the storybook later in this guide: +Then add the following NPM script to your `package.json` in order to start the storybook later in this guide: > In order for your storybook to run properly be sure to be either run `ember serve` or `ember build` before running any storybook commands. Running `ember serve` before storybook will enable live reloading. ```json { "scripts": { - "build-storybook": "ember build && build-storybook -p 9001 -s dist", + "build-storybook": "ember build && build-storybook -s dist", "storybook": "ember serve & start-storybook -p 9001 -s dist" } } @@ -52,19 +52,16 @@ Then add the following NPM script to your package json in order to start the sto Your environment will be preconfigured using `ember-cli-storybook`. This will add a `preview-head.html`, a `.env` and make sure that your environment is configured to work with live reload. -## Create the config file +## Create the main file -Storybook can be configured in several different ways. -That’s why we need a config directory. We've added a `-c` option to the above NPM script mentioning `.storybook` as the config directory. +For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -For the basic Storybook configuration file, you don't need to do much, but tell Storybook where to find stories. - -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/ember'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. @@ -76,7 +73,7 @@ Now you can write some stories inside the `../stories/index.stories.js` file, li > It is important that you import the `hbs` function that is provided by a babel plugin in `@storybook/ember` ```js -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; export default { title: 'Demo' }; @@ -105,7 +102,7 @@ export const component = () => { }; ``` -> If you are using an older version of ember <= 3.1 please use this story style +> If you are using an older version of Ember <= 3.1 please use this story style ```js import { compile } from 'ember-source/dist/ember-template-compiler'; diff --git a/docs/src/pages/guides/guide-html/index.md b/docs/src/pages/guides/guide-html/index.md index 5fe563f9c2b1..93ce7163a749 100644 --- a/docs/src/pages/guides/guide-html/index.md +++ b/docs/src/pages/guides/guide-html/index.md @@ -54,16 +54,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/html'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-marko/index.md b/docs/src/pages/guides/guide-marko/index.md index 01334129f25c..1cef3e554b08 100644 --- a/docs/src/pages/guides/guide-marko/index.md +++ b/docs/src/pages/guides/guide-marko/index.md @@ -46,16 +46,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/marko'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-mithril/index.md b/docs/src/pages/guides/guide-mithril/index.md index cb9c1684fc18..a3e678b4aa9b 100644 --- a/docs/src/pages/guides/guide-mithril/index.md +++ b/docs/src/pages/guides/guide-mithril/index.md @@ -47,16 +47,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/mithril'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-preact/index.md b/docs/src/pages/guides/guide-preact/index.md index b4f9e0fdf178..ad59164a42fe 100644 --- a/docs/src/pages/guides/guide-preact/index.md +++ b/docs/src/pages/guides/guide-preact/index.md @@ -47,16 +47,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/preact'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-rax/index.md b/docs/src/pages/guides/guide-rax/index.md index 0284101df107..0d9d748db097 100644 --- a/docs/src/pages/guides/guide-rax/index.md +++ b/docs/src/pages/guides/guide-rax/index.md @@ -47,16 +47,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/rax'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-react-native/index.md b/docs/src/pages/guides/guide-react-native/index.md index 6fd2baf3c1a1..8491694b1230 100644 --- a/docs/src/pages/guides/guide-react-native/index.md +++ b/docs/src/pages/guides/guide-react-native/index.md @@ -63,7 +63,7 @@ Create a file called `rn-addons.js` that you can use to include on device addons `storybook/rn-addons.js` -``` +```js import '@storybook/addon-ondevice-knobs/register'; import '@storybook/addon-ondevice-notes/register'; ... diff --git a/docs/src/pages/guides/guide-react/index.md b/docs/src/pages/guides/guide-react/index.md index e2f92d26cc3f..0b1ed604f45b 100644 --- a/docs/src/pages/guides/guide-react/index.md +++ b/docs/src/pages/guides/guide-react/index.md @@ -5,6 +5,12 @@ title: 'Storybook for React' ## Automatic setup +Before trying the below commands, you should try the following command. In most cases, Storybook will detect that you're using `react` or `react-scripts`, and install the appropriate packages. + +```sh +npx -p @storybook/cli sb init +``` + You may have tried to use our quick start guide to setup your project for Storybook. If it failed because it couldn't detect you're using React, you could try forcing it to use React: @@ -12,14 +18,14 @@ If it failed because it couldn't detect you're using React, you could try forcin npx -p @storybook/cli sb init --type react ``` -Note: be sure you have a `package.json` in your project or the above command will fail. - -If you're using [Create React App](https://create-react-app.dev/), use: +If you're using [Create React App](https://create-react-app.dev/) (or a fork of `react-scripts`), you should use this command instead: ```sh npx -p @storybook/cli sb init --type react_scripts ``` +Note: You must have a `package.json` in your project or the above commands will fail. + ## Manual setup If you want to set up Storybook manually for your React project, this is the guide for you. @@ -59,16 +65,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/react'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports = { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-riot/index.md b/docs/src/pages/guides/guide-riot/index.md index 72c41123f1a2..8a12f01d364a 100644 --- a/docs/src/pages/guides/guide-riot/index.md +++ b/docs/src/pages/guides/guide-riot/index.md @@ -46,16 +46,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: -```ts -import { configure } from '@storybook/riot'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +```js +module.exports { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. diff --git a/docs/src/pages/guides/guide-svelte/index.md b/docs/src/pages/guides/guide-svelte/index.md index 6d19073af4b0..bc877a8bf6f7 100644 --- a/docs/src/pages/guides/guide-svelte/index.md +++ b/docs/src/pages/guides/guide-svelte/index.md @@ -36,6 +36,15 @@ Make sure that you have `@babel/core`, and `babel-loader` in your dependencies a npm install babel-loader @babel/core --save-dev ``` +### svelte-loader + +You'll also need to install `svelte-loader` if you haven't already. + +```sh + +npm install svelte-loader --save-dev +``` + ## Step 2: Add a npm script Then add the following NPM script to your `package.json` in order to start the storybook later in this guide: @@ -48,16 +57,16 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/svelte'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports = { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. @@ -95,7 +104,7 @@ So you can create a story "view" file, which is essentially a .svelte file to lo <!-- MyButtonView --> <MyButton {rounded} on:click> {buttonText} -</Button> +</MyButton> ``` In this example, the `on:click` that is heard on the `MyButton` component is passed up to the containing component `MyButtonView` using the svelte shorthand. diff --git a/docs/src/pages/guides/guide-vue/index.md b/docs/src/pages/guides/guide-vue/index.md index 28631e33dbdc..5b79cd0b22b7 100644 --- a/docs/src/pages/guides/guide-vue/index.md +++ b/docs/src/pages/guides/guide-vue/index.md @@ -47,21 +47,21 @@ Then add the following NPM script to your `package.json` in order to start the s } ``` -## Step 3: Create the config file +## Step 3: Create the main file For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories. -To do that, create a file at `.storybook/config.js` with the following content: +To do that, create a file at `.storybook/main.js` with the following content: ```js -import { configure } from '@storybook/vue'; - -configure(require.context('../src', true, /\.stories\.js$/), module); +module.exports = { + stories: ['../src/**/*.stories.[tj]s'], +}; ``` That will load all the stories underneath your `../src` directory that match the pattern `*.stories.js`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. -> You might be using global components or vue plugins such as vuex, in that case you'll need to register them in this `config.js` file. +> You might be using global components or vue plugins such as vuex, in that case you'll need to register them in this `preview.js` file. > > <details> > <summary>details</summary> @@ -124,7 +124,7 @@ Button > If your story is returning a plain template you can only use globally registered components. > -> To register them, use `Vue.component('my-button', Mybutton)` in your `config.js` file. +> To register them, use `Vue.component('my-button', Mybutton)` in your `preview.js` file. > > <details> > <summary>details</summary> diff --git a/docs/src/pages/guides/slow-start-guide/index.md b/docs/src/pages/guides/slow-start-guide/index.md index ce972a6183b2..f411004c2d5d 100644 --- a/docs/src/pages/guides/slow-start-guide/index.md +++ b/docs/src/pages/guides/slow-start-guide/index.md @@ -13,5 +13,6 @@ Storybook supports multiple UI libraries. The manual setup for each framework is - [Storybook for Marko](/guides/guide-marko/) - [Storybook for HTML](/guides/guide-html/) - [Storybook for Svelte](/guides/guide-svelte/) +- [Storybook for Ember](/guides/guide-ember/) - [Storybook for Riot](/guides/guide-riot/) - [Storybook for Preact](/guides/guide-preact/) diff --git a/docs/src/pages/presets/introduction/index.md b/docs/src/pages/presets/introduction/index.md index d7494be1ffea..32a791974905 100644 --- a/docs/src/pages/presets/introduction/index.md +++ b/docs/src/pages/presets/introduction/index.md @@ -3,24 +3,28 @@ id: 'introduction' title: 'Intro to Presets' --- +> 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 **presets** are grouped collections of `babel`, `webpack`, and `addons` configurations that support specific use cases. For example, to write your stories in Typescript, rather than [manually configuring Storybook for typescript](../../configurations/typescript-config/) with individual [babel](../../configurations/custom-babel-config/) and [webpack](../../configurations/custom-webpack-config/) configs, you can use the `@storybook/preset-typescript` package, which does the heavy lifting for you. ## Basic usage -Each preset has its own installation instructions, but the idea of presets is to install the preset and then load it. +Each preset has its own installation instructions, but the idea of presets is to install an addon and then load its preset. -For example, to get typescript support, first install the preset: +For example, to get typescript support, first install the addon: ```sh yarn add @storybook/preset-typescript --dev ``` -Then load it in the file `presets.js` in your storybook folder (`.storybook` by default): +Then load it in the file `main.js` in your storybook folder (`.storybook` by default): ```js -module.exports = ['@storybook/preset-typescript']; +module.exports = { + addons: ['@storybook/preset-typescript'], +}; ``` That's it. When Storybook starts up, it will configure itself for typescript without any further configuration. For more information, see the Typescript preset [README](https://github.com/storybookjs/presets/tree/master/packages/preset-typescript). @@ -33,17 +37,19 @@ Consider this example: ```js const path = require('path'); -module.exports = [ - { - name: '@storybook/preset-typescript', - options: { - tsDocgenLoaderOptions: { - tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), +module.exports = { + addons: [ + { + name: '@storybook/preset-typescript', + options: { + tsDocgenLoaderOptions: { + tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), + }, + include: [path.resolve(__dirname)], }, - include: [path.resolve(__dirname)], }, - }, -]; + ], +}; ``` This configures the typescript docgen loader using the app's `tsconfig.json` and also tells the typescript loaders to only be applied to the current directory. diff --git a/docs/src/pages/presets/preset-gallery/index.md b/docs/src/pages/presets/preset-gallery/index.md index daead0aa4277..6203a94ce42a 100644 --- a/docs/src/pages/presets/preset-gallery/index.md +++ b/docs/src/pages/presets/preset-gallery/index.md @@ -25,6 +25,10 @@ One-line SCSS configuration for Storybook. One-line AntDesign configuration for Storybook. +### [AntDesign](https://github.com/storybookjs/presets/tree/master/packages/preset-ant-design) + +One-line AntDesign configuration for storybook. + ## Community presets There are no community presets available yet. Check back here or edit this page to add yours. diff --git a/docs/src/pages/presets/writing-presets/index.md b/docs/src/pages/presets/writing-presets/index.md index 199d2291ba17..2005391c667a 100644 --- a/docs/src/pages/presets/writing-presets/index.md +++ b/docs/src/pages/presets/writing-presets/index.md @@ -3,6 +3,8 @@ id: 'writing-presets' title: 'Writing Presets' --- +> 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 presets](../introduction/) are grouped collections of `babel`, `webpack`, and `addons` configurations that support specific use cases in Storybook, such as typescript or MDX support. This doc covers the [presets API](#presets-api) and how to use the presets mechanism for [advanced configuration](#advanced-configuration). @@ -79,22 +81,51 @@ export function webpackFinal(config, { configDir }) { - `webpackFinal` is applied to the preview config after all user presets have been applied - `webpackManager` is applied to the manager config -### Addons +### Manager entries -The addon config `addons` allows you to add addons to Storybook from within a preset. For addons that require custom webpack/babel configuration, it is easier to install the preset, and it will take care of everything. +The addon config `managerEntries` allows you to add addons to Storybook from within a preset. For addons that require custom webpack/babel configuration, it is easier to install the preset, and it will take care of everything. For example, the Storysource preset contains the following code: ```js -export function addons(entry = []) { +export function managerEntries(entry = []) { return [...entry, require.resolve('@storybook/addon-storysource/register')]; } ``` -This is equivalent to [registering the addon manually](../../addons/using-addons/) in `addons.js`: +This is equivalent to [registering the addon manually](../../addons/using-addons/) in `main.js`: + +```js +module.exports = { + managerEntries: ['@storybook/addon-storysource/register'], +}; +``` + +### Addons + +For users, the name `managerEntries` might be a bit too technical, so instead both users and preset-authors can simply use the property: `addons`: + +```js +module.exports = { + addons: ['@storybook/addon-storysource/register'], +}; +``` + +The array of values can support both references to other presets and addons that should be included into the manager. + +Storybook will automatically detect whether a reference to an addon is a preset or a manager entry by checking if the package contains a `./preset.js` or `./register.js` (manager entry), falling back to preset if it is unsure. + +If this heuristic is incorrect for an addon you are using, you can explicitly opt in to an entry being an a manager entry using the `managerEntries` key. + +Here's what it looks when combining presets and managerEntries in the addons property: ```js -import '@storybook/addon-storysource/register'; +module.exports = { + addons: [ + '@storybook/addon-storysource/register', // a managerEntry + '@storybook/addon-docs/preset', // a preset + ], +}; ``` ### Entries @@ -107,31 +138,59 @@ The presets API is also more powerful than the [standard configuration options]( For example, some users want to configure the webpack for Storybook's UI and addons ([issue](https://github.com/storybookjs/storybook/issues/4995)), but this is not possible using [standard webpack configuration](../custom-webpack-config/) (it used to be possible before SB4.1). However, you can achieve this with a private preset. -First, create a file `my-preset.js` in your storybook folder: +If it doesn't exist yet, create a file `.storybook/main.js`: ```js -async function managerWebpack(config, options) { - // update config here - return config; -} -async function managerBabel(config, options) { - // update config here - return config; -} -async function webpack(config, options) { - return config; -} -async function babel(config, options) { - return config; -} -async function addons(entry = []) { - return entry; -} -module.exports = { managerWebpack, managerBabel, webpack, babel, addons } +module.exports = { + managerWebpack: async (config, options) => { + // update config here + return config; + }, + managerBabel: async (config, options) => { + // update config here + return config; + }, + webpackFinal: async (config, options) => { + // change webpack config + return config; + }, + babel: async (config, options) => { + return config; + }, +}; ``` -Then, load that preset in your `presets.js` file: +## Sharing advanced configuration + +Change your `main.js` file to: ```js -module.exports = [path.resolve('./.storybook/my-preset')]; +const path = require('path'); + +module.exports = { + addons: [path.resolve('./.storybook/my-preset')], +}; ``` + +and extract the configuration to a new file `./storybook/my-preset.js`: + +```js +module.exports = { + managerWebpack: async (config, options) => { + // update config here + return config; + }, + managerBabel: async (config, options) => { + // update config here + return config; + }, + webpackFinal: async (config, options) => { + return config; + }, + babel: async (config, options) => { + return config; + }, +}; +``` + +Place your `my-preset.js` file wherever you want, if you want to share it far and wide you'll want to make it its own package. diff --git a/docs/src/stories/data.js b/docs/src/stories/data.js index 8e2fdd884ce3..4bd0bf47774f 100644 --- a/docs/src/stories/data.js +++ b/docs/src/stories/data.js @@ -82,7 +82,7 @@ storiesOf('Toggle', module) owner: 'https://avatars3.githubusercontent.com/u/239676?v=3&s=460', storybook: { name: 'React Native Web', - link: 'https://necolas.github.io/react-native-web/storybook', + link: 'https://necolas.github.io/react-native-web/docs/', }, source: 'https://github.com/necolas/react-native-web', }, diff --git a/docs/src/versions/latest.json b/docs/src/versions/latest.json index e81ba33cc81f..75c255c58dd7 100644 --- a/docs/src/versions/latest.json +++ b/docs/src/versions/latest.json @@ -1 +1 @@ -{"version":"5.2.0-alpha.23","info":{"plain":"Storybook 5.1 is a juicy upgrade including:\n\n- 📱 Mobile: Standalone package architecture for React Native\n- 🎟 A11y addon: Realtime accessibility checks and visual feedback\n- 🛠 Context addon: New UI for themes, internationalization, & more\n- 🎛 Presets: One-line configuration for babel, webpack, & addons\n\n5.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."}} \ No newline at end of file +{"version":"5.3.7","info":{"plain":"### Bug Fixes\n\n* Node-logger: Move `@types/npmlog` to dependencies ([#9538](https://github.com/storybookjs/storybook/pull/9538))\n* Core: Fix legacy story URLs ([#9545](https://github.com/storybookjs/storybook/pull/9545))\n* Addon-docs: Convert default prop value to string ([#9525](https://github.com/storybookjs/storybook/pull/9525))\n* Addon-docs: Preserve Source indentation by default ([#9513](https://github.com/storybookjs/storybook/pull/9513))"}} \ No newline at end of file diff --git a/docs/src/versions/next.json b/docs/src/versions/next.json index 7a2559f2767a..dc7c0506dc05 100644 --- a/docs/src/versions/next.json +++ b/docs/src/versions/next.json @@ -1 +1 @@ -{"version":"5.3.0-alpha.41","info":{"plain":"### Features\n\n* Addon-docs: Render array of shape properly in props table ([#8707](https://github.com/storybookjs/storybook/pull/8707))\n* Addon-docs: Render params description in props table, support @ignore ([#8702](https://github.com/storybookjs/storybook/pull/8702))\n\n### Bug Fixes\n\n* Addon-docs: Fix \"Cannot read property 'props'\" ([#8731](https://github.com/storybookjs/storybook/pull/8731))\n* UI: Fix unmount components on Canvas/Docs tab switch ([#8625](https://github.com/storybookjs/storybook/pull/8625))\n* Angular: Fix loading baseConfig if no angular.json found ([#8727](https://github.com/storybookjs/storybook/pull/8727))"}} \ No newline at end of file +{"version":"6.0.0-alpha.2","info":{"plain":"### Features\n\n* UI: Configure tabs title, visibility, order and disable ([#9095](https://github.com/storybookjs/storybook/pull/9095))\n* Addon-cssresources: Add hideCode option ([#9627](https://github.com/storybookjs/storybook/pull/9627))\n* UI: Add `viewMode` parameter to control story nav UI ([#9090](https://github.com/storybookjs/storybook/pull/9090))\n\n### Bug Fixes\n\n* Web-components: Fix default value for prop table docs ([#9655](https://github.com/storybookjs/storybook/pull/9655))\n* Web-components: Make TypeScript types play nicely with lit-element ([#9557](https://github.com/storybookjs/storybook/pull/9557))\n* UI: Fix tabs to scroll horizontally ([#9383](https://github.com/storybookjs/storybook/pull/9383))\n* UI: Add support for className prop on Form.Field ([#9665](https://github.com/storybookjs/storybook/pull/9665))\n* Core: Upgrade `min-css-extract-plugin` to fix SASS loading ([#9652](https://github.com/storybookjs/storybook/pull/9652))\n* Adon-docs: Fix ColorPalette styling ([#9643](https://github.com/storybookjs/storybook/pull/9643))\n* Addon-storyshots: Remove excess slashes from jest transform warning ([#9616](https://github.com/storybookjs/storybook/pull/9616))\n\n### Maintenance\n\n* Source-loader: Overhaul to remove decorators, support user-configurable source ([#9547](https://github.com/storybookjs/storybook/pull/9547))\n* Build: Use Netlify for examples again ([#9585](https://github.com/storybookjs/storybook/pull/9585))\n* Ember: Migrate to new \"import { hbs } from 'ember-cli-htmlbars'\" ([#9633](https://github.com/storybookjs/storybook/pull/9633))\n* Publish: Remove docs to reduce package size ([#9612](https://github.com/storybookjs/storybook/pull/9612))"}} \ No newline at end of file diff --git a/docs/yarn.lock b/docs/yarn.lock index 903df224c825..067a0ceaed77 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -163,25 +163,13 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.2.tgz#53211e564604beb9befa7a4400ebf8147473eeef" integrity sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q== -"@emotion/is-prop-valid@0.8.2": +"@emotion/is-prop-valid@0.8.2", "@emotion/is-prop-valid@^0.8.1": version "0.8.2" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.2.tgz#b9692080da79041683021fcc32f96b40c54c59dc" integrity sha512-ZQIMAA2kLUWiUeMZNJDTeCwYRx1l8SQL0kHktze4COT22occKpDML1GDUXP5/sxhOMrZO8vZw773ni4H5Snrsg== dependencies: "@emotion/memoize" "0.7.2" -"@emotion/is-prop-valid@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.1.tgz#6fb3ae2d24f07c8cd090f233e45771d9cd826d48" - integrity sha512-Wtaek/KGUP+HusWIa8DqtOR6U/Tl+QIdVkfJQHV3IT6ZImnJwklP5UVVPKZvkfljeFk3Q45CAPJ5N10gJ5XoLA== - dependencies: - "@emotion/memoize" "0.7.1" - -"@emotion/memoize@0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f" - integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg== - "@emotion/memoize@0.7.2": version "0.7.2" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.2.tgz#7f4c71b7654068dfcccad29553520f984cc66b30" @@ -226,16 +214,11 @@ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.4.tgz#6c51afdf1dd0d73666ba09d2eb6c25c220d6fe4c" integrity sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ== -"@emotion/unitless@0.7.4": +"@emotion/unitless@0.7.4", "@emotion/unitless@^0.7.0": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.4.tgz#a87b4b04e5ae14a88d48ebef15015f6b7d1f5677" integrity sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ== -"@emotion/unitless@^0.7.0": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.3.tgz#6310a047f12d21a1036fb031317219892440416f" - integrity sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg== - "@emotion/utils@0.11.2": version "0.11.2" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.2.tgz#713056bfdffb396b0a14f1c8f18e7b4d0d200183" @@ -3309,11 +3292,6 @@ data-uri-to-buffer@0.0.4: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz#46e13ab9da8e309745c8d01ce547213ebdb2fe3f" integrity sha1-RuE6udqOMJdFyNAc5UchPr2y/j8= -date-fns@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d" - integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw== - date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -3653,14 +3631,7 @@ domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domhandler@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" - integrity sha1-LeWaCCLVAn+r/28DLCsloqir5zg= - dependencies: - domelementtype "1" - -domhandler@^2.3.0: +domhandler@2.4.2, domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== @@ -5811,48 +5782,36 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-dom-parser@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-0.2.2.tgz#960bdbf82632f11dfa7550c1b6f2aabcd6307243" - integrity sha512-8qWXUf0JY8k21oPZIWN9VddADoac+TeNTvFyF/ZJ5JgtzDIt2Fk9uwUHYKkGHnZb2LPj3SeB2V4WK0Hn/MEH/Q== +html-dom-parser@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-0.2.3.tgz#bfee592fc01c12bac08dcfa5da2611f9559a1812" + integrity sha512-GdzE63/U0IQEvcpAz0cUdYx2zQx0Ai+HWvE9TXEgwP27+SymUzKa7iB4DhjYpf2IdNLfTTOBuMS5nxeWOosSMQ== dependencies: "@types/domhandler" "2.4.1" - domhandler "2.3.0" - htmlparser2 "3.9.1" + domhandler "2.4.2" + htmlparser2 "3.10.1" html-entities@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= -html-react-parser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-0.9.1.tgz#1fc1434c201c18930a20b271f862b1967f0497e2" - integrity sha512-hr5s4FvVeocxt9bZILNi/2571/JrxlLvyDzQr6WxOc2PeZXXF6s3r4UkXOHFHgFH3WV9DV7S75zNZfE78momsw== +html-react-parser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-0.10.0.tgz#b0f3700831ce52e04013158982167c65292a418d" + integrity sha512-IDSoBgA8ZCiSoJSiumFMEUY1gsqEUeDDj8zUBGjET39RAC0aZpICp2F/doQFRqdLV0dCLLYHI7NabUara2nH1w== dependencies: "@types/domhandler" "2.4.1" - html-dom-parser "0.2.2" + html-dom-parser "0.2.3" react-property "1.0.1" - style-to-object "0.2.3" + style-to-object "0.3.0" html-void-elements@^1.0.0, html-void-elements@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.4.tgz#95e8bb5ecd6b88766569c2645f2b5f1591db9ba5" integrity sha512-yMk3naGPLrfvUV9TdDbuYXngh/TpHbA6TrOw3HL9kS8yhwx7i309BReNg7CbAJXGE+UMJ6je5OqJ7lC63o6YuQ== -htmlparser2@3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.1.tgz#621b7a58bc9acd003f7af0a2c9a00aa67c8505d2" - integrity sha1-Yht6WLyazQA/evCiyaAKpnyFBdI= - dependencies: - domelementtype "^1.3.0" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^2.0.2" - -htmlparser2@^3.10.0, htmlparser2@^3.3.0, htmlparser2@^3.9.1: +htmlparser2@3.10.1, htmlparser2@^3.10.0, htmlparser2@^3.3.0, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -9843,25 +9802,15 @@ react-dom@^15.6.0: object-assign "^4.1.0" prop-types "^15.5.10" -react-dom@^16.11.0: - version "16.11.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.11.0.tgz#7e7c4a5a85a569d565c2462f5d345da2dd849af5" - integrity sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA== +react-dom@^16.12.0, react-dom@^16.8.3: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" + integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.17.0" - -react-dom@^16.8.3: - version "16.9.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0.tgz#5e65527a5e26f22ae3701131bcccaee9fb0d3962" - integrity sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.15.0" + scheduler "^0.18.0" react-error-overlay@^3.0.0: version "3.0.0" @@ -10076,19 +10025,10 @@ react@^15.6.0: object-assign "^4.1.0" prop-types "^15.5.10" -react@^16.11.0: - version "16.11.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.11.0.tgz#d294545fe62299ccee83363599bf904e4a07fdbb" - integrity sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - -react@^16.8.3: - version "16.9.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.9.0.tgz#40ba2f9af13bc1a38d75dbf2f4359a5185c4f7aa" - integrity sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w== +react@^16.12.0, react@^16.8.3: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" + integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -10918,18 +10858,10 @@ sc-formatter@^3.0.1: resolved "https://registry.yarnpkg.com/sc-formatter/-/sc-formatter-3.0.2.tgz#9abdb14e71873ce7157714d3002477bbdb33c4e6" integrity sha512-9PbqYBpCq+OoEeRQ3QfFIGE6qwjjBcd2j7UjgDlhnZbtSnuGgHdcRklPKYGuYFH82V/dwd+AIpu8XvA1zqTd+A== -scheduler@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.15.0.tgz#6bfcf80ff850b280fed4aeecc6513bc0b4f17f8e" - integrity sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" - integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA== +scheduler@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" + integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -11828,17 +11760,17 @@ style-loader@^0.13.0: dependencies: loader-utils "^1.0.2" -style-to-object@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.2.3.tgz#afcf42bc03846b1e311880c55632a26ad2780bcb" - integrity sha512-1d/k4EY2N7jVLOqf2j04dTc37TPOv/hHxZmvpg8Pdh8UYydxeu/C1W1U4vD8alzf5V2Gt7rLsmkr4dxAlDm9ng== +style-to-object@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== dependencies: inline-style-parser "0.1.1" -styled-components@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.4.0.tgz#4e381e2dab831d0e6ea431c2840a96323e84e21b" - integrity sha512-xQ6vTI/0zNjZ1BBDRxyjvBddrxhQ3DxjeCdaLM1lSn5FDnkTOQgRkmWvcUiTajqc5nJqKVl+7sUioMqktD0+Zw== +styled-components@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.4.1.tgz#e0631e889f01db67df4de576fedaca463f05c2f2" + integrity sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/traverse" "^7.0.0" @@ -12717,12 +12649,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validatorjs@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/validatorjs/-/validatorjs-3.17.1.tgz#164308585eafbcd78d3eb56bb5b0493237aa602b" - integrity sha512-i/aOdu1FPW48Y2NRt/BmHxcpoKcl7vvGtxUkPoacUVNU8jtPlb68QYcYqt7Fls9wqO5YpfLEoUHCrpk3pkIqsQ== - dependencies: - date-fns "2.0.1" +validatorjs@^3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/validatorjs/-/validatorjs-3.18.0.tgz#f0d8e3284b0dc446197dd0d43bacafdb8e3d3b5e" + integrity sha512-SciNNWcEshji0lntKNIZkltWbGNaXyDv58Kqtdj8esjJujwAvPmUPX8WMkiCV6MUYORcAKACrCEOi1iQCgFp4g== value-equal@^0.4.0: version "0.4.0" diff --git a/examples-native/crna-kitchen-sink/.babelrc.js b/examples-native/crna-kitchen-sink/.babelrc.js deleted file mode 100644 index 03439a5b821d..000000000000 --- a/examples-native/crna-kitchen-sink/.babelrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - env: { - development: { - presets: ['babel-preset-expo'], - plugins: ['@babel/plugin-transform-react-jsx-source'], - }, - production: { - presets: ['babel-preset-expo'], - plugins: [], - }, - storybook: { - presets: [], - plugins: [], - }, - }, -}; diff --git a/examples-native/crna-kitchen-sink/.env b/examples-native/crna-kitchen-sink/.env deleted file mode 100644 index d24e628d2810..000000000000 --- a/examples-native/crna-kitchen-sink/.env +++ /dev/null @@ -1 +0,0 @@ -STORYBOOK_EXAMPLE_APP=true diff --git a/examples-native/crna-kitchen-sink/.flowconfig b/examples-native/crna-kitchen-sink/.flowconfig deleted file mode 100644 index bfe0bb12ccc0..000000000000 --- a/examples-native/crna-kitchen-sink/.flowconfig +++ /dev/null @@ -1,63 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -<PROJECT_ROOT>/\.buckd/ - -; Ignore unexpected extra "@providesModule" -.*/node_modules/.*/node_modules/fbjs/.* - -; Ignore duplicate module providers -; For RN Apps installed via npm, "Libraries" folder is inside -; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js -.*/Libraries/react-native/ReactNative.js - -; Additional create-react-native-app ignores - -; Ignore duplicate module providers -.*/node_modules/fbemitter/lib/* - -; Ignore misbehaving dev-dependencies -.*/node_modules/xdl/build/* -.*/node_modules/reqwest/tests/* - -; Ignore missing expo-sdk dependencies (temporarily) -; https://github.com/expo/expo/issues/162 -.*/node_modules/expo/src/* - -; Ignore react-native-fbads dependency of the expo sdk -.*/node_modules/react-native-fbads/* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow -flow/ - -[options] -module.system=haste - -emoji=true - -experimental.strict_type_args=true - -munge_underscores=true - -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FixMe - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - -unsafe.enable_getters_and_setters=true - -[version] -^0.47.0 diff --git a/examples-native/crna-kitchen-sink/.gitignore b/examples-native/crna-kitchen-sink/.gitignore deleted file mode 100644 index 1025e17bd3c6..000000000000 --- a/examples-native/crna-kitchen-sink/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -.expo/ -npm-debug.* diff --git a/examples-native/crna-kitchen-sink/.watchmanconfig b/examples-native/crna-kitchen-sink/.watchmanconfig deleted file mode 100644 index 0967ef424bce..000000000000 --- a/examples-native/crna-kitchen-sink/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/examples-native/crna-kitchen-sink/App.js b/examples-native/crna-kitchen-sink/App.js deleted file mode 100644 index 51a052035833..000000000000 --- a/examples-native/crna-kitchen-sink/App.js +++ /dev/null @@ -1,31 +0,0 @@ -import StorybookUIRoot from './storybook'; - -export { StorybookUIRoot as default }; - -// NOTE: The code below is what CRNA generates out of the box. We currently -// have no clever way of replacing this with Storybook's UI (Vanilla RN does!) -// so for now we replace the code outright. Keeping this here for clarity. -// -// import React from 'react'; -// import { StyleSheet, Text, View } from 'react-native'; -// -// export default class App extends React.Component { -// render() { -// return ( -// <View style={styles.container}> -// <Text>Open up App.js to start working on your app!</Text> -// <Text>Changes you make will automatically reload.</Text> -// <Text>Shake your phone to open the developer menu.</Text> -// </View> -// ); -// } -// } -// -// const styles = StyleSheet.create({ -// container: { -// flex: 1, -// backgroundColor: '#fff', -// alignItems: 'center', -// justifyContent: 'center', -// }, -// }); diff --git a/examples-native/crna-kitchen-sink/App.test.js b/examples-native/crna-kitchen-sink/App.test.js deleted file mode 100644 index 0b2ae61c645f..000000000000 --- a/examples-native/crna-kitchen-sink/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import renderer from 'react-test-renderer'; - -import React from 'react'; -import App from './App'; - -it('renders without crashing', () => { - const rendered = renderer.create(<App />).toJSON(); - expect(rendered).toBeTruthy(); -}); diff --git a/examples-native/crna-kitchen-sink/README.md b/examples-native/crna-kitchen-sink/README.md deleted file mode 100644 index 7bb23d56d896..000000000000 --- a/examples-native/crna-kitchen-sink/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# CRNA Kitchen Sink - -This project was bootstrapped wit [Expo](https://github.com/expo/expo-cli) and storybook using [storybook CLI](https://www.npmjs.com/package/@storybook/cli). - -## Getting started - -1. Install dependencies: `yarn install` at the root -2. Run the storybook server: `yarn storybook` -3. Run the app: `yarn start` diff --git a/examples-native/crna-kitchen-sink/app.json b/examples-native/crna-kitchen-sink/app.json deleted file mode 100644 index 16b4ec7db1bd..000000000000 --- a/examples-native/crna-kitchen-sink/app.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "expo": { - "sdkVersion": "33.0.0", - "platforms": [ - "ios", - "android" - ], - "androidStatusBarColor": "#C2185B", - "androidStatusBar": { - "barStyle": "light-content", - "backgroundColor": "#C2185B" - }, - "assetBundlePatterns": [ - "**/*" - ], - "packagerOpts": { - "config": "metro.config.js" - }, - "name": "crna-kitchen-sink", - "slug": "crna-kitchen-sink", - "githubUrl": "https://github.com/storybookjs/storybook/" - } -} \ No newline at end of file diff --git a/examples-native/crna-kitchen-sink/metro.config.js b/examples-native/crna-kitchen-sink/metro.config.js deleted file mode 100644 index 1badc02560ca..000000000000 --- a/examples-native/crna-kitchen-sink/metro.config.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -const path = require('path'); -const blacklist = require('metro-config/src/defaults/blacklist'); - -module.exports = { - watchFolders: [ - // The monorepo - path.resolve(__dirname, '../..'), - ], - resolver: { - blacklistRE: blacklist([ - // exclude react-native modules outside of this package - /app\/.*\/node_modules\/react-native\/.*/, - /node_modules\/.*\/node_modules\/react-native\/.*/, - // duplicate packages in server mocks. We don't need them so it's safe to exclude. - /__mocks__\/.*/, - ]), - extraNodeModules: { - // resolve react-native to this package's node_modules - 'react-native': path.resolve(__dirname, 'node_modules/react-native'), - }, - }, -}; diff --git a/examples-native/crna-kitchen-sink/package.json b/examples-native/crna-kitchen-sink/package.json deleted file mode 100644 index 0d3fbc495041..000000000000 --- a/examples-native/crna-kitchen-sink/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "crna-kitchen-sink", - "version": "5.3.0-alpha.41", - "private": true, - "main": "node_modules/expo/AppEntry.js", - "workspaces": { - "nohoist": [ - "react-native/**", - "**/app/react-native*", - "expo" - ] - }, - "scripts": { - "android": "expo start --android", - "eject": "expo eject", - "ios": "expo start --ios", - "start": "expo start", - "storybook": "BABEL_ENV=storybook node ../../app/react-native-server/bin/index", - "test": "node node_modules/jest/bin/jest.js --watch" - }, - "jest": { - "preset": "jest-expo" - }, - "dependencies": { - "expo": "^33.0.7", - "prop-types": "^15.6.2", - "react": "16.10.1", - "react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz", - "whatwg-fetch": "^3.0.0" - }, - "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/plugin-transform-react-jsx-source": "^7.2.0", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-ondevice-actions": "5.3.0-alpha.41", - "@storybook/addon-ondevice-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-ondevice-knobs": "5.3.0-alpha.41", - "@storybook/addon-ondevice-notes": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/react-native": "5.3.0-alpha.41", - "babel-loader": "^8.0.4", - "babel-plugin-module-resolver": "^3.2.0", - "babel-preset-expo": "^7.0.0", - "core-js": "^3.0.1", - "expo-cli": "^2.17.1", - "jest-expo": "^33.0.2", - "react-test-renderer": "16.10.2", - "schedule": "^0.5.0" - } -} diff --git a/examples-native/crna-kitchen-sink/storybook/addons.js b/examples-native/crna-kitchen-sink/storybook/addons.js deleted file mode 100644 index e8a3976c7539..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/addons.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-notes/register'; diff --git a/examples-native/crna-kitchen-sink/storybook/index.js b/examples-native/crna-kitchen-sink/storybook/index.js deleted file mode 100644 index 877a46d95924..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { getStorybookUI, configure } from '@storybook/react-native'; -import './rn-addons'; - -configure(() => { - // eslint-disable-next-line global-require - require('./stories'); -}, module); - -// const darkTheme = { -// backgroundColor: 'black', -// headerTextColor: 'white', -// labelColor: 'white', -// borderColor: 'white', -// previewBorderColor: 'gray', -// buttonTextColor: 'white', -// buttonActiveTextColor: 'white', -// }; - -// const StorybookUIRoot = getStorybookUI({ theme: darkTheme }); - -const StorybookUIRoot = getStorybookUI(); -export default StorybookUIRoot; diff --git a/examples-native/crna-kitchen-sink/storybook/rn-addons.js b/examples-native/crna-kitchen-sink/storybook/rn-addons.js deleted file mode 100644 index 2321f99cf843..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/rn-addons.js +++ /dev/null @@ -1,4 +0,0 @@ -require('@storybook/addon-ondevice-actions/register'); -require('@storybook/addon-ondevice-knobs/register'); -require('@storybook/addon-ondevice-notes/register'); -require('@storybook/addon-ondevice-backgrounds/register'); diff --git a/examples-native/crna-kitchen-sink/storybook/stories/Button/index.android.js b/examples-native/crna-kitchen-sink/storybook/stories/Button/index.android.js deleted file mode 100644 index 3c4c405180da..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/Button/index.android.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { TouchableNativeFeedback } from 'react-native'; - -export default function Button({ onPress, children }) { - return <TouchableNativeFeedback onPress={onPress}>{children}</TouchableNativeFeedback>; -} - -Button.defaultProps = { - children: null, - onPress: () => {}, -}; - -Button.propTypes = { - children: PropTypes.node, - onPress: PropTypes.func, -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/Button/index.ios.js b/examples-native/crna-kitchen-sink/storybook/stories/Button/index.ios.js deleted file mode 100644 index 37c8437e72e4..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/Button/index.ios.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { TouchableHighlight } from 'react-native'; - -export default function Button({ onPress, children }) { - return <TouchableHighlight onPress={onPress}>{children}</TouchableHighlight>; -} - -Button.defaultProps = { - children: null, - onPress: () => {}, -}; - -Button.propTypes = { - children: PropTypes.node, - onPress: PropTypes.func, -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/CenterView/index.js b/examples-native/crna-kitchen-sink/storybook/stories/CenterView/index.js deleted file mode 100644 index 29732b27ab24..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/CenterView/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; -import style from './style'; - -export default function CenterView({ children }) { - return <View style={style.main}>{children}</View>; -} - -CenterView.defaultProps = { - children: null, -}; - -CenterView.propTypes = { - children: PropTypes.node, -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/CenterView/style.js b/examples-native/crna-kitchen-sink/storybook/stories/CenterView/style.js deleted file mode 100644 index bc7b2bbafc86..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/CenterView/style.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - main: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/Knobs/index.js b/examples-native/crna-kitchen-sink/storybook/stories/Knobs/index.js deleted file mode 100644 index e3d008f33a20..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/Knobs/index.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { View, Text } from 'react-native'; - -import { - text, - number, - boolean, - color, - select, - radios, - array, - date, - object, -} from '@storybook/addon-knobs'; - -export default () => { - const name = text('Name', 'Storyteller'); - const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }); - const fruits = { - Apple: 'apple', - Banana: 'banana', - Cherry: 'cherry', - }; - const fruit = select('Fruit', fruits, 'apple'); - - const otherFruits = { - Kiwi: 'kiwi', - Guava: 'guava', - Watermelon: 'watermelon', - }; - const otherFruit = radios('Other Fruit', otherFruits, 'watermelon'); - const dollars = number('Dollars', 12.5); - - // NOTE: color picker is currently broken - const backgroundColor = color('background', '#ffff00'); - const items = array('Items', ['Laptop', 'Book', 'Whiskey']); - const otherStyles = object('Styles', { - borderWidth: 3, - borderColor: '#ff00ff', - padding: 10, - }); - const nice = boolean('Nice', true); - - // NOTE: put this last because it currently breaks everything after it :D - const birthday = date('Birthday', new Date('Jan 20 2017')); - - const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}. I also enjoy ${otherFruit}.`; - const style = { backgroundColor, ...otherStyles }; - const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!'; - const dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - - return ( - <View style={style}> - <Text>{intro}</Text> - <Text>My birthday is: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}</Text> - <Text>My wallet contains: ${dollars.toFixed(2)}</Text> - <Text>In my backpack, I have:</Text> - <View> - {items.map(item => ( - <Text key={item}>{item}</Text> - ))} - </View> - <Text>{salutation}</Text> - </View> - ); -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/Welcome/index.js b/examples-native/crna-kitchen-sink/storybook/stories/Welcome/index.js deleted file mode 100644 index 8e0fe99bcc7f..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/Welcome/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text } from 'react-native'; - -export default class Welcome extends React.Component { - styles = { - wrapper: { - flex: 1, - padding: 24, - justifyContent: 'center', - }, - header: { - fontSize: 18, - marginBottom: 18, - }, - content: { - fontSize: 12, - marginBottom: 10, - lineHeight: 18, - }, - }; - - showApp = event => { - const { showApp } = this.props; - - event.preventDefault(); - if (showApp) { - showApp(); - } - }; - - render() { - return ( - <View style={this.styles.wrapper}> - <Text style={this.styles.header}>Welcome to React Native Storybook</Text> - <Text style={this.styles.content}> - This is a UI Component development environment for your React Native app. Here you can - display and interact with your UI components as stories. A story is a single state of one - or more UI components. You can have as many stories as you want. In other words a story is - like a visual test case. - </Text> - <Text style={this.styles.content}> - We have added some stories inside the "storybook/stories" directory for examples. Try - editing the "storybook/stories/Welcome.js" file to edit this message. - </Text> - </View> - ); - } -} - -Welcome.defaultProps = { - showApp: null, -}; - -Welcome.propTypes = { - showApp: PropTypes.func, -}; diff --git a/examples-native/crna-kitchen-sink/storybook/stories/index.js b/examples-native/crna-kitchen-sink/storybook/stories/index.js deleted file mode 100644 index 8065ffd3d017..000000000000 --- a/examples-native/crna-kitchen-sink/storybook/stories/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; - -import { storiesOf, addDecorator, addParameters } from '@storybook/react-native'; -import { action } from '@storybook/addon-actions'; -import { linkTo } from '@storybook/addon-links'; -import { withKnobs } from '@storybook/addon-knobs'; -import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds'; -import knobsWrapper from './Knobs'; -// eslint-disable-next-line import/no-unresolved -import Button from './Button'; -import CenterView from './CenterView'; -import Welcome from './Welcome'; - -addDecorator(withBackgrounds); - -addParameters({ - backgrounds: [ - { name: 'dark', value: '#222222' }, - { name: 'white', value: '#ffffff', default: true }, - ], -}); - -storiesOf('Welcome', module) - .addParameters({ - component: Welcome, - }) - .add('to Storybook', () => <Welcome showApp={linkTo('Button')} />, { - notes: ` -# Markdown!\n -* List Item -* [List Item with Link](https://storybook.js.org) -`, - }); - -storiesOf('Button', module) - .addParameters({ - component: Button, - }) - .addParameters({ - backgrounds: [ - { name: 'dark', value: '#222222' }, - { name: 'light', value: '#eeeeee', default: true }, - ], - notes: ` -# Custom note\n -_This component doesn't look right_ -`, - }) - .addDecorator(getStory => <CenterView>{getStory()}</CenterView>) - .add('with text', () => ( - <Button onPress={action('clicked-text')}> - <Text>Hello Button</Text> - </Button> - )) - .add('with some emoji', () => ( - <Button onPress={action('clicked-emoji')}> - <Text>😀 😎 👍 💯</Text> - </Button> - )); - -storiesOf('Knobs', module) - .addDecorator(withKnobs) - .add('with knobs', knobsWrapper); - -const globalParameter = 'globalParameter'; -const chapterParameter = 'chapterParameter'; -const storyParameter = 'storyParameter'; - -addParameters({ globalParameter }); - -storiesOf('Core|Parameters', module) - .addParameters({ chapterParameter }) - .add( - 'passed to story', - ({ parameters }) => <Text>Parameters are {JSON.stringify(parameters)}</Text>, - { - storyParameter, - } - ); diff --git a/examples/angular-cli/.storybook/addons.ts b/examples/angular-cli/.storybook/addons.ts deleted file mode 100644 index eaf11c22a38d..000000000000 --- a/examples/angular-cli/.storybook/addons.ts +++ /dev/null @@ -1,9 +0,0 @@ -import '@storybook/addon-storysource/register'; -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-notes/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-jest/register'; -import '@storybook/addon-backgrounds/register'; -import '@storybook/addon-a11y/register'; diff --git a/examples/angular-cli/.storybook/main.js b/examples/angular-cli/.storybook/main.js new file mode 100644 index 000000000000..87199e4c9eb8 --- /dev/null +++ b/examples/angular-cli/.storybook/main.js @@ -0,0 +1,14 @@ +module.exports = { + stories: ['../src/stories/**/*.stories.(ts|mdx)'], + addons: [ + '@storybook/addon-docs', + '@storybook/addon-storysource', + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-knobs', + '@storybook/addon-options', + '@storybook/addon-jest', + '@storybook/addon-backgrounds', + '@storybook/addon-a11y', + ], +}; diff --git a/examples/angular-cli/.storybook/manager.js b/examples/angular-cli/.storybook/manager.js new file mode 100644 index 000000000000..ee8398728448 --- /dev/null +++ b/examples/angular-cli/.storybook/manager.js @@ -0,0 +1,5 @@ +import { addons } from '@storybook/addons'; + +addons.setConfig({ + showRoots: true, +}); diff --git a/examples/angular-cli/.storybook/presets.js b/examples/angular-cli/.storybook/presets.js deleted file mode 100644 index da117ff98532..000000000000 --- a/examples/angular-cli/.storybook/presets.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['@storybook/addon-docs/angular/preset']; diff --git a/examples/angular-cli/.storybook/config.ts b/examples/angular-cli/.storybook/preview.ts similarity index 67% rename from examples/angular-cli/.storybook/config.ts rename to examples/angular-cli/.storybook/preview.ts index 344389e4ddd9..4e526489c8ce 100644 --- a/examples/angular-cli/.storybook/config.ts +++ b/examples/angular-cli/.storybook/preview.ts @@ -1,4 +1,4 @@ -import { configure, addParameters, addDecorator } from '@storybook/angular'; +import { addParameters, addDecorator } from '@storybook/angular'; import { withA11y } from '@storybook/addon-a11y'; import { setCompodocJson } from '@storybook/addon-docs/angular'; import addCssWarning from '../src/cssWarning'; @@ -13,13 +13,8 @@ addDecorator(withA11y); addCssWarning(); addParameters({ - options: { - hierarchyRootSeparator: /\|/, - }, docs: { // inlineStories: true, iframeHeight: '60px', }, }); - -configure(require.context('../src/stories', true, /\.stories\.(ts|mdx)$/), module); diff --git a/examples/angular-cli/package.json b/examples/angular-cli/package.json index 720c8b1c69b4..2e5d40d18460 100644 --- a/examples/angular-cli/package.json +++ b/examples/angular-cli/package.json @@ -1,6 +1,6 @@ { "name": "angular-cli", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "license": "MIT", "scripts": { @@ -37,25 +37,24 @@ "@angular/cli": "^8.3.6", "@angular/compiler-cli": "^8.2.8", "@compodoc/compodoc": "^1.1.11", - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-docs": "5.3.0-alpha.41", - "@storybook/addon-jest": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storyshots": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/angular": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/addon-jest": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/angular": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", "@types/core-js": "^2.5.0", "@types/jest": "^24.0.11", - "@types/node": "^12.7.9", - "@types/webpack-env": "^1.14.0", + "@types/node": "^12.12.11", + "@types/webpack-env": "^1.15.0", "babel-plugin-require-context-hook": "^1.0.0", "global": "^4.3.2", "jasmine-core": "~3.5.0", @@ -63,7 +62,7 @@ "jest": "^24.7.1", "jest-preset-angular": "^7.1.0", "protractor": "~5.4.2", - "ts-node": "~8.4.1", + "ts-node": "~8.5.2", "typescript": "^3.4.0" } } diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot index 15bf0a822832..1ed30bcfe769 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-actions.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Actions Action And Method 1`] = ` +exports[`Storyshots Addon/Actions Action and method 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -18,7 +18,7 @@ exports[`Storyshots Addon|Actions Action And Method 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Actions Action Only 1`] = ` +exports[`Storyshots Addon/Actions Action only 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-background.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-background.stories.storyshot index f9548c66262a..14c4f68af5d8 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-background.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-background.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Background background component 1`] = ` +exports[`Storyshots Addon/Background background component 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -64,7 +64,7 @@ exports[`Storyshots Addon|Background background component 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Background background template 1`] = ` +exports[`Storyshots Addon/Background background template 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-centered.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-centered.stories.storyshot index a567e0501801..ddb525e3dd51 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-centered.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-centered.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Centered centered component 1`] = ` +exports[`Storyshots Addon/Centered centered component 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -76,7 +76,7 @@ exports[`Storyshots Addon|Centered centered component 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Centered centered template 1`] = ` +exports[`Storyshots Addon/Centered centered template 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-docs.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-docs.stories.storyshot index 5f393342ed7e..3f6bbf0f2aec 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-docs.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-docs.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Docs With Some Emoji 1`] = ` +exports[`Storyshots Addon/Docs with some emoji 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -21,7 +21,7 @@ exports[`Storyshots Addon|Docs With Some Emoji 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Docs With Text 1`] = ` +exports[`Storyshots Addon/Docs with text 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-jest.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-jest.stories.storyshot index 1c3a8b3f4756..f790202b114d 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-jest.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-jest.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Jest App Component With Jest Tests 1`] = ` +exports[`Storyshots Addon/Jest app.component with jest tests 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot index b165c070723f..648edb60acaf 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Knobs All Knobs 1`] = ` +exports[`Storyshots Addon/Knobs All knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -51,7 +51,7 @@ exports[`Storyshots Addon|Knobs All Knobs 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Knobs Simple 1`] = ` +exports[`Storyshots Addon/Knobs Simple 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -76,7 +76,7 @@ exports[`Storyshots Addon|Knobs Simple 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Addon|Knobs Xss Safety 1`] = ` +exports[`Storyshots Addon/Knobs XSS safety 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot index aca946a25e03..7c9183ea2179 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-links.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addon|Links Button With Link To Another Story 1`] = ` +exports[`Storyshots Addon/Links button with link to another story 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot deleted file mode 100644 index ee78510f1a64..000000000000 --- a/examples/angular-cli/src/stories/__snapshots__/addon-notes.stories.storyshot +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots Addon|Notes Note With Html 1`] = ` -<storybook-dynamic-app-root - cfr={[Function CodegenComponentFactoryResolver]} - data={[Function Object]} - target={[Function ViewContainerRef_]} -> - <storybook-button-component - _nghost-a-c19="" - > - <button - _ngcontent-a-c19="" - > - Notes with HTML - </button> - </storybook-button-component> -</storybook-dynamic-app-root> -`; - -exports[`Storyshots Addon|Notes Simple Note 1`] = ` -<storybook-dynamic-app-root - cfr={[Function CodegenComponentFactoryResolver]} - data={[Function Object]} - target={[Function ViewContainerRef_]} -> - <storybook-button-component - _nghost-a-c18="" - > - <button - _ngcontent-a-c18="" - > - Notes on some Button - </button> - </storybook-button-component> -</storybook-dynamic-app-root> -`; diff --git a/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot index 812919e5188b..3425505a2436 100644 --- a/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/app.component.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots App Component Component With Separate Template 1`] = ` +exports[`Storyshots App Component Component with separate template 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot index 405f45045017..baa509cf0f47 100644 --- a/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot @@ -1,18 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Core|Parameters Passed To Story 1`] = ` +exports[`Storyshots Core/Parameters passed to story 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} target={[Function ViewContainerRef_]} > <storybook-button-component - _nghost-a-c20="" + _nghost-a-c18="" > <button - _ngcontent-a-c20="" + _ngcontent-a-c18="" > - Parameters are {"options":{"hierarchyRootSeparator":{},"hierarchySeparator":{}},"docs":{"iframeHeight":"60px"},"globalParameter":"globalParameter","framework":"angular","chapterParameter":"chapterParameter","storyParameter":"storyParameter","displayName":"passed to story"} + Parameters are {"docs":{"iframeHeight":"60px"},"options":{},"globalParameter":"globalParameter","framework":"angular","chapterParameter":"chapterParameter","storyParameter":"storyParameter","__id":"core-parameters--passed-to-story"} </button> </storybook-button-component> </storybook-dynamic-app-root> diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-ng-content.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-ng-content.stories.storyshot index 3616eacf80cd..abe754745402 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-ng-content.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-ng-content.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|ng-content Default 1`] = ` +exports[`Storyshots Custom/ng-content Default 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-pipes.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-pipes.stories.storyshot index ac80b8afb219..d7b43692652c 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-pipes.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-pipes.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Pipes Simple 1`] = ` +exports[`Storyshots Custom/Pipes Simple 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -14,7 +14,7 @@ exports[`Storyshots Custom|Pipes Simple 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Pipes With Knobs Story 1`] = ` +exports[`Storyshots Custom/Pipes With Knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-providers.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-providers.stories.storyshot index e16b95cda994..2f9b464a2d1a 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-providers.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-providers.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Providers Simple 1`] = ` +exports[`Storyshots Custom/Providers Simple 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -17,7 +17,7 @@ exports[`Storyshots Custom|Providers Simple 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Providers With Knobs Story 1`] = ` +exports[`Storyshots Custom/Providers With knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot index df81df2c4406..7aaa2e151141 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Style Default Story 1`] = ` +exports[`Storyshots Custom/Style Default 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -8,11 +8,11 @@ exports[`Storyshots Custom|Style Default Story 1`] = ` > <ng-component> <storybook-button-component - _nghost-a-c21="" + _nghost-a-c19="" ng-reflect-text="Button with custom styles" > <button - _ngcontent-a-c21="" + _ngcontent-a-c19="" > Button with custom styles </button> @@ -21,7 +21,7 @@ exports[`Storyshots Custom|Style Default Story 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Style With Knobs Story 1`] = ` +exports[`Storyshots Custom/Style With Knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -29,11 +29,11 @@ exports[`Storyshots Custom|Style With Knobs Story 1`] = ` > <ng-component> <storybook-button-component - _nghost-a-c22="" + _nghost-a-c20="" ng-reflect-text="Button with custom styles" > <button - _ngcontent-a-c22="" + _ngcontent-a-c20="" > Button with custom styles </button> diff --git a/examples/angular-cli/src/stories/__snapshots__/metadata-combined.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/metadata-combined.stories.storyshot index f4a53212f89a..30ef8eefcaa6 100644 --- a/examples/angular-cli/src/stories/__snapshots__/metadata-combined.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/metadata-combined.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Metadata|Combined Combined 1 1`] = ` +exports[`Storyshots Metadata/Combined Combined 1 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -30,7 +30,7 @@ exports[`Storyshots Metadata|Combined Combined 1 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Metadata|Combined Combined 2 1`] = ` +exports[`Storyshots Metadata/Combined Combined 2 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/metadata-individual.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/metadata-individual.stories.storyshot index 25b3e0f48148..5e437c1bdd10 100644 --- a/examples/angular-cli/src/stories/__snapshots__/metadata-individual.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/metadata-individual.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Metadata|Individual Individual 1 1`] = ` +exports[`Storyshots Metadata/Individual Individual 1 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -30,7 +30,7 @@ exports[`Storyshots Metadata|Individual Individual 1 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Metadata|Individual Individual 2 1`] = ` +exports[`Storyshots Metadata/Individual Individual 2 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/metadata-shared.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/metadata-shared.stories.storyshot index 42116cfe07a7..0f8de42d1b3a 100644 --- a/examples/angular-cli/src/stories/__snapshots__/metadata-shared.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/metadata-shared.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Metadata|Shared Shared 1 1`] = ` +exports[`Storyshots Metadata/Shared Shared 1 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -30,7 +30,7 @@ exports[`Storyshots Metadata|Shared Shared 1 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Metadata|Shared Shared 2 1`] = ` +exports[`Storyshots Metadata/Shared Shared 2 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/__snapshots__/ngrx-store.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/ngrx-store.stories.storyshot index 7891f51327e7..82c58dc1f6c3 100644 --- a/examples/angular-cli/src/stories/__snapshots__/ngrx-store.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/ngrx-store.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots ngrx|Store With component 1`] = ` +exports[`Storyshots ngrx/Store With component 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -14,7 +14,7 @@ exports[`Storyshots ngrx|Store With component 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots ngrx|Store With template 1`] = ` +exports[`Storyshots ngrx/Store With template 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/addon-actions.stories.ts b/examples/angular-cli/src/stories/addon-actions.stories.ts index dc7d8a047f3b..7d9b6ad77774 100644 --- a/examples/angular-cli/src/stories/addon-actions.stories.ts +++ b/examples/angular-cli/src/stories/addon-actions.stories.ts @@ -2,10 +2,10 @@ import { action } from '@storybook/addon-actions'; import { Button } from '@storybook/angular/demo'; export default { - title: 'Addon|Actions', + title: 'Addon/Actions', }; -export const actionOnly = () => ({ +export const ActionOnly = () => ({ component: Button, props: { text: 'Action only', @@ -13,11 +13,11 @@ export const actionOnly = () => ({ }, }); -actionOnly.story = { +ActionOnly.story = { name: 'Action only', }; -export const actionAndMethod = () => ({ +export const ActionAndMethod = () => ({ component: Button, props: { text: 'Action and Method', @@ -29,6 +29,6 @@ export const actionAndMethod = () => ({ }, }); -actionAndMethod.story = { +ActionAndMethod.story = { name: 'Action and method', }; diff --git a/examples/angular-cli/src/stories/addon-background.stories.ts b/examples/angular-cli/src/stories/addon-background.stories.ts index aa1f808d6eaa..3d1ed5d3e480 100644 --- a/examples/angular-cli/src/stories/addon-background.stories.ts +++ b/examples/angular-cli/src/stories/addon-background.stories.ts @@ -2,7 +2,7 @@ import { moduleMetadata, storiesOf } from '@storybook/angular'; import { Button } from '@storybook/angular/demo'; import { AppComponent } from '../app/app.component'; -storiesOf('Addon|Background', module) +storiesOf('Addon/Background', module) .addParameters({ backgrounds: [ { name: 'twitter', value: '#00aced', default: true }, @@ -14,7 +14,7 @@ storiesOf('Addon|Background', module) props: {}, })); -storiesOf('Addon|Background', module) +storiesOf('Addon/Background', module) .addDecorator( moduleMetadata({ declarations: [Button], diff --git a/examples/angular-cli/src/stories/addon-centered.stories.ts b/examples/angular-cli/src/stories/addon-centered.stories.ts index 9748d17255ff..1f2834a541a7 100644 --- a/examples/angular-cli/src/stories/addon-centered.stories.ts +++ b/examples/angular-cli/src/stories/addon-centered.stories.ts @@ -3,14 +3,14 @@ import { moduleMetadata, storiesOf } from '@storybook/angular'; import { Button } from '@storybook/angular/demo'; import { AppComponent } from '../app/app.component'; -storiesOf('Addon|Centered', module) +storiesOf('Addon/Centered', module) .addDecorator(centered) .add('centered component', () => ({ component: AppComponent, props: {}, })); -storiesOf('Addon|Centered', module) +storiesOf('Addon/Centered', module) .addDecorator( moduleMetadata({ declarations: [Button], diff --git a/examples/angular-cli/src/stories/addon-docs.stories.mdx b/examples/angular-cli/src/stories/addon-docs.stories.mdx index 318db4f30800..71a7309fe024 100644 --- a/examples/angular-cli/src/stories/addon-docs.stories.mdx +++ b/examples/angular-cli/src/stories/addon-docs.stories.mdx @@ -2,6 +2,7 @@ import { moduleMetadata } from '@storybook/angular'; import { Story, Meta } from '@storybook/addon-docs/blocks'; import { Welcome, Button } from '@storybook/angular/demo'; import { linkTo } from '@storybook/addon-links'; +import { text, withKnobs } from '@storybook/addon-knobs'; # Storybook Docs for Angular @@ -19,7 +20,7 @@ How you like them apples?! Just like in React, we first declare our component. -<Meta title="Addon|Docs" decorators={[moduleMetadata({ declarations: [Button] })]} /> +<Meta title="Addon/Docs" decorators={[withKnobs, moduleMetadata({ declarations: [Button] })]} /> This declaration doesn't show up in the MDX output. @@ -53,7 +54,7 @@ Similarly, here's how we do it in the Docs MDX format. We've already added the d {{ template: `<storybook-button-component [text]="text" (onClick)="onClick($event)"></storybook-button-component>`, props: { - text: 'Hello Button', + text: text('Button text', 'Hello Button'), onClick: () => {}, }, }} diff --git a/examples/angular-cli/src/stories/addon-jest.stories.ts b/examples/angular-cli/src/stories/addon-jest.stories.ts index f7c2ac7b4c3b..b5ca229ef1b7 100644 --- a/examples/angular-cli/src/stories/addon-jest.stories.ts +++ b/examples/angular-cli/src/stories/addon-jest.stories.ts @@ -4,7 +4,7 @@ import { AppComponent } from '../app/app.component'; import * as results from '../../addon-jest.testresults.json'; export default { - title: 'Addon|Jest', + title: 'Addon/Jest', decorators: [ withTests({ results, @@ -13,12 +13,12 @@ export default { ], }; -export const appComponentWithJestTests = () => ({ +export const AppComponentWithJestTests = () => ({ component: AppComponent, props: {}, }); -appComponentWithJestTests.story = { +AppComponentWithJestTests.story = { name: 'app.component with jest tests', parameters: { diff --git a/examples/angular-cli/src/stories/addon-knobs.stories.ts b/examples/angular-cli/src/stories/addon-knobs.stories.ts index 61b79ee52140..36501f9165be 100644 --- a/examples/angular-cli/src/stories/addon-knobs.stories.ts +++ b/examples/angular-cli/src/stories/addon-knobs.stories.ts @@ -17,7 +17,7 @@ import { SimpleKnobsComponent } from './knobs.component'; import { AllKnobsComponent } from './all-knobs.component'; export default { - title: 'Addon|Knobs', + title: 'Addon/Knobs', decorators: [withKnobs], parameters: { knobs: { @@ -26,7 +26,7 @@ export default { }, }; -export const simple = () => { +export const Simple = () => { const name = text('name', 'John Doe'); const age = number('age', 0); const phoneNumber = text('phoneNumber', '555-55-55'); @@ -53,11 +53,11 @@ export const simple = () => { }; }; -simple.story = { +Simple.story = { name: 'Simple', }; -export const allKnobs = () => { +export const AllKnobs = () => { const name = text('name', 'Jane'); const stock = number('stock', 20, { range: true, @@ -101,14 +101,14 @@ export const allKnobs = () => { }; }; -allKnobs.story = { +AllKnobs.story = { name: 'All knobs', }; -export const xssSafety = () => ({ +export const XssSafety = () => ({ template: text('Rendered string', '<img src=x onerror="alert(\'XSS Attack\')" >'), }); -xssSafety.story = { +XssSafety.story = { name: 'XSS safety', }; diff --git a/examples/angular-cli/src/stories/addon-links.stories.ts b/examples/angular-cli/src/stories/addon-links.stories.ts index ec41687fbfcf..065d4df98410 100644 --- a/examples/angular-cli/src/stories/addon-links.stories.ts +++ b/examples/angular-cli/src/stories/addon-links.stories.ts @@ -2,10 +2,10 @@ import { linkTo } from '@storybook/addon-links'; import { Button } from '@storybook/angular/demo'; export default { - title: 'Addon|Links', + title: 'Addon/Links', }; -export const buttonWithLinkToAnotherStory = () => ({ +export const ButtonWithLinkToAnotherStory = () => ({ component: Button, props: { text: 'Go to Welcome Story', @@ -13,6 +13,6 @@ export const buttonWithLinkToAnotherStory = () => ({ }, }); -buttonWithLinkToAnotherStory.story = { +ButtonWithLinkToAnotherStory.story = { name: 'button with link to another story', }; diff --git a/examples/angular-cli/src/stories/addon-notes.stories.ts b/examples/angular-cli/src/stories/addon-notes.stories.ts deleted file mode 100644 index 59d68ab17e75..000000000000 --- a/examples/angular-cli/src/stories/addon-notes.stories.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Button } from '@storybook/angular/demo'; - -export default { - title: 'Addon|Notes', -}; - -export const simpleNote = () => ({ - component: Button, - props: { - text: 'Notes on some Button', - onClick: () => {}, - }, -}); - -simpleNote.story = { - name: 'Simple note', - parameters: { notes: 'My notes on some button' }, -}; - -export const noteWithHtml = () => ({ - component: Button, - props: { - text: 'Notes with HTML', - onClick: () => {}, - }, -}); - -noteWithHtml.story = { - name: 'Note with HTML', - parameters: { - notes: ` - <h2>My notes on emojis</h2> - - <em>It's not all that important to be honest, but..</em> - - Emojis are great, I love emojis, in fact I like using them in my Component notes too! 😇 - `, - }, -}; diff --git a/examples/angular-cli/src/stories/app.component.stories.ts b/examples/angular-cli/src/stories/app.component.stories.ts index 40f7b779e89d..d9a65e40769d 100644 --- a/examples/angular-cli/src/stories/app.component.stories.ts +++ b/examples/angular-cli/src/stories/app.component.stories.ts @@ -5,12 +5,12 @@ export default { component: AppComponent, }; -export const componentWithSeparateTemplate = () => ({ +export const ComponentWithSeparateTemplate = () => ({ component: AppComponent, props: {}, }); -componentWithSeparateTemplate.story = { +ComponentWithSeparateTemplate.story = { name: 'Component with separate template', parameters: { docs: { iframeHeight: 400 } }, }; diff --git a/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot b/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot index e5a7203f3a78..e7a5a337303e 100644 --- a/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot +++ b/examples/angular-cli/src/stories/component-with-di/__snapshots__/di.component.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Dependencies Inputs And Inject Dependencies 1`] = ` +exports[`Storyshots Custom/Dependencies inputs and inject dependencies 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -31,7 +31,7 @@ exports[`Storyshots Custom|Dependencies Inputs And Inject Dependencies 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Dependencies Inputs And Inject Dependencies With Knobs 1`] = ` +exports[`Storyshots Custom/Dependencies inputs and inject dependencies with knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/component-with-di/di.component.stories.ts b/examples/angular-cli/src/stories/component-with-di/di.component.stories.ts index bbfc736dd1f1..59e2734db89c 100644 --- a/examples/angular-cli/src/stories/component-with-di/di.component.stories.ts +++ b/examples/angular-cli/src/stories/component-with-di/di.component.stories.ts @@ -2,28 +2,28 @@ import { withKnobs, text } from '@storybook/addon-knobs'; import { DiComponent } from './di.component'; export default { - title: 'Custom|Dependencies', + title: 'Custom/Dependencies', }; -export const inputsAndInjectDependencies = () => ({ +export const InputsAndInjectDependencies = () => ({ component: DiComponent, props: { title: 'Component dependencies', }, }); -inputsAndInjectDependencies.story = { +InputsAndInjectDependencies.story = { name: 'inputs and inject dependencies', }; -export const inputsAndInjectDependenciesWithKnobs = () => ({ +export const InputsAndInjectDependenciesWithKnobs = () => ({ component: DiComponent, props: { title: text('title', 'Component dependencies'), }, }); -inputsAndInjectDependenciesWithKnobs.story = { +InputsAndInjectDependenciesWithKnobs.story = { name: 'inputs and inject dependencies with knobs', decorators: [withKnobs], }; diff --git a/examples/angular-cli/src/stories/component-with-style/__snapshots__/styled.component.stories.storyshot b/examples/angular-cli/src/stories/component-with-style/__snapshots__/styled.component.stories.storyshot index d26ec96e6091..fde2c3b965a7 100644 --- a/examples/angular-cli/src/stories/component-with-style/__snapshots__/styled.component.stories.storyshot +++ b/examples/angular-cli/src/stories/component-with-style/__snapshots__/styled.component.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|styleUrls Component With Styles 1`] = ` +exports[`Storyshots Custom/styleUrls Component with styles 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/component-with-style/styled.component.stories.ts b/examples/angular-cli/src/stories/component-with-style/styled.component.stories.ts index 3934fb7a5d2a..868e6dd35485 100644 --- a/examples/angular-cli/src/stories/component-with-style/styled.component.stories.ts +++ b/examples/angular-cli/src/stories/component-with-style/styled.component.stories.ts @@ -1,13 +1,13 @@ import { StyledComponent } from './styled.component'; export default { - title: 'Custom|styleUrls', + title: 'Custom/styleUrls', }; -export const componentWithStyles = () => ({ +export const ComponentWithStyles = () => ({ component: StyledComponent, }); -componentWithStyles.story = { +ComponentWithStyles.story = { name: 'Component with styles', }; diff --git a/examples/angular-cli/src/stories/core.stories.ts b/examples/angular-cli/src/stories/core.stories.ts index 10c2a42233e8..91898719d28f 100644 --- a/examples/angular-cli/src/stories/core.stories.ts +++ b/examples/angular-cli/src/stories/core.stories.ts @@ -8,13 +8,13 @@ const storyParameter = 'storyParameter'; addParameters({ globalParameter }); export default { - title: 'Core|Parameters', + title: 'Core/Parameters', parameters: { chapterParameter, }, }; -export const passedToStory = ({ parameters: { fileName, ...parameters } }) => ({ +export const PassedToStory = ({ parameters: { fileName, ...parameters } }) => ({ component: Button, props: { text: `Parameters are ${JSON.stringify(parameters)}`, @@ -22,7 +22,7 @@ export const passedToStory = ({ parameters: { fileName, ...parameters } }) => ({ }, }); -passedToStory.story = { +PassedToStory.story = { name: 'passed to story', parameters: { storyParameter }, }; diff --git a/examples/angular-cli/src/stories/custom-ng-content.stories.ts b/examples/angular-cli/src/stories/custom-ng-content.stories.ts index 4ccc2c3e46b6..495047971eaf 100644 --- a/examples/angular-cli/src/stories/custom-ng-content.stories.ts +++ b/examples/angular-cli/src/stories/custom-ng-content.stories.ts @@ -9,7 +9,7 @@ import { storiesOf } from '@storybook/angular'; }) class WithNgContentComponent {} -storiesOf('Custom|ng-content', module).add('Default', () => ({ +storiesOf('Custom/ng-content', module).add('Default', () => ({ template: `<storybook-with-ng-content><h1>This is rendered in ng-content</h1></storybook-with-ng-content>`, moduleMetadata: { declarations: [WithNgContentComponent], diff --git a/examples/angular-cli/src/stories/custom-pipes.stories.ts b/examples/angular-cli/src/stories/custom-pipes.stories.ts index 217ebe5ee360..f0c9d379fcea 100644 --- a/examples/angular-cli/src/stories/custom-pipes.stories.ts +++ b/examples/angular-cli/src/stories/custom-pipes.stories.ts @@ -5,7 +5,7 @@ import { NameComponent } from './moduleMetadata/name.component'; import { CustomPipePipe } from './moduleMetadata/custom.pipe'; export default { - title: 'Custom|Pipes', + title: 'Custom/Pipes', decorators: [ moduleMetadata({ imports: [], @@ -16,25 +16,25 @@ export default { ], }; -export const simple = () => ({ +export const Simple = () => ({ component: NameComponent, props: { field: 'foobar', }, }); -simple.story = { +Simple.story = { name: 'Simple', }; -export const withKnobsStory = () => ({ +export const WithKnobsStory = () => ({ component: NameComponent, props: { field: text('field', 'foobar'), }, }); -withKnobsStory.story = { +WithKnobsStory.story = { name: 'With Knobs', decorators: [withKnobs], }; diff --git a/examples/angular-cli/src/stories/custom-providers.stories.ts b/examples/angular-cli/src/stories/custom-providers.stories.ts index ad8b66a9048b..86b2c2093798 100644 --- a/examples/angular-cli/src/stories/custom-providers.stories.ts +++ b/examples/angular-cli/src/stories/custom-providers.stories.ts @@ -5,7 +5,7 @@ import { DummyService } from './moduleMetadata/dummy.service'; import { ServiceComponent } from './moduleMetadata/service.component'; export default { - title: 'Custom|Providers', + title: 'Custom/Providers', decorators: [ moduleMetadata({ imports: [], @@ -16,18 +16,18 @@ export default { ], }; -export const simple = () => ({ +export const Simple = () => ({ component: ServiceComponent, props: { name: 'Static name', }, }); -simple.story = { +Simple.story = { name: 'Simple', }; -export const withKnobsStory = () => { +export const WithKnobsStory = () => { const name = text('name', 'Dynamic knob'); return { @@ -38,7 +38,7 @@ export const withKnobsStory = () => { }; }; -withKnobsStory.story = { +WithKnobsStory.story = { name: 'With knobs', decorators: [withKnobs], }; diff --git a/examples/angular-cli/src/stories/custom-styles.stories.ts b/examples/angular-cli/src/stories/custom-styles.stories.ts index 1d1bcd1f69b0..207e818174ca 100644 --- a/examples/angular-cli/src/stories/custom-styles.stories.ts +++ b/examples/angular-cli/src/stories/custom-styles.stories.ts @@ -4,7 +4,7 @@ import { withKnobs, text } from '@storybook/addon-knobs'; import { Button } from '@storybook/angular/demo'; export default { - title: 'Custom|Style', + title: 'Custom/Style', decorators: [ moduleMetadata({ declarations: [Button], @@ -12,7 +12,7 @@ export default { ], }; -export const defaultStory = () => ({ +export const DefaultStory = () => ({ template: `<storybook-button-component [text]="text" (onClick)="onClick($event)"></storybook-button-component>`, props: { text: 'Button with custom styles', @@ -28,11 +28,11 @@ export const defaultStory = () => ({ ], }); -defaultStory.story = { +DefaultStory.story = { name: 'Default', }; -export const withKnobsStory = () => ({ +export const WithKnobsStory = () => ({ template: `<storybook-button-component [text]="text" (onClick)="onClick($event)"></storybook-button-component>`, props: { text: text('text', 'Button with custom styles'), @@ -48,7 +48,7 @@ export const withKnobsStory = () => ({ ], }); -withKnobsStory.story = { +WithKnobsStory.story = { name: 'With Knobs', decorators: [withKnobs], }; diff --git a/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot b/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot index 414a67409592..36c082cd97fa 100644 --- a/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot +++ b/examples/angular-cli/src/stories/customControlValueAccessor/__snapshots__/custom-cva-component.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|ngModel Custom Control Value Accessor 1`] = ` +exports[`Storyshots Custom/ngModel custom ControlValueAccessor 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/customControlValueAccessor/custom-cva-component.stories.ts b/examples/angular-cli/src/stories/customControlValueAccessor/custom-cva-component.stories.ts index 1abfbbfb60fe..e4a7ae06b432 100644 --- a/examples/angular-cli/src/stories/customControlValueAccessor/custom-cva-component.stories.ts +++ b/examples/angular-cli/src/stories/customControlValueAccessor/custom-cva-component.stories.ts @@ -1,15 +1,11 @@ import { action } from '@storybook/addon-actions'; import { CustomCvaComponent } from './custom-cva.component'; -const description = ` - This is an example of component that implements ControlValueAccessor interface -`; - export default { - title: 'Custom|ngModel', + title: 'Custom/ngModel', }; -export const customControlValueAccessor = () => ({ +export const CustomControlValueAccessor = () => ({ component: CustomCvaComponent, props: { ngModel: 'Type anything', @@ -17,7 +13,6 @@ export const customControlValueAccessor = () => ({ }, }); -customControlValueAccessor.story = { +CustomControlValueAccessor.story = { name: 'custom ControlValueAccessor', - parameters: { notes: description }, }; diff --git a/examples/angular-cli/src/stories/doc-button/doc-button.stories.ts b/examples/angular-cli/src/stories/doc-button/doc-button.stories.ts index fba2d69a05bb..30faec0ae6c7 100644 --- a/examples/angular-cli/src/stories/doc-button/doc-button.stories.ts +++ b/examples/angular-cli/src/stories/doc-button/doc-button.stories.ts @@ -6,7 +6,7 @@ export default { parameters: { docs: { iframeHeight: 120 } }, }; -export const basic = () => ({ +export const Basic = () => ({ component: ButtonComponent, props: { label: 'Docs Test', diff --git a/examples/angular-cli/src/stories/doc-directive/__snapshots__/doc-directive.stories.storyshot b/examples/angular-cli/src/stories/doc-directive/__snapshots__/doc-directive.stories.storyshot new file mode 100644 index 000000000000..de28ae96a794 --- /dev/null +++ b/examples/angular-cli/src/stories/doc-directive/__snapshots__/doc-directive.stories.storyshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots DocDirective Basic 1`] = ` +<storybook-dynamic-app-root + cfr={[Function CodegenComponentFactoryResolver]} + data={[Function Object]} + target={[Function ViewContainerRef_]} +> + <ng-component> + <div + docdirective="" + ng-reflect-has-gray-background="true" + style="background-color: lightgray;" + > + <h1> + DocDirective + </h1> + </div> + </ng-component> +</storybook-dynamic-app-root> +`; diff --git a/examples/angular-cli/src/stories/doc-directive/doc-directive.directive.ts b/examples/angular-cli/src/stories/doc-directive/doc-directive.directive.ts new file mode 100644 index 000000000000..bcb039c9d9f1 --- /dev/null +++ b/examples/angular-cli/src/stories/doc-directive/doc-directive.directive.ts @@ -0,0 +1,25 @@ +/* eslint-disable no-useless-constructor */ +import { Directive, ElementRef, AfterViewInit, Input } from '@angular/core'; + +/** + * This is an Angular Directive + * example that has a Prop Table. + */ +@Directive({ + selector: '[docDirective]', +}) +export class DocDirective implements AfterViewInit { + constructor(private ref: ElementRef) {} + + /** + * Will apply gray background color + * if set to true. + */ + @Input() hasGrayBackground = false; + + ngAfterViewInit(): void { + if (this.hasGrayBackground) { + this.ref.nativeElement.style = 'background-color: lightgray'; + } + } +} diff --git a/examples/angular-cli/src/stories/doc-directive/doc-directive.stories.ts b/examples/angular-cli/src/stories/doc-directive/doc-directive.stories.ts new file mode 100644 index 000000000000..519302892b64 --- /dev/null +++ b/examples/angular-cli/src/stories/doc-directive/doc-directive.stories.ts @@ -0,0 +1,16 @@ +import { DocDirective } from './doc-directive.directive'; + +export default { + title: 'DocDirective', + component: DocDirective, + parameters: { docs: { iframeHeight: 120 } }, +}; + +const modules = { + declarations: [DocDirective], +}; + +export const Basic = () => ({ + moduleMetadata: modules, + template: '<div docDirective [hasGrayBackground]="true"><h1>DocDirective</h1></div>', +}); diff --git a/examples/angular-cli/src/stories/inheritance/__snapshots__/inheritance.stories.storyshot b/examples/angular-cli/src/stories/inheritance/__snapshots__/inheritance.stories.storyshot index 4d680501adf0..a273a408a4ee 100644 --- a/examples/angular-cli/src/stories/inheritance/__snapshots__/inheritance.stories.storyshot +++ b/examples/angular-cli/src/stories/inheritance/__snapshots__/inheritance.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Inheritance Base Button 1`] = ` +exports[`Storyshots Custom/Inheritance base button 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -14,7 +14,7 @@ exports[`Storyshots Custom|Inheritance Base Button 1`] = ` </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Inheritance Icon Button 1`] = ` +exports[`Storyshots Custom/Inheritance icon button 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts b/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts index 6d5dbc9030d5..37060f32ca92 100644 --- a/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts +++ b/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts @@ -2,10 +2,10 @@ import { IconButtonComponent } from './icon-button.component'; import { BaseButtonComponent } from './base-button.component'; export default { - title: 'Custom|Inheritance', + title: 'Custom/Inheritance', }; -export const iconButton = () => ({ +export const IconButton = () => ({ component: IconButtonComponent, props: { icon: 'this is icon', @@ -13,17 +13,17 @@ export const iconButton = () => ({ }, }); -iconButton.story = { +IconButton.story = { name: 'icon button', }; -export const baseButton = () => ({ +export const BaseButton = () => ({ component: BaseButtonComponent, props: { label: 'this is label', }, }); -baseButton.story = { +BaseButton.story = { name: 'base button', }; diff --git a/examples/angular-cli/src/stories/metadata-combined.stories.ts b/examples/angular-cli/src/stories/metadata-combined.stories.ts index 9aeceb8b8d2f..a1af39192691 100644 --- a/examples/angular-cli/src/stories/metadata-combined.stories.ts +++ b/examples/angular-cli/src/stories/metadata-combined.stories.ts @@ -3,7 +3,7 @@ import { TokenComponent, ITEMS, DEFAULT_NAME } from './moduleMetadata/token.comp import { CustomPipePipe } from './moduleMetadata/custom.pipe'; export default { - title: 'Metadata|Combined', + title: 'Metadata/Combined', decorators: [ moduleMetadata({ imports: [], @@ -22,18 +22,18 @@ export default { ], }; -export const combined1 = () => ({ +export const Combined1 = () => ({ template: `<storybook-simple-token-component [name]="name"></storybook-simple-token-component>`, props: { name: 'Prop Name', }, }); -combined1.story = { +Combined1.story = { name: 'Combined 1', }; -export const combined2 = () => ({ +export const Combined2 = () => ({ template: `<storybook-simple-token-component [name]="name | customPipe"></storybook-simple-token-component>`, props: { name: 'Prop Name', @@ -43,6 +43,6 @@ export const combined2 = () => ({ }, }); -combined2.story = { +Combined2.story = { name: 'Combined 2', }; diff --git a/examples/angular-cli/src/stories/metadata-individual.stories.ts b/examples/angular-cli/src/stories/metadata-individual.stories.ts index 39887d5a822a..d70c0306f690 100644 --- a/examples/angular-cli/src/stories/metadata-individual.stories.ts +++ b/examples/angular-cli/src/stories/metadata-individual.stories.ts @@ -1,10 +1,10 @@ import { TokenComponent, ITEMS, DEFAULT_NAME } from './moduleMetadata/token.component'; export default { - title: 'Metadata|Individual', + title: 'Metadata/Individual', }; -export const individual1 = () => ({ +export const Individual1 = () => ({ template: `<storybook-simple-token-component [name]="name"></storybook-simple-token-component>`, props: { name: 'Prop Name', @@ -21,11 +21,11 @@ export const individual1 = () => ({ }, }); -individual1.story = { +Individual1.story = { name: 'Individual 1', }; -export const individual2 = () => ({ +export const Individual2 = () => ({ template: `<storybook-simple-token-component></storybook-simple-token-component>`, moduleMetadata: { imports: [], @@ -43,6 +43,6 @@ export const individual2 = () => ({ }, }); -individual2.story = { +Individual2.story = { name: 'Individual 2', }; diff --git a/examples/angular-cli/src/stories/metadata-shared.stories.ts b/examples/angular-cli/src/stories/metadata-shared.stories.ts index 0189ffb13f19..9748a174f306 100644 --- a/examples/angular-cli/src/stories/metadata-shared.stories.ts +++ b/examples/angular-cli/src/stories/metadata-shared.stories.ts @@ -2,7 +2,7 @@ import { moduleMetadata } from '@storybook/angular'; import { TokenComponent, ITEMS, DEFAULT_NAME } from './moduleMetadata/token.component'; export default { - title: 'Metadata|Shared', + title: 'Metadata/Shared', decorators: [ moduleMetadata({ imports: [], @@ -21,21 +21,21 @@ export default { ], }; -export const shared1 = () => ({ +export const Shared1 = () => ({ template: `<storybook-simple-token-component [name]="name"></storybook-simple-token-component>`, props: { name: 'Prop Name', }, }); -shared1.story = { +Shared1.story = { name: 'Shared 1', }; -export const shared2 = () => ({ +export const Shared2 = () => ({ template: `<storybook-simple-token-component></storybook-simple-token-component>`, }); -shared2.story = { +Shared2.story = { name: 'Shared 2', }; diff --git a/examples/angular-cli/src/stories/module-context/__snapshots__/module-context-forRoot.stories.storyshot b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context-forRoot.stories.storyshot index fa0397a37c2d..8272f9662872 100644 --- a/examples/angular-cli/src/stories/module-context/__snapshots__/module-context-forRoot.stories.storyshot +++ b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context-forRoot.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Feature Module as Context with forRoot Component with default providers 1`] = ` +exports[`Storyshots Custom/Feature Module as Context with forRoot Component with default providers 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -31,7 +31,7 @@ exports[`Storyshots Custom|Feature Module as Context with forRoot Component with </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Feature Module as Context with forRoot Component with overridden provider 1`] = ` +exports[`Storyshots Custom/Feature Module as Context with forRoot Component with overridden provider 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -62,7 +62,7 @@ exports[`Storyshots Custom|Feature Module as Context with forRoot Component with </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Feature Module as Context with forRoot Component with self and dependencies declared in its feature module 1`] = ` +exports[`Storyshots Custom/Feature Module as Context with forRoot Component with self and dependencies declared in its feature module 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot index 838d283d5c21..2e34fd8ed68a 100644 --- a/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot +++ b/examples/angular-cli/src/stories/module-context/__snapshots__/module-context.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Custom|Feature Module as Context Component with default providers 1`] = ` +exports[`Storyshots Custom/Feature Module as Context Component with default providers 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -31,7 +31,7 @@ exports[`Storyshots Custom|Feature Module as Context Component with default prov </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Feature Module as Context Component with overridden provider 1`] = ` +exports[`Storyshots Custom/Feature Module as Context Component with overridden provider 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} @@ -62,7 +62,7 @@ exports[`Storyshots Custom|Feature Module as Context Component with overridden p </storybook-dynamic-app-root> `; -exports[`Storyshots Custom|Feature Module as Context Component with self and dependencies declared in its feature module 1`] = ` +exports[`Storyshots Custom/Feature Module as Context Component with self and dependencies declared in its feature module 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} diff --git a/examples/angular-cli/src/stories/module-context/module-context-forRoot.stories.ts b/examples/angular-cli/src/stories/module-context/module-context-forRoot.stories.ts index 7f9e1f56a41c..10438400c401 100644 --- a/examples/angular-cli/src/stories/module-context/module-context-forRoot.stories.ts +++ b/examples/angular-cli/src/stories/module-context/module-context-forRoot.stories.ts @@ -6,40 +6,33 @@ import { ChipsModule } from './chips.module'; import { ChipsGroupComponent } from './chips-group.component'; import { ChipComponent } from './chip.component'; -storiesOf('Custom|Feature Module as Context with forRoot', module) +storiesOf('Custom/Feature Module as Context with forRoot', module) .addDecorator(withKnobs) .addDecorator( moduleMetadata({ imports: [ChipsModule.forRoot()], }) ) - .add( - 'Component with self and dependencies declared in its feature module', - () => { - const props: { [K in keyof ChipsGroupComponent]?: any } = { - chips: object('Chips', [ - { - id: 1, - text: 'Chip 1', - }, - { - id: 2, - text: 'Chip 2', - }, - ]), - removeChipClick: action('Remove chip'), - removeAllChipsClick: action('Remove all chips clicked'), - }; - return { - component: ChipsGroupComponent, - props, - }; - }, - { - notes: `This component includes a child component, a pipe, and a default provider, all which come from - the specified feature module.`, - } - ) + .add('Component with self and dependencies declared in its feature module', () => { + const props: { [K in keyof ChipsGroupComponent]?: any } = { + chips: object('Chips', [ + { + id: 1, + text: 'Chip 1', + }, + { + id: 2, + text: 'Chip 2', + }, + ]), + removeChipClick: action('Remove chip'), + removeAllChipsClick: action('Remove all chips clicked'), + }; + return { + component: ChipsGroupComponent, + props, + }; + }) .add('Component with default providers', () => { const props: { [K in keyof ChipComponent]?: any } = { displayText: text('Display Text', 'My Chip'), diff --git a/examples/angular-cli/src/stories/module-context/module-context.stories.ts b/examples/angular-cli/src/stories/module-context/module-context.stories.ts index c3ffbe7c147c..0c3ad488bfb2 100644 --- a/examples/angular-cli/src/stories/module-context/module-context.stories.ts +++ b/examples/angular-cli/src/stories/module-context/module-context.stories.ts @@ -7,7 +7,7 @@ import { ChipsGroupComponent } from './chips-group.component'; import { ChipComponent } from './chip.component'; import { CHIP_COLOR } from './chip-color.token'; -storiesOf('Custom|Feature Module as Context', module) +storiesOf('Custom/Feature Module as Context', module) .addDecorator(withKnobs) .addDecorator( moduleMetadata({ diff --git a/examples/angular-cli/src/stories/moduleMetadata/dummy.service.ts b/examples/angular-cli/src/stories/moduleMetadata/dummy.service.ts index 7033f0564003..1196064f6dfc 100644 --- a/examples/angular-cli/src/stories/moduleMetadata/dummy.service.ts +++ b/examples/angular-cli/src/stories/moduleMetadata/dummy.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; @Injectable() export class DummyService { - // eslint-disable-next-line @typescript-eslint/no-empty-function, no-useless-constructor + // eslint-disable-next-line no-useless-constructor constructor() {} getItems() { diff --git a/examples/angular-cli/src/stories/ngrx-store.stories.ts b/examples/angular-cli/src/stories/ngrx-store.stories.ts index cb20255a1059..8312f8b372c5 100644 --- a/examples/angular-cli/src/stories/ngrx-store.stories.ts +++ b/examples/angular-cli/src/stories/ngrx-store.stories.ts @@ -18,7 +18,7 @@ class WithStoreComponent { } } -storiesOf('ngrx|Store', module) +storiesOf('ngrx/Store', module) .addDecorator( moduleMetadata({ imports: [ diff --git a/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot b/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot index 15ea94c59450..ab226b47795a 100644 --- a/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot +++ b/examples/angular-cli/src/stories/on-push/__snapshots__/on-push.stories.storyshot @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Core|OnPush Class Specified Component With On Push And Knobs 1`] = ` +exports[`Storyshots Core/OnPush Class-specified component with OnPush and Knobs 1`] = ` <storybook-dynamic-app-root cfr={[Function CodegenComponentFactoryResolver]} data={[Function Object]} target={[Function ViewContainerRef_]} > <storybook-on-push-box - _nghost-a-c23="" + _nghost-a-c21="" style="background-color: rgb(255, 240, 0);" > Word of the day: OnPush diff --git a/examples/angular-cli/src/stories/on-push/on-push.stories.ts b/examples/angular-cli/src/stories/on-push/on-push.stories.ts index d050b138787a..543a6187e630 100644 --- a/examples/angular-cli/src/stories/on-push/on-push.stories.ts +++ b/examples/angular-cli/src/stories/on-push/on-push.stories.ts @@ -2,11 +2,11 @@ import { withKnobs, text, color } from '@storybook/addon-knobs'; import { OnPushBoxComponent } from './on-push-box.component'; export default { - title: 'Core|OnPush', + title: 'Core/OnPush', decorators: [withKnobs], }; -export const classSpecifiedComponentWithOnPushAndKnobs = () => ({ +export const ClassSpecifiedComponentWithOnPushAndKnobs = () => ({ component: OnPushBoxComponent, props: { word: text('Word', 'OnPush'), @@ -14,11 +14,6 @@ export const classSpecifiedComponentWithOnPushAndKnobs = () => ({ }, }); -classSpecifiedComponentWithOnPushAndKnobs.story = { +ClassSpecifiedComponentWithOnPushAndKnobs.story = { name: 'Class-specified component with OnPush and Knobs', - parameters: { - notes: ` - This component is specified by class and uses OnPush change detection. It has two properties, one being a HostBinding. Both should be updatable using knobs. - `.trim(), - }, }; diff --git a/examples/cra-kitchen-sink/.storybook/addons.js b/examples/cra-kitchen-sink/.storybook/addons.js deleted file mode 100644 index 55875383363f..000000000000 --- a/examples/cra-kitchen-sink/.storybook/addons.js +++ /dev/null @@ -1,9 +0,0 @@ -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-events/register'; -import '@storybook/addon-notes/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-backgrounds/register'; -import '@storybook/addon-a11y/register'; -import '@storybook/addon-jest/register'; diff --git a/examples/cra-kitchen-sink/.storybook/config.js b/examples/cra-kitchen-sink/.storybook/config.js deleted file mode 100644 index 4e9b936e6a9d..000000000000 --- a/examples/cra-kitchen-sink/.storybook/config.js +++ /dev/null @@ -1,26 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/react'; -import { create } from '@storybook/theming/create'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - isFullscreen: false, - showAddonsPanel: true, - showSearchBox: false, - panelPosition: 'right', - hierarchySeparator: /\./, - hierarchyRootSeparator: /\|/, - enableShortcuts: true, - theme: create({ - base: 'light', - brandTitle: 'CRA Kitchen Sink', - brandUrl: 'https://github.com/storybookjs/storybook/tree/master/examples/cra-kitchen-sink', - gridCellSize: 12, - }), - storySort: (a, b) => - a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, { numeric: true }), - }, -}); - -configure(require.context('../src/stories', true, /\.stories\.(js|mdx)$/), module); diff --git a/examples/cra-kitchen-sink/.storybook/main.js b/examples/cra-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..cded0ccb8265 --- /dev/null +++ b/examples/cra-kitchen-sink/.storybook/main.js @@ -0,0 +1,20 @@ +module.exports = { + addons: [ + '@storybook/preset-create-react-app', + { + name: '@storybook/addon-docs/preset', + options: { + configureJSX: true, + }, + }, + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-events', + '@storybook/addon-options', + '@storybook/addon-knobs', + '@storybook/addon-backgrounds', + '@storybook/addon-a11y', + '@storybook/addon-jest', + ], + stories: ['../src/stories/**/*.stories.(js|mdx)'], +}; diff --git a/examples/cra-kitchen-sink/.storybook/manager.js b/examples/cra-kitchen-sink/.storybook/manager.js new file mode 100644 index 000000000000..8b2c926cf5fe --- /dev/null +++ b/examples/cra-kitchen-sink/.storybook/manager.js @@ -0,0 +1,14 @@ +import { create } from '@storybook/theming/create'; +import { addons } from '@storybook/addons'; + +addons.setConfig({ + isFullscreen: false, + showAddonsPanel: true, + panelPosition: 'right', + theme: create({ + base: 'light', + brandTitle: 'CRA Kitchen Sink', + brandUrl: 'https://github.com/storybookjs/storybook/tree/master/examples/cra-kitchen-sink', + gridCellSize: 12, + }), +}); diff --git a/examples/cra-kitchen-sink/.storybook/presets.js b/examples/cra-kitchen-sink/.storybook/presets.js deleted file mode 100644 index bd5d86e6e969..000000000000 --- a/examples/cra-kitchen-sink/.storybook/presets.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = [ - { - name: '@storybook/addon-docs/react/preset', - options: { - configureJSX: true, - }, - }, -]; diff --git a/examples/cra-kitchen-sink/.storybook/preview.js b/examples/cra-kitchen-sink/.storybook/preview.js new file mode 100644 index 000000000000..6f9c8caefc7f --- /dev/null +++ b/examples/cra-kitchen-sink/.storybook/preview.js @@ -0,0 +1,10 @@ +import { addParameters, addDecorator } from '@storybook/react'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); +addParameters({ + options: { + storySort: (a, b) => + a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), + }, +}); diff --git a/examples/cra-kitchen-sink/package.json b/examples/cra-kitchen-sink/package.json index 0595029941d1..96b5cd1dcba1 100644 --- a/examples/cra-kitchen-sink/package.json +++ b/examples/cra-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "cra-kitchen-sink", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { "build": "react-scripts build", @@ -18,23 +18,21 @@ "react-lifecycles-compat": "^3.0.4" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-docs": "5.3.0-alpha.41", - "@storybook/addon-events": "5.3.0-alpha.41", - "@storybook/addon-info": "5.3.0-alpha.41", - "@storybook/addon-jest": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storyshots": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/react": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/addon-events": "6.0.0-alpha.2", + "@storybook/addon-jest": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/react": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "react-scripts": "^3.0.1" } } diff --git a/examples/cra-kitchen-sink/src/stories/App.stories.js b/examples/cra-kitchen-sink/src/stories/App.stories.js index 953d945c6f44..2f773b4b223a 100644 --- a/examples/cra-kitchen-sink/src/stories/App.stories.js +++ b/examples/cra-kitchen-sink/src/stories/App.stories.js @@ -8,8 +8,8 @@ export default { title: 'App', }; -export const fullApp = () => <App />; +export const FullApp = () => <App />; -fullApp.story = { +FullApp.story = { name: 'full app', }; diff --git a/examples/cra-kitchen-sink/src/stories/Lifecycle.stories.js b/examples/cra-kitchen-sink/src/stories/Lifecycle.stories.js index 0b48d1a40321..0a8fbaceb3ff 100644 --- a/examples/cra-kitchen-sink/src/stories/Lifecycle.stories.js +++ b/examples/cra-kitchen-sink/src/stories/Lifecycle.stories.js @@ -5,4 +5,4 @@ export default { title: 'Lifecycle', }; -export const logging = () => <LifecycleLogger />; +export const Logging = () => <LifecycleLogger />; diff --git a/examples/cra-kitchen-sink/src/stories/button.stories.js b/examples/cra-kitchen-sink/src/stories/button.stories.js index b0ec9efecd6b..d4045dc185b2 100644 --- a/examples/cra-kitchen-sink/src/stories/button.stories.js +++ b/examples/cra-kitchen-sink/src/stories/button.stories.js @@ -1,80 +1,30 @@ -/* eslint-disable react/destructuring-assignment */ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { withInfo } from '@storybook/addon-info'; import { Button } from '@storybook/react/demo'; -import Container from '../components/Container'; - -const InfoButton = () => ( - <span - style={{ - fontFamily: 'sans-serif', - fontSize: 12, - textDecoration: 'none', - background: '#027ac5', - color: '#fff', - padding: '5px 15px', - margin: 10, - borderRadius: '0px 0px 0px 5px', - }} - > -  Show Info  - </span> -); - export default { title: 'Button', component: Button, }; -export const story1 = () => <Button onClick={action('clicked', { depth: 1 })}>Hello Button</Button>; -story1.story = { +export const Story1 = () => <Button onClick={action('clicked', { depth: 1 })}>Hello Button</Button>; +Story1.story = { name: 'with text', parameters: { options: { selectedPanel: 'storybook/actions/panel' }, }, }; -export const story2 = () => ( +export const Story2 = () => ( <Button onClick={action('clicked')}> <span role="img" aria-label="yolo"> 😀 😎 👍 💯 </span> </Button> ); -story2.story = { +Story2.story = { name: 'with some emoji', parameters: { options: { selectedPanel: 'storybook/actions/panel' }, }, }; - -export const story3 = () => <Button>Check my notes in the notes panel</Button>; -story3.story = { - name: 'with notes', - parameters: { - notes: 'A very simple button', - options: { selectedPanel: 'storybook/notes/panel' }, - }, -}; - -export const story4 = context => ( - <Container> - <span> - click the <InfoButton /> label in top right for info about "{context.name}" - </span> - </Container> -); -story4.story = { - name: 'with new info', - parameters: { - notes: 'Composition: Info(Notes())', - options: { selectedPanel: 'storybook/info/panel' }, - }, - decorators: [ - withInfo( - 'Use the [info addon](https://github.com/storybookjs/storybook/tree/master/addons/info) with its new painless API.' - ), - ], -}; diff --git a/examples/cra-kitchen-sink/src/stories/cra-dynamic-import.stories.js b/examples/cra-kitchen-sink/src/stories/cra-dynamic-import.stories.js index 45fb81c14241..24b6f340196d 100644 --- a/examples/cra-kitchen-sink/src/stories/cra-dynamic-import.stories.js +++ b/examples/cra-kitchen-sink/src/stories/cra-dynamic-import.stories.js @@ -7,7 +7,7 @@ export default { title: 'CRA', }; -export const story1 = () => { +export const Story1 = () => { if (!Component) { import('@storybook/react/demo').then(({ Button }) => { Component = Button; @@ -20,4 +20,4 @@ export const story1 = () => { return <Component>Hello Button</Component>; }; -story1.story = { name: 'Dynamic import' }; +Story1.story = { name: 'Dynamic import' }; diff --git a/examples/cra-kitchen-sink/src/stories/force-rerender.stories.js b/examples/cra-kitchen-sink/src/stories/force-rerender.stories.js index ad1c9edb4f68..2635e6739ffd 100644 --- a/examples/cra-kitchen-sink/src/stories/force-rerender.stories.js +++ b/examples/cra-kitchen-sink/src/stories/force-rerender.stories.js @@ -1,5 +1,6 @@ import React from 'react'; import { forceReRender } from '@storybook/react'; +import { Button } from '@storybook/react/demo'; let count = 0; const increment = () => { @@ -11,8 +12,8 @@ export default { title: 'Force ReRender', }; -export const button = () => ( - <button type="button" onClick={increment}> +export const DefaultView = () => ( + <Button type="button" onClick={increment}> Click me to increment: {count} - </button> + </Button> ); diff --git a/examples/cra-kitchen-sink/src/stories/long-description.stories.js b/examples/cra-kitchen-sink/src/stories/long-description.stories.js index 02b61f388686..2fc4ffe73949 100644 --- a/examples/cra-kitchen-sink/src/stories/long-description.stories.js +++ b/examples/cra-kitchen-sink/src/stories/long-description.stories.js @@ -8,5 +8,5 @@ export default { decorators: [centered], }; -export const story1 = () => <Button onClick={action('clicked')}>Hello Button</Button>; -story1.story = { name: 'with text' }; +export const Story1 = () => <Button onClick={action('clicked')}>Hello Button</Button>; +Story1.story = { name: 'with text' }; diff --git a/examples/cra-kitchen-sink/src/stories/perf.stories.js b/examples/cra-kitchen-sink/src/stories/perf.stories.js deleted file mode 100644 index d0612b21af1d..000000000000 --- a/examples/cra-kitchen-sink/src/stories/perf.stories.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import { storiesOf } from '@storybook/react'; - -import { Button } from '@storybook/react/demo'; - -for (let i = 0; i < 1; i += 1) { - const randomDemoName = Math.random() - .toString(36) - .substring(7); - const stories = storiesOf(`Perf.${randomDemoName}`); - - for (let j = 0; j < 10; j += 1) { - stories - .add(`with text ${j}`, () => <Button>Hello Button</Button>) - .add(`with emoji ${j}`, () => ( - <Button> - <span role="img" aria-label="so cool"> - 😀 😎 👍 💯 - </span> - </Button> - )); - } -} diff --git a/examples/cra-kitchen-sink/src/stories/test.stories.mdx b/examples/cra-kitchen-sink/src/stories/test.stories.mdx index 1e6df8d1d17d..6d296277b347 100644 --- a/examples/cra-kitchen-sink/src/stories/test.stories.mdx +++ b/examples/cra-kitchen-sink/src/stories/test.stories.mdx @@ -1,6 +1,6 @@ import { Story, Meta } from '@storybook/addon-docs/blocks'; -<Meta title="Docs|Test" /> +<Meta title="Docs/Test" /> <Story name="NewStory123"> <h2> Hello </h2> diff --git a/examples/cra-kitchen-sink/src/stories/welcome.stories.js b/examples/cra-kitchen-sink/src/stories/welcome.stories.js index a7203b79b87d..878844fa562c 100644 --- a/examples/cra-kitchen-sink/src/stories/welcome.stories.js +++ b/examples/cra-kitchen-sink/src/stories/welcome.stories.js @@ -7,5 +7,5 @@ export default { component: Welcome, }; -export const story1 = () => <Welcome showApp={linkTo('Button')} />; -story1.title = 'to Storybook'; +export const Story1 = () => <Welcome showApp={linkTo('Button')} />; +Story1.title = 'to Storybook'; diff --git a/examples/cra-react15/.storybook/addons.js b/examples/cra-react15/.storybook/addons.js deleted file mode 100644 index 6aed412d04af..000000000000 --- a/examples/cra-react15/.storybook/addons.js +++ /dev/null @@ -1,2 +0,0 @@ -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; diff --git a/examples/cra-react15/.storybook/config.js b/examples/cra-react15/.storybook/config.js deleted file mode 100644 index 4e08d6629c4c..000000000000 --- a/examples/cra-react15/.storybook/config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { configure, addParameters } from '@storybook/react'; -import { create } from '@storybook/theming/create'; - -addParameters({ - options: { - theme: create({ colorPrimary: 'hotpink', colorSecondary: 'orangered' }), - }, -}); - -// test loading function -const loadFn = () => { - // place welcome first, test storiesof files - require('../src/stories/welcome.stories'); - - // test mixtures of storiesof & module files - const req = require.context('../src/stories', true, /\.stories\.js$/); - return req.keys().map(fname => req(fname)); -}; - -configure(loadFn, module); diff --git a/examples/cra-react15/.storybook/main.js b/examples/cra-react15/.storybook/main.js new file mode 100644 index 000000000000..46cf3a05227d --- /dev/null +++ b/examples/cra-react15/.storybook/main.js @@ -0,0 +1,8 @@ +module.exports = { + stories: ['../src/stories/welcome.stories', '../src/stories/**/button.stories.js'], + addons: [ + '@storybook/preset-create-react-app', + '@storybook/addon-actions', + '@storybook/addon-links', + ], +}; diff --git a/examples/cra-react15/.storybook/manager.js b/examples/cra-react15/.storybook/manager.js new file mode 100644 index 000000000000..c7f477dc0cdf --- /dev/null +++ b/examples/cra-react15/.storybook/manager.js @@ -0,0 +1,6 @@ +import { create } from '@storybook/theming/create'; +import { addons } from '@storybook/addons'; + +addons.setConfig({ + theme: create({ colorPrimary: 'hotpink', colorSecondary: 'orangered' }), +}); diff --git a/examples/cra-react15/package.json b/examples/cra-react15/package.json index 7b3bd6013481..670f3dec951b 100644 --- a/examples/cra-react15/package.json +++ b/examples/cra-react15/package.json @@ -1,6 +1,6 @@ { "name": "cra-react15", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { "build": "react-scripts build", @@ -18,10 +18,11 @@ "react-scripts": "3.0.1" }, "devDependencies": { - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/react": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/react": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "babel-core": "6", "babel-runtime": "6" } diff --git a/examples/cra-react15/src/stories/button.stories.js b/examples/cra-react15/src/stories/button.stories.js index 3c443e29464f..1b35a0e35f1f 100644 --- a/examples/cra-react15/src/stories/button.stories.js +++ b/examples/cra-react15/src/stories/button.stories.js @@ -9,14 +9,14 @@ export default { }, }; -export const story1 = () => <Button onClick={action('clicked')}>Hello Button</Button>; -story1.story = { name: 'with text' }; +export const Story1 = () => <Button onClick={action('clicked')}>Hello Button</Button>; +Story1.story = { name: 'with text' }; -export const story2 = () => ( +export const Story2 = () => ( <Button onClick={action('clicked')}> <span role="img" aria-label="so cool"> 😀 😎 👍 💯 </span> </Button> ); -story2.story = { name: 'with some emoji' }; +Story2.story = { name: 'with some emoji' }; diff --git a/examples/cra-ts-essentials/.storybook/main.js b/examples/cra-ts-essentials/.storybook/main.js new file mode 100644 index 000000000000..9ec4897ee490 --- /dev/null +++ b/examples/cra-ts-essentials/.storybook/main.js @@ -0,0 +1,12 @@ +module.exports = { + stories: ['../src/**/*.stories.tsx'], + addons: [ + '@storybook/preset-create-react-app', + { + name: '@storybook/addon-essentials', + options: { + backgrounds: false, + }, + }, + ], +}; diff --git a/examples/cra-ts-essentials/README.md b/examples/cra-ts-essentials/README.md new file mode 100644 index 000000000000..8ef4a5d7cdcf --- /dev/null +++ b/examples/cra-ts-essentials/README.md @@ -0,0 +1 @@ +Demonstrate `@storybook/addon-essentials` default configuration with CRA / Typescript. diff --git a/examples/cra-ts-essentials/package.json b/examples/cra-ts-essentials/package.json new file mode 100644 index 000000000000..632eae3b8a22 --- /dev/null +++ b/examples/cra-ts-essentials/package.json @@ -0,0 +1,44 @@ +{ + "name": "cra-ts-essentials", + "version": "6.0.0-alpha.2", + "private": true, + "scripts": { + "build": "react-scripts build", + "build-storybook": "build-storybook -s public", + "eject": "react-scripts eject", + "start": "react-scripts start", + "storybook": "start-storybook -p 9009 -s public", + "test": "react-scripts test" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "eslintConfig": { + "extends": "react-app" + }, + "dependencies": { + "@types/jest": "24.0.22", + "@types/node": "12.12.6", + "@types/react": "16.9.11", + "@types/react-dom": "16.9.4", + "react": "^16.11.0", + "react-dom": "^16.11.0", + "react-scripts": "3.2.0", + "typescript": "3.7.2" + }, + "devDependencies": { + "@storybook/addon-essentials": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/preset-create-react-app": "^1.5.0", + "@storybook/react": "6.0.0-alpha.2" + } +} diff --git a/examples/cra-ts-essentials/public/favicon.ico b/examples/cra-ts-essentials/public/favicon.ico new file mode 100644 index 000000000000..c2c86b859eaa Binary files /dev/null and b/examples/cra-ts-essentials/public/favicon.ico differ diff --git a/examples/cra-ts-essentials/public/index.html b/examples/cra-ts-essentials/public/index.html new file mode 100644 index 000000000000..c240d2ca8b0f --- /dev/null +++ b/examples/cra-ts-essentials/public/index.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="theme-color" content="#000000" /> + <meta + name="description" + content="Web site created using create-react-app" + /> + <link rel="apple-touch-icon" href="logo192.png" /> + <!-- + manifest.json provides metadata used when your web app is installed on a + user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ + --> + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> + <!-- + Notice the use of %PUBLIC_URL% in the tags above. + It will be replaced with the URL of the `public` folder during the build. + Only files inside the `public` folder can be referenced from the HTML. + + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will + work correctly both with client-side routing and a non-root public URL. + Learn how to configure a non-root public URL by running `npm run build`. + --> + <title>React App + + + +
+ + + diff --git a/examples/cra-ts-essentials/public/logo192.png b/examples/cra-ts-essentials/public/logo192.png new file mode 100644 index 000000000000..fbdb05d4eb6b Binary files /dev/null and b/examples/cra-ts-essentials/public/logo192.png differ diff --git a/examples/cra-ts-essentials/public/logo512.png b/examples/cra-ts-essentials/public/logo512.png new file mode 100644 index 000000000000..917458c29a82 Binary files /dev/null and b/examples/cra-ts-essentials/public/logo512.png differ diff --git a/examples/cra-ts-essentials/public/manifest.json b/examples/cra-ts-essentials/public/manifest.json new file mode 100644 index 000000000000..080d6c77ac21 --- /dev/null +++ b/examples/cra-ts-essentials/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/cra-ts-essentials/public/robots.txt b/examples/cra-ts-essentials/public/robots.txt new file mode 100644 index 000000000000..01b0f9a10733 --- /dev/null +++ b/examples/cra-ts-essentials/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/examples/cra-ts-essentials/src/App.css b/examples/cra-ts-essentials/src/App.css new file mode 100644 index 000000000000..afc3885715f4 --- /dev/null +++ b/examples/cra-ts-essentials/src/App.css @@ -0,0 +1,22 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #09d3ac; +} diff --git a/examples/cra-ts-essentials/src/App.test.tsx b/examples/cra-ts-essentials/src/App.test.tsx new file mode 100644 index 000000000000..a754b201bf9c --- /dev/null +++ b/examples/cra-ts-essentials/src/App.test.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/examples/cra-ts-essentials/src/App.tsx b/examples/cra-ts-essentials/src/App.tsx new file mode 100644 index 000000000000..226ee6316af7 --- /dev/null +++ b/examples/cra-ts-essentials/src/App.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import logo from './logo.svg'; +import './App.css'; + +const App: React.FC = () => { + return ( +
+
+ logo +

+ Edit src/App.tsx and save to reload. +

+ + Learn React + +
+
+ ); +} + +export default App; diff --git a/examples/cra-ts-essentials/src/index.css b/examples/cra-ts-essentials/src/index.css new file mode 100644 index 000000000000..ec2585e8c0bb --- /dev/null +++ b/examples/cra-ts-essentials/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/examples/cra-ts-essentials/src/index.tsx b/examples/cra-ts-essentials/src/index.tsx new file mode 100644 index 000000000000..87d1be551891 --- /dev/null +++ b/examples/cra-ts-essentials/src/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(, document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/examples/cra-ts-essentials/src/logo.svg b/examples/cra-ts-essentials/src/logo.svg new file mode 100644 index 000000000000..7bd1599766bb --- /dev/null +++ b/examples/cra-ts-essentials/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/cra-ts-essentials/src/react-app-env.d.ts b/examples/cra-ts-essentials/src/react-app-env.d.ts new file mode 100644 index 000000000000..6431bc5fc6b2 --- /dev/null +++ b/examples/cra-ts-essentials/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/cra-ts-essentials/src/serviceWorker.ts b/examples/cra-ts-essentials/src/serviceWorker.ts new file mode 100644 index 000000000000..15d90cb81a1d --- /dev/null +++ b/examples/cra-ts-essentials/src/serviceWorker.ts @@ -0,0 +1,143 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +type Config = { + onSuccess?: (registration: ServiceWorkerRegistration) => void; + onUpdate?: (registration: ServiceWorkerRegistration) => void; +}; + +export function register(config?: Config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL( + (process as { env: { [key: string]: string } }).env.PUBLIC_URL, + window.location.href + ); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl: string, config?: Config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl: string, config?: Config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx b/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx new file mode 100644 index 000000000000..fa267f24c316 --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { linkTo } from '@storybook/addon-links'; +import { Welcome } from '@storybook/react/demo'; + +export default { + title: 'Welcome', + component: Welcome, +}; + +export const toStorybook = () => ; + +toStorybook.story = { + name: 'to Storybook', +}; diff --git a/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx b/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx new file mode 100644 index 000000000000..bf3304c3f2ea --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import { Button } from '@storybook/react/demo'; +import { text } from '@storybook/addon-knobs'; + +export default { + title: 'Button', + component: Button, +}; + +export const _text = () => ( + +); + +export const emoji = () => ( + +); diff --git a/examples/cra-ts-essentials/tsconfig.json b/examples/cra-ts-essentials/tsconfig.json new file mode 100644 index 000000000000..f2850b71613e --- /dev/null +++ b/examples/cra-ts-essentials/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src" + ] +} diff --git a/examples/cra-ts-kitchen-sink/.storybook/addons.ts b/examples/cra-ts-kitchen-sink/.storybook/addons.ts deleted file mode 100644 index 39ffabfe9dd3..000000000000 --- a/examples/cra-ts-kitchen-sink/.storybook/addons.ts +++ /dev/null @@ -1,3 +0,0 @@ -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-a11y/register'; diff --git a/examples/cra-ts-kitchen-sink/.storybook/main.js b/examples/cra-ts-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..04b6bba21210 --- /dev/null +++ b/examples/cra-ts-kitchen-sink/.storybook/main.js @@ -0,0 +1,28 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/**/*.stories.(mdx|[tj]sx?)'], + addons: [ + { + name: '@storybook/preset-create-react-app', + options: { + tsDocgenLoaderOptions: { + tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), + shouldExtractLiteralValuesFromEnum: true, + propFilter: prop => { + // Currently not working, prop.parent is always null. + if (prop.parent) { + return !prop.parent.fileName.includes('node_modules/@types/react/'); + } + + return true; + }, + }, + }, + }, + '@storybook/addon-docs', + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-a11y', + ], +}; diff --git a/examples/cra-ts-kitchen-sink/.storybook/presets.js b/examples/cra-ts-kitchen-sink/.storybook/presets.js deleted file mode 100644 index d8b8fdeeb455..000000000000 --- a/examples/cra-ts-kitchen-sink/.storybook/presets.js +++ /dev/null @@ -1,13 +0,0 @@ -const path = require('path'); - -module.exports = [ - { - name: '@storybook/preset-create-react-app', - options: { - tsDocgenLoaderOptions: { - tsconfigPath: path.resolve(__dirname, '../tsconfig.json'), - }, - }, - }, - '@storybook/addon-docs/react/preset', -]; diff --git a/examples/cra-ts-kitchen-sink/.storybook/config.ts b/examples/cra-ts-kitchen-sink/.storybook/preview.ts similarity index 59% rename from examples/cra-ts-kitchen-sink/.storybook/config.ts rename to examples/cra-ts-kitchen-sink/.storybook/preview.ts index db54c9b99c86..ec21c3337ee9 100644 --- a/examples/cra-ts-kitchen-sink/.storybook/config.ts +++ b/examples/cra-ts-kitchen-sink/.storybook/preview.ts @@ -1,4 +1,4 @@ -import { configure, addParameters, addDecorator } from '@storybook/react'; +import { addParameters, addDecorator } from '@storybook/react'; import { withA11y } from '@storybook/addon-a11y'; import { withKnobs } from '@storybook/addon-knobs'; @@ -8,8 +8,6 @@ addParameters({ options: { brandTitle: 'CRA TypeScript Kitchen Sink', brandUrl: 'https://github.com/storybookjs/storybook/tree/master/examples/cra-ts-kitchen-sink', + showRoots: true, }, }); - -// automatically import all files ending in *.stories.(tsx|jsx) -configure(require.context('../src/stories', true, /\.stories\.(mdx|[tj]sx?)$/), module); diff --git a/examples/cra-ts-kitchen-sink/package.json b/examples/cra-ts-kitchen-sink/package.json index 8dbef179d508..94e55238304f 100644 --- a/examples/cra-ts-kitchen-sink/package.json +++ b/examples/cra-ts-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "cra-ts-kitchen-sink", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { "build": "react-scripts build", @@ -23,26 +23,25 @@ ] }, "dependencies": { - "@types/jest": "24.0.19", - "@types/node": "12.11.1", - "@types/react": "16.9.9", - "@types/react-dom": "16.9.2", + "@types/jest": "24.0.23", + "@types/node": "12.12.11", + "@types/react": "16.9.11", + "@types/react-dom": "16.9.4", "prop-types": "^15.7.2", "react": "^16.10.2", "react-dom": "^16.10.2", "react-scripts": "3.2.0", - "typescript": "3.6.4" + "typescript": "3.7.2" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-info": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/preset-create-react-app": "^1.2.0", - "@storybook/react": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/preset-create-react-app": "^1.5.0", + "@storybook/react": "6.0.0-alpha.2", "@types/enzyme": "^3.9.0", "@types/react": "^16.8.14", "@types/react-dom": "^16.8.2", @@ -50,7 +49,8 @@ "enzyme-adapter-react-16": "^1.9.1", "enzyme-to-json": "^3.4.1", "fork-ts-checker-webpack-plugin": "^3.0.1", - "react-docgen-typescript-loader": "^3.0.1", + "react-docgen-typescript-loader": "^3.3.0", + "react-moment-proptypes": "^1.7.0", "react-scripts": "^3.0.1", "tslint": "^5.14.0", "tslint-config-airbnb": "^5.11.1", diff --git a/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx b/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx index 46209d5111e5..d058304c95f1 100644 --- a/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx +++ b/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx @@ -4,13 +4,13 @@ import { radios } from '@storybook/addon-knobs'; import Button, { Type } from './Button'; export default { - title: 'Docgen|Button', + title: 'Docgen/Button', component: Button, }; type Story = () => any; -export const simpleButton: Story = () => { +export const SimpleButton: Story = () => { const x = 0; return ; }; @@ -20,6 +20,6 @@ const typeOptions = { Action: 'action', }; -export const withType = () => ( +export const WithType = () => ( ); diff --git a/examples/cra-ts-kitchen-sink/src/stories/0-Welcome.stories.tsx b/examples/cra-ts-kitchen-sink/src/stories/0-Welcome.stories.tsx index fa267f24c316..c76f45f3b4be 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/0-Welcome.stories.tsx +++ b/examples/cra-ts-kitchen-sink/src/stories/0-Welcome.stories.tsx @@ -7,8 +7,8 @@ export default { component: Welcome, }; -export const toStorybook = () => ; +export const ToStorybook = () => ; -toStorybook.story = { +ToStorybook.story = { name: 'to Storybook', }; diff --git a/examples/cra-ts-kitchen-sink/src/stories/1-Button.stories.tsx b/examples/cra-ts-kitchen-sink/src/stories/1-Button.stories.tsx index b4608d78e926..f9cd3c5119d3 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/1-Button.stories.tsx +++ b/examples/cra-ts-kitchen-sink/src/stories/1-Button.stories.tsx @@ -7,9 +7,9 @@ export default { component: Button, }; -export const text = () => ; +export const Text = () => ; -export const emoji = () => ( +export const Emoji = () => ( ); + +Button.defaultProps = { + primary: true, + secondary: false, +}; diff --git a/examples/cra-ts-kitchen-sink/src/stories/PropsSort.js b/examples/cra-ts-kitchen-sink/src/stories/PropsSort.js new file mode 100644 index 000000000000..2e4a6b383e16 --- /dev/null +++ b/examples/cra-ts-kitchen-sink/src/stories/PropsSort.js @@ -0,0 +1,16 @@ +/* eslint-disable react/no-unused-prop-types */ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const PropsSort = () =>
PropsSort!
; +PropsSort.propTypes = { + foo: PropTypes.string, + middleWithDefaultValue: PropTypes.string, + bar: PropTypes.string, + endWithDefaultValue: PropTypes.string, +}; +PropsSort.defaultProps = { + middleWithDefaultValue: 'Middle!', + endWithDefaultValue: 'End!', +}; diff --git a/examples/cra-ts-kitchen-sink/src/stories/anchors/anchors.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/anchors/anchors.stories.mdx new file mode 100644 index 000000000000..fa27a4109c98 --- /dev/null +++ b/examples/cra-ts-kitchen-sink/src/stories/anchors/anchors.stories.mdx @@ -0,0 +1,62 @@ +import { Meta } from '@storybook/addon-docs/blocks'; + + + +go to bottom + +## Hey + +Some code!!! +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Ho + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Hey + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Ho + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Hey + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Ho + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

+ +## Bottom + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque leo dolor, pharetra non arcu vel, accumsan commodo ligula. Vestibulum ut nulla mauris. Mauris vehicula pharetra sem, ac semper quam euismod eget. Fusce sit amet laoreet leo. Maecenas vitae mollis nibh. Morbi ullamcorper justo at enim malesuada, non porta augue ullamcorper. Etiam varius ultrices nisi, eu pharetra lacus sollicitudin eu. Suspendisse sit amet tincidunt dolor, a lobortis lectus.

+

Quisque venenatis placerat est, sed pulvinar dui cursus ut. Curabitur rutrum odio sit amet est bibendum maximus. Nulla euismod finibus condimentum. Vestibulum finibus, felis nec malesuada accumsan, urna velit convallis ligula, ut pharetra lacus lorem elementum nunc. Donec ac massa eget massa auctor rhoncus at et nulla. Nam sollicitudin, mauris eget egestas pellentesque, leo eros tincidunt felis, in luctus metus tortor sit amet tortor. Mauris ut velit vitae ipsum pharetra consequat. Vivamus id magna quis orci congue fringilla. Vestibulum vitae mollis risus.

+

Phasellus eu mauris lacus. Fusce nec ante non ante condimentum ullamcorper. Phasellus condimentum massa vitae diam dignissim volutpat. Aenean ut dignissim felis. Suspendisse vitae sollicitudin est, vel dapibus elit. Nullam auctor dui et erat sagittis, quis imperdiet tellus consequat. Donec eu auctor nulla. Pellentesque sed leo lectus. Quisque molestie, urna non gravida vestibulum, nulla augue pellentesque neque, sed ultrices quam diam id metus.

+

Etiam nec suscipit nunc. Vestibulum in euismod neque, eu vehicula tellus. Praesent id suscipit nunc. Pellentesque non orci egestas, bibendum magna et, commodo lorem. Phasellus et convallis arcu, ac dapibus velit. Integer ac pellentesque nibh. Aenean erat magna, hendrerit bibendum fringilla a, maximus eu mi. Curabitur pulvinar pulvinar pellentesque. Duis in dapibus enim.

+

Integer vitae convallis velit. Integer ut turpis risus. Nunc non dui vitae est aliquam tempus nec ac nibh. Integer condimentum libero lorem, vitae ultrices neque commodo sit amet. Sed et lacinia sapien. Sed mattis magna eu nunc varius vehicula. Ut id velit vitae nunc aliquet iaculis. Proin enim turpis, ultricies vel vulputate in, pharetra at mauris. Duis non tincidunt augue.

\ No newline at end of file diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/docgen.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/docgen.stories.mdx index 61f7e9022f33..3bc0a9ceea0e 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/docgen.stories.mdx +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/docgen.stories.mdx @@ -2,7 +2,7 @@ import { Meta, Props, Description } from '@storybook/addon-docs/blocks'; import * as DocgenJS from './DocgenJS'; import * as DocgenTS from './DocgenTS'; - + export const DescriptionProps = ({ of }) => ( <> diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc-perfo.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc-perfo.stories.mdx index 960fb64c368d..b419ff21e35f 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc-perfo.stories.mdx +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc-perfo.stories.mdx @@ -1,10 +1,10 @@ import { Meta, Props } from '@storybook/addon-docs/blocks'; -import { ButtonTooManyProps } from "./jsdoc-perfo"; +import { ButtonTooManyProps } from './jsdoc-perfo'; - + ## Render 150 props with JSDoc tags - \ No newline at end of file + diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc.stories.mdx index 00a62562eb24..ea2d2ff7da86 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc.stories.mdx +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/jsdoc/jsdoc.stories.mdx @@ -1,9 +1,9 @@ import { Meta, Props } from '@storybook/addon-docs/blocks'; -import { JsDocProps, FailingJsDocProps } from "./jsdoc"; -import { TypeScriptProps } from "./jsdoc-ts"; +import { JsDocProps, FailingJsDocProps } from './jsdoc'; +import { TypeScriptProps } from './jsdoc-ts'; - + - \ No newline at end of file + diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ext.js b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ext.js new file mode 100644 index 000000000000..047b6b362bd3 --- /dev/null +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ext.js @@ -0,0 +1,13 @@ +// @ts-ignore +import PropTypes from 'prop-types'; + +export const PRESET_SHAPE = { + text: PropTypes.string.isRequired, + startDate: PropTypes.object.isRequired, + endDate: PropTypes.object.isRequired, +}; + +export const SOME_PROP_TYPES = { + ext1: PropTypes.string, + ext2: PropTypes.number, +}; diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/prop-types.js b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/prop-types.js index 79876bf5da73..a314f31bd6a1 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/prop-types.js +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/prop-types.js @@ -1,24 +1,499 @@ /* eslint-disable react/no-unused-prop-types */ import React from 'react'; -import PropTypes, { string } from 'prop-types'; +// @ts-ignore +import PropTypes, { string, shape } from 'prop-types'; +import momentPropTypes from 'react-moment-proptypes'; +import { PRESET_SHAPE, SOME_PROP_TYPES } from './ext'; -const ITEM_SHAPE = { +const NAMED_OBJECT = { text: PropTypes.string.isRequired, value: PropTypes.string.isRequired, }; +const ANOTHER_OBJECT = { + foo: PropTypes.string, + bar: PropTypes.string, +}; + +const NAMED_SHAPE = PropTypes.shape({ + foo: PropTypes.string, +}); + +export const POSITIONS = ['top-left', 'top-right', 'top-center']; + +const FunctionalComponent = () => { + return
FunctionnalComponent!
; +}; + +class ClassComponent extends React.PureComponent { + render() { + return
ClassComponent!
; + } +} + +function concat(a, b) { + return a + b; +} + +function customPropType() { + return null; +} + +const nestedCustomPropType = { + custom: customPropType, +}; + +const SOME_INLINE_PROP_TYPES = { + /** + * Hey Hey! + */ + inlineString: PropTypes.string, + inlineBool: PropTypes.bool, + inlineNumber: PropTypes.number, + inlineObj: PropTypes.shape({ + foo: PropTypes.string, + }), + inlineArray: PropTypes.arrayOf(PropTypes.number), + inlineArrayOfObjects: PropTypes.arrayOf({ foo: PropTypes.string }), + inlineFunctionalElement: PropTypes.element, + inlineFunctionalElementInline: PropTypes.element, + inlineFunctionalElementInlineReturningNull: PropTypes.element, + inlineHtmlElement: PropTypes.element, + inlineFunctionalElementInlineWithProps: PropTypes.element, + inlineFunctionalElementNamedInline: PropTypes.element, + inlineClassElement: PropTypes.element, + inlineClassElementWithProps: PropTypes.element, + inlineClassElementWithChildren: PropTypes.element, + inlineClassElementInline: PropTypes.element, + inlineFunc: PropTypes.func, +}; + +const SOME_INLINE_DEFAULT_PROPS = { + inlineString: 'Inline prop default value', + inlineBool: true, + inlineNumber: 10, + inlineObj: { foo: 'bar' }, + inlineArray: [1, 2, 3], + inlineArrayOfObjects: [ + { foo: 'bar' }, + { foo: 'bar' }, + { foo: 'bar' }, + { foo: 'bar' }, + { foo: 'bar' }, + ], + inlineFunctionalElement: , + inlineFunctionalElementInline: () => { + return
Inlined FunctionnalComponent!
; + }, + inlineFunctionalElementInlineReturningNull: () => { + return null; + }, + inlineHtmlElement:
Hey!
, + // eslint-disable-next-line react/prop-types + inlineFunctionalElementInlineWithProps: ({ foo }) => { + return
{foo}
; + }, + inlineFunctionalElementNamedInline: function InlinedFunctionalComponent() { + return
Inlined FunctionnalComponent!
; + }, + inlineClassElement: , + inlineClassElementWithProps: , + inlineClassElementWithChildren: ( + +
hey!
+
+ ), + inlineClassElementInline: class InlinedClassComponent extends React.PureComponent { + render() { + return
Inlined ClassComponent!
; + } + }, + inlineFunc: function add(a, b) { + return a + b; + }, +}; + export const PropTypesProps = () =>
PropTypes!
; PropTypesProps.propTypes = { - arrayOfPrimitive: PropTypes.arrayOf(PropTypes.string), - arrayOfShape: PropTypes.arrayOf(ITEM_SHAPE), - arrayOfInlineShape: PropTypes.arrayOf({ - text: string.isRequired, - value: string.isRequired, - }).isRequired, + // eslint-disable-next-line react/forbid-prop-types + any: PropTypes.any, + bool: PropTypes.bool, + string: PropTypes.string, + func: PropTypes.func, + /** + * A function with JSDoc tags. + * + * @param {string} foo - A foo value. + * @param {number} bar - A bar value. + * @returns {ComplexObject} - Returns a complex object. + */ + funcWithJsDoc: PropTypes.func, + /** + * @param {string} foo - A foo value. + * @param {number} bar - A bar value. + * @param {number} bar1 - A bar value. + * @param {number} bar2 - A bar value. + * @param {number} bar3 - A bar value. + * @param {number} bar4 - A bar value. + * @param {number} bar5 - A bar value. + * @returns {ComplexObject} - Returns a complex object. + */ + semiLongFuncWithJsDoc: PropTypes.func, + /** + * @param {string} foo - A foo value. + * @param {number} bar - A bar value. + * @param {number} bar1 - A bar value. + * @param {number} bar2 - A bar value. + * @param {number} bar3 - A bar value. + * @param {number} bar4 - A bar value. + * @param {number} bar5 - A bar value. + * @param {number} bar6 - A bar value. + * @param {number} bar7 - A bar value. + * @param {number} bar8 - A bar value. + * @param {number} bar9 - A bar value. + * @param {number} bar10 - A bar value. + * @returns {ComplexObject} - Returns a complex object. + */ + veryLongFuncWithJsDoc: PropTypes.func, + namedDefaultFunc: PropTypes.func, + number: PropTypes.number, + /** + * Plain object propType (use shape!!) + */ + obj: PropTypes.object, // eslint-disable-line react/forbid-prop-types + symbol: PropTypes.symbol, + node: PropTypes.node, + useCustomPropType: customPropType, + useNestedCustomPropType: nestedCustomPropType.custom, + externalMomentPropType: momentPropTypes.momentObj, + functionalElement: PropTypes.element, + functionalElementInline: PropTypes.element, + functionalElementNamedInline: PropTypes.element, + classElement: PropTypes.element, + classElementInline: PropTypes.element, + functionalElementType: PropTypes.elementType, + classElementType: PropTypes.elementType, + elementWithProps: PropTypes.elementType, + /** + * `instanceOf` is also supported and the custom type will be shown instead of `instanceOf` + */ + instanceOf: PropTypes.instanceOf(Set), + /** + * `oneOf` is basically an Enum which is also supported but can be pretty big. + */ + oneOfString: PropTypes.oneOf(['News', 'Photos']), + oneOfNumeric: PropTypes.oneOf([0, 1, 2, 3]), + oneOfShapes: PropTypes.oneOf([ + PropTypes.shape({ foo: PropTypes.string }), + PropTypes.shape({ bar: PropTypes.number }), + ]), + oneOfComplexShapes: PropTypes.oneOf([ + PropTypes.shape({ + /** + * Just an internal propType for a shape. + * It's also required, and as you can see it supports multi-line comments! + */ + id: PropTypes.number.isRequired, + /** + * A simple non-required function + */ + func: PropTypes.func, + /** + * An `arrayOf` shape + */ + arr: PropTypes.arrayOf( + PropTypes.shape({ + /** + * 5-level deep propType definition and still works. + */ + index: PropTypes.number.isRequired, + }) + ), + }), + shape({ bar: PropTypes.number }), + ]), + oneOfComplexType: PropTypes.oneOf([NAMED_OBJECT, ANOTHER_OBJECT]), + oneOfComponents: PropTypes.oneOf([FunctionalComponent, ClassComponent]), + oneOfEval: PropTypes.oneOf((() => ['News', 'Photos'])()), + oneOfVar: PropTypes.oneOf(POSITIONS), + oneOfNested: PropTypes.oneOf(['News', ['bottom-left', 'botton-center', 'bottom-right']]), + oneOfNestedSimpleInlineObject: PropTypes.oneOf(['News', [{ foo: PropTypes.string }]]), + oneOfNestedComplexInlineObject: PropTypes.oneOf([ + 'News', + [{ nested: { foo: PropTypes.string } }], + ]), + oneOfNestedComplexShape: PropTypes.oneOf([ + 'News', + [{ nested: PropTypes.shape({ foo: PropTypes.string }) }], + ]), + /** + * A multi-type prop is also valid and is displayed as `Union` + */ + oneOfType: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Set)]), + /** + * array of a primitive type + */ + arrayOfPrimitive: PropTypes.arrayOf(PropTypes.number), + arrayOfNamedObject: PropTypes.arrayOf(NAMED_OBJECT), + arrayOfShortInlineObject: PropTypes.arrayOf({ + foo: PropTypes.string, + }), + arrayOfInlineObject: PropTypes.arrayOf({ + text: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + }), + arrayOfComplexInlineObject: PropTypes.arrayOf({ + text: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + shape: { + id: PropTypes.string.isRequired, + age: PropTypes.number.isRequired, + }, + }), + arrayOfShortShape: PropTypes.arrayOf( + PropTypes.shape({ + bar: PropTypes.string, + }) + ), + arrayOfComplexShape: PropTypes.arrayOf( + PropTypes.shape({ + /** + * Just an internal propType for a shape. + * It's also required, and as you can see it supports multi-line comments! + */ + id: PropTypes.number.isRequired, + /** + * A simple non-required function + */ + func: PropTypes.func, + /** + * An `arrayOf` shape + */ + arr: PropTypes.arrayOf( + PropTypes.shape({ + /** + * 5-level deep propType definition and still works. + */ + index: PropTypes.number.isRequired, + }) + ), + }) + ), + arrayExternalShape: PropTypes.arrayOf(PropTypes.shape(PRESET_SHAPE)), + /** + * A simple `objectOf` propType. + */ + simpleObjectOf: PropTypes.objectOf(PropTypes.number), + objectOfShortInlineObject: PropTypes.objectOf({ + foo: PropTypes.string, + }), + objectOfInlineObject: PropTypes.objectOf({ + foo: PropTypes.string, + bar: PropTypes.string, + barry: PropTypes.string, + }), + objectOfShortShape: PropTypes.objectOf( + PropTypes.shape({ + foo: string, + }) + ), + /** + * A very complex `objectOf` propType. + */ + objectOfComplexShape: PropTypes.objectOf( + PropTypes.shape({ + /** + * Just an internal propType for a shape. + * It's also required, and as you can see it supports multi-line comments! + */ + id: PropTypes.number.isRequired, + /** + * A simple non-required function + */ + func: PropTypes.func, + /** + * An `arrayOf` shape + */ + arr: PropTypes.arrayOf( + PropTypes.shape({ + /** + * 5-level deep propType definition and still works. + */ + index: PropTypes.number.isRequired, + }) + ), + }) + ), + namedObjectOf: PropTypes.objectOf(NAMED_OBJECT), + shapeShort: PropTypes.shape({ + foo: string, + }), + shapeLong: PropTypes.shape({ + foo: string, + prop1: string, + prop2: string, + prop3: string, + prop4: string, + prop5: string, + prop6: string, + prop7: string, + }), + /** + * propType for shape with nested arrayOf + * + * Also, multi-line description + */ + shapeComplex: PropTypes.shape({ + /** + * Just an internal propType for a shape. + * It's also required, and as you can see it supports multi-line comments! + */ + id: PropTypes.number.isRequired, + /** + * A simple non-required function + */ + func: PropTypes.func, + /** + * An `arrayOf` shape + */ + arr: PropTypes.arrayOf( + PropTypes.shape({ + /** + * 5-level deep propType definition and still works. + */ + index: PropTypes.number.isRequired, + }) + ), + shape: PropTypes.shape({ + shape: PropTypes.shape({ + foo: PropTypes.string, + oneOf: PropTypes.oneOf(['one', 'two']), + }), + }), + oneOf: PropTypes.oneOf(['one', 'two']), + }), + shapeWithArray: PropTypes.shape({ + arr: PropTypes.arrayOf({ foo: PropTypes.string }), + }), + namedShape: NAMED_SHAPE, + namedObjectInShape: PropTypes.shape(NAMED_OBJECT), + exact: PropTypes.exact({ + name: PropTypes.string, + quantity: PropTypes.number, + }), + namedExact: PropTypes.exact(NAMED_OBJECT), + /** + * test string with a comment that has + * two identical lines + * two identical lines + */ + optionalString: PropTypes.string, + requiredString: PropTypes.string.isRequired, + nullDefaultValue: PropTypes.string, + undefinedDefaultValue: PropTypes.string, + ...SOME_INLINE_PROP_TYPES, + ...SOME_PROP_TYPES, }; PropTypesProps.defaultProps = { - arrayOfPrimitive: ['foo', 'bar'], - arrayOfShape: [{ text: 'foo', value: 'bar' }], + any: 'Default any', + bool: false, + string: 'Default string', + func: () => {}, + funcWithJsDoc: (foo, bar) => { + // eslint-disable-next-line + const yo = window.document; + // eslint-disable-next-line + const pouf = souffle; + + return { foo, bar }; + }, + namedDefaultFunc: concat, + number: 5, + obj: { + key: 'value', + }, + symbol: Symbol('Default symbol'), + node:
Hello!
, + functionalElement: , + functionalElementInline: () => { + return
Inlined FunctionnalComponent!
; + }, + functionalElementNamedInline: function InlinedFunctionalComponent() { + return
Inlined FunctionnalComponent!
; + }, + classElement: , + classElementInline: class InlinedClassComponent extends React.PureComponent { + render() { + return
Inlined ClassComponent!
; + } + }, + functionalElementType: FunctionalComponent, + classElementType: ClassComponent, + elementWithProps: , + instanceOf: new Set(), + oneOfString: 'News', + oneOfNumeric: 1, + oneOfShapes: { foo: 'bar' }, + oneOfComplexShapes: { + thing: { + id: 2, + func: () => {}, + arr: [], + }, + }, + oneOfComplexType: { text: 'foo', value: 'bar' }, + oneOfComponents: , + oneOfEval: 'Photos', + oneOfVar: 'top-right', + oneOfNested: 'top-right', + oneOfType: 'hello', + arrayOfPrimitive: [1, 2, 3], + arrayOfString: ['0px', '0px'], + arrayOfNamedObject: [{ text: 'foo', value: 'bar' }], + arrayOfShortInlineObject: [{ foo: 'bar' }], + arrayOfInlineObject: [{ text: 'foo', value: 'bar' }], + arrayOfComplexInlineObject: [{ text: 'foo', value: 'bar' }], + arrayOfShortShape: [{ bar: 'foo' }], + arrayOfComplexShape: [ + { + thing: { + id: 2, + func: () => {}, + arr: [], + }, + }, + ], + simpleObjectOf: { key: 1 }, + objectOfShortInlineObject: { foo: 'bar' }, + objectOfInlineObject: { foo: 'bar', bar: 'foo' }, + objectOfShortShape: { foo: 'bar' }, + objectOfComplexShape: { + thing: { + id: 2, + func: () => {}, + arr: [], + }, + }, + namedObjectOf: { text: 'foo', value: 'bar' }, + shapeShort: { foo: 'bar' }, + shapeComplex: { + id: 3, + func: () => {}, + arr: [], + shape: { + shape: { + foo: 'bar', + }, + }, + }, + namedShape: { foo: 'bar' }, + namedObjectInShape: { text: 'foo', value: 'bar' }, + exact: { name: 'foo', quantity: 2 }, + namedExact: { text: 'foo', value: 'bar' }, + optionalString: 'Default String', + nullDefaultValue: null, + undefinedDefaultValue: undefined, + ...SOME_INLINE_DEFAULT_PROPS, }; diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ts-types.tsx b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ts-types.tsx index 75b6df11da07..ca1193f17044 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ts-types.tsx +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/ts-types.tsx @@ -1,17 +1,132 @@ import React, { FC } from 'react'; -interface ItemShape { +function concat(a: string, b: string): string { + return a + b; +} + +interface ItemInterface { text: string; value: string; } +interface PersonInterface { + name: string; +} + +type InterfaceIntersection = ItemInterface & PersonInterface; + +interface GenericInterface { + value: T; +} + +enum DefaultEnum { + TopLeft, + TopRight, + TopCenter, +} + +enum NumericEnum { + TopLeft = 0, + TopRight, + TopCenter, +} + +enum StringEnum { + TopLeft = 'top-left', + TopRight = 'top-right', + TopCenter = 'top-center', +} + +type EnumUnion = DefaultEnum | NumericEnum; + +type StringLiteralUnion = 'top-left' | 'top-right' | 'top-center'; +type NumericLiteralUnion = 0 | 1 | 2; + +type StringAlias = string; +type NumberAlias = number; +type AliasesIntersection = StringAlias & NumberAlias; +type AliasesUnion = StringAlias | NumberAlias; +type GenericAlias = { value: T }; + interface TypeScriptPropsProps { + any: any; + string: string; + bool: boolean; + number: number; + voidFunc: () => void; + funcWithArgsAndReturns: (a: string, b: string) => string; + funcWithunionArg: (a: string | number) => string; + funcWithMultipleUnionReturns: () => string | ItemInterface; + funcWithIndexTypes: (o: T, propertyNames: K[]) => T[K][]; + symbol: symbol; + interface: ItemInterface; + genericInterface: GenericInterface; arrayOfPrimitive: string[]; - arrayOfShape: ItemShape[]; + arrayOfComplexObject: ItemInterface[]; + tupleOfPrimitive: [string, number]; + tupleWithComplexType: [string, ItemInterface]; + defaultEnum: DefaultEnum; + numericEnum: NumericEnum; + stringEnum: StringEnum; + enumUnion: EnumUnion; + recordOfPrimitive: Record; + recordOfComplexObject: Record; + intersectionType: InterfaceIntersection; + intersectionWithInlineType: ItemInterface & { inlineValue: string }; + unionOfPrimitive: string | number; + unionOfComplexType: ItemInterface | InterfaceIntersection; + nullablePrimitve?: string; + nullableComplexType?: ItemInterface; + nullableComplexTypeUndefinedDefaultValue?: ItemInterface; + readonly readonlyPrimitive: string; + typeAlias: StringAlias; + aliasesIntersection: AliasesIntersection; + aliasesUnion: AliasesUnion; + genericAlias: GenericAlias; + namedStringLiteralUnion: StringLiteralUnion; + inlinedStringLiteralUnion: 'bottom-left' | 'bottom-right' | 'bottom-center'; + namedNumericLiteralUnion: NumericLiteralUnion; + inlinedNumericLiteralUnion: 0 | 1 | 2; } export const TypeScriptProps: FC = () =>
TypeScript!
; TypeScriptProps.defaultProps = { + any: 'Any value', + string: 'A string value', + bool: true, + number: 5, + voidFunc: () => {}, + funcWithArgsAndReturns: concat, + symbol: Symbol('Default symbol'), + interface: { text: 'foo', value: 'bar' }, + genericInterface: { value: 'A string value' }, arrayOfPrimitive: ['foo', 'bar'], - arrayOfShape: [{ text: 'foo', value: 'bar' }], + arrayOfComplexObject: [{ text: 'foo', value: 'bar' }], + tupleOfPrimitive: ['string value', 5], + tupleWithComplexType: ['string value', { text: 'foo', value: 'bar' }], + defaultEnum: DefaultEnum.TopRight, + numericEnum: NumericEnum.TopRight, + stringEnum: StringEnum.TopRight, + enumUnion: DefaultEnum.TopLeft, + recordOfPrimitive: { foo: 1, bar: 2 }, + recordOfComplexObject: { foo: { text: 'bar', value: 'bar2' } }, + intersectionType: { text: 'foo', value: 'bar', name: 'foo-bar' }, + intersectionWithInlineType: { text: 'foo', value: 'bar', inlineValue: 'this is inlined' }, + unionOfPrimitive: 'A string value', + unionOfComplexType: { text: 'foo', value: 'bar' }, + nullableComplexTypeUndefinedDefaultValue: undefined, + typeAlias: 'foo', + aliasesUnion: 'foo', + genericAlias: { value: 'foo' }, + namedStringLiteralUnion: 'top-right', + inlinedStringLiteralUnion: 'bottom-right', + namedNumericLiteralUnion: 0, + inlinedNumericLiteralUnion: 1, }; + +interface TypeScriptHtmlComponentProps { + text: string; +} + +export const TypeScriptHtmlComponent: FC & + TypeScriptHtmlComponentProps> = () =>
My HTML component
; diff --git a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/types.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/types.stories.mdx index 50540fdc5293..ac98de4a291a 100644 --- a/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/types.stories.mdx +++ b/examples/cra-ts-kitchen-sink/src/stories/docgen-tests/types/types.stories.mdx @@ -1,8 +1,8 @@ import { Meta, Props } from '@storybook/addon-docs/blocks'; -import { PropTypesProps } from "./prop-types"; -import { TypeScriptProps } from "./ts-types"; +import { PropTypesProps } from './prop-types'; +import { TypeScriptProps, TypeScriptHtmlComponent } from './ts-types'; - + ## Prop Types @@ -10,4 +10,6 @@ import { TypeScriptProps } from "./ts-types"; ## TypeScript - \ No newline at end of file + + + \ No newline at end of file diff --git a/examples/cra-ts-kitchen-sink/src/stories/props-sort.stories.mdx b/examples/cra-ts-kitchen-sink/src/stories/props-sort.stories.mdx new file mode 100644 index 000000000000..5441e4f1f39b --- /dev/null +++ b/examples/cra-ts-kitchen-sink/src/stories/props-sort.stories.mdx @@ -0,0 +1,7 @@ +import { PropsSort } from "./PropsSort"; +import { Props, Meta } from '@storybook/addon-docs/blocks'; + + + + + \ No newline at end of file diff --git a/examples/dev-kits/addons.js b/examples/dev-kits/addons.js deleted file mode 100644 index 25fb6ff0e5d6..000000000000 --- a/examples/dev-kits/addons.js +++ /dev/null @@ -1,10 +0,0 @@ -import '@storybook/addon-roundtrip/register'; -import '@storybook/addon-parameter/register'; -import '@storybook/addon-preview-wrapper/register'; - -import { addons } from '@storybook/addons'; -import { themes } from '@storybook/theming'; - -addons.setConfig({ - theme: themes.dark, -}); diff --git a/examples/dev-kits/config.js b/examples/dev-kits/config.js deleted file mode 100644 index 51d216a42df9..000000000000 --- a/examples/dev-kits/config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { configure } from '@storybook/react'; - -// The simplest version of examples would export this function for users to use -function importAll(context) { - context.keys().forEach(filename => context(filename)); -} - -function loadStories() { - importAll(require.context('./stories', true, /\.js$/)); -} - -configure(loadStories, module); diff --git a/examples/dev-kits/main.js b/examples/dev-kits/main.js new file mode 100644 index 000000000000..d4012dcbfbc7 --- /dev/null +++ b/examples/dev-kits/main.js @@ -0,0 +1,39 @@ +module.exports = { + stories: ['./stories/*.*'], + webpack: async (config, { configType }) => ({ + ...config, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /\.(ts|tsx)$/, + loader: require.resolve('babel-loader'), + options: { + presets: [['react-app', { flow: false, typescript: true }]], + }, + }, + ], + }, + resolve: { + ...config.resolve, + extensions: [...(config.resolve.extensions || []), '.ts', '.tsx'], + }, + }), + managerWebpack: async config => ({ + ...config, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /manager\.js$/, + loader: require.resolve('babel-loader'), + options: { + presets: [['react-app', { flow: false, typescript: true }]], + }, + }, + ], + }, + }), +}; diff --git a/examples/dev-kits/manager.js b/examples/dev-kits/manager.js new file mode 100644 index 000000000000..027b5032041d --- /dev/null +++ b/examples/dev-kits/manager.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Button } from '@storybook/react/demo'; +import { addons } from '@storybook/addons'; +import { useAddonState, useStoryState } from '@storybook/api'; +import { themes } from '@storybook/theming'; +import { AddonPanel } from '@storybook/components'; + +addons.setConfig({ + theme: themes.dark, + panelPosition: 'bottom', + selectedPanel: 'storybook/roundtrip', +}); + +const StatePanel = ({ active, key }) => { + const [managerState, setManagerState] = useAddonState('manager', 10); + const [previewState, setPreviewState] = useAddonState('preview'); + const [storyState, setstoryState] = useStoryState(10); + return ( + +
+ Manager counter: {managerState} +
+ + +
+
+
+ Preview counter: {previewState} +
+ + +
+
+
+ Story counter: {storyState} +
+ + +
+
+ ); +}; + +StatePanel.propTypes = { + active: PropTypes.bool.isRequired, + key: PropTypes.string.isRequired, +}; + +addons.addPanel('useAddonState', { + id: 'useAddonState', + title: 'useAddonState', + render: StatePanel, +}); diff --git a/examples/dev-kits/package.json b/examples/dev-kits/package.json index a83195f3162c..9478636e2ede 100644 --- a/examples/dev-kits/package.json +++ b/examples/dev-kits/package.json @@ -1,22 +1,25 @@ { "name": "@storybook/example-devkits", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { - "build-storybook": "build-storybook -c ./", - "storybook": "start-storybook -p 9011 -c ./" + "build-storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true build-storybook -c ./", + "debug": "cross-env NODE_OPTIONS=--inspect-brk STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll", + "storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll" }, "devDependencies": { - "@storybook/addon-decorator": "5.3.0-alpha.41", - "@storybook/addon-parameter": "5.3.0-alpha.41", - "@storybook/addon-preview-wrapper": "5.3.0-alpha.41", - "@storybook/addon-roundtrip": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/node-logger": "5.3.0-alpha.41", - "@storybook/react": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addon-decorator": "6.0.0-alpha.2", + "@storybook/addon-parameter": "6.0.0-alpha.2", + "@storybook/addon-preview-wrapper": "6.0.0-alpha.2", + "@storybook/addon-roundtrip": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/node-logger": "6.0.0-alpha.2", + "@storybook/react": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "cors": "^2.8.5", "cross-env": "^6.0.3", "enzyme-to-json": "^3.4.1", diff --git a/examples/dev-kits/stories/addon-useaddonstate.tsx b/examples/dev-kits/stories/addon-useaddonstate.tsx new file mode 100644 index 000000000000..dbad9aa0193b --- /dev/null +++ b/examples/dev-kits/stories/addon-useaddonstate.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Button } from '@storybook/react/demo'; +import { useAddonState } from '@storybook/client-api'; + +export default { + title: 'addons|useAddonState', +}; + +export const managerDefault = () => { + const [state, setState] = useAddonState('manager'); + + return ( +
+ Manager counter: {state} +
+ + +
+ ); +}; + +export const previewDefault = () => { + const [state, setState] = useAddonState('preview', 50); + + return ( +
+ Preview counter: {state} +
+ + +
+ ); +}; diff --git a/examples/dev-kits/stories/addon-usestorystate.tsx b/examples/dev-kits/stories/addon-usestorystate.tsx new file mode 100644 index 000000000000..ded51bae63e5 --- /dev/null +++ b/examples/dev-kits/stories/addon-usestorystate.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Button } from '@storybook/react/demo'; +import { useStoryState } from '@storybook/client-api'; + +export default { + title: 'addons|useAddonState', +}; + +export const storyState1 = () => { + const [state, setState] = useStoryState(10); + + return ( +
+ Story counter: {state} +
+ + +
+ ); +}; + +export const storyState2 = () => { + const [state, setState] = useStoryState(10); + + return ( +
+ Story counter: {state} +
+ + +
+ ); +}; diff --git a/examples/ember-cli/.eslintrc.js b/examples/ember-cli/.eslintrc.js index 06c6cb67bdec..45b9d104adb4 100644 --- a/examples/ember-cli/.eslintrc.js +++ b/examples/ember-cli/.eslintrc.js @@ -8,7 +8,6 @@ module.exports = { '@ember/routing/router', '@ember/application', './config/environment', - 'htmlbars-inline-precompile', ], }, }; diff --git a/examples/ember-cli/.storybook/addons.js b/examples/ember-cli/.storybook/addons.js deleted file mode 100644 index aad8e9120d3d..000000000000 --- a/examples/ember-cli/.storybook/addons.js +++ /dev/null @@ -1,9 +0,0 @@ -import '@storybook/addon-a11y/register'; -import '@storybook/addon-storysource/register'; -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-notes/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-viewport/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-backgrounds/register'; diff --git a/examples/ember-cli/.storybook/config.js b/examples/ember-cli/.storybook/config.js deleted file mode 100644 index f3f05b316dc4..000000000000 --- a/examples/ember-cli/.storybook/config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/ember'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - hierarchySeparator: /\/|\./, - hierarchyRootSeparator: /\|/, - }, -}); - -configure(require.context('../stories', true, /\.stories\.js$/), module); diff --git a/examples/ember-cli/.storybook/main.js b/examples/ember-cli/.storybook/main.js new file mode 100644 index 000000000000..989dc9ee719b --- /dev/null +++ b/examples/ember-cli/.storybook/main.js @@ -0,0 +1,25 @@ +const path = require('path'); + +module.exports = { + addons: [ + '@storybook/addon-a11y', + '@storybook/addon-storysource', + '@storybook/addon-actions', + '@storybook/addon-docs', + '@storybook/addon-links', + '@storybook/addon-knobs', + '@storybook/addon-viewport', + '@storybook/addon-options', + '@storybook/addon-backgrounds', + ], + stories: ['../stories/**/*.stories.js'], + webpackFinal: async config => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/source-loader')], + include: [path.resolve(__dirname, '../')], + enforce: 'pre', + }); + return config; + }, +}; diff --git a/examples/ember-cli/.storybook/preset.js b/examples/ember-cli/.storybook/preset.js new file mode 100644 index 000000000000..a04174c7a331 --- /dev/null +++ b/examples/ember-cli/.storybook/preset.js @@ -0,0 +1 @@ +module.exports = ['@storybook/addon-docs/preset']; diff --git a/examples/ember-cli/.storybook/preview.js b/examples/ember-cli/.storybook/preview.js new file mode 100644 index 000000000000..0bf5e7f8dcfc --- /dev/null +++ b/examples/ember-cli/.storybook/preview.js @@ -0,0 +1,13 @@ +import { addParameters, addDecorator } from '@storybook/ember'; +import { setJSONDoc } from '@storybook/addon-docs/ember'; +import { withA11y } from '@storybook/addon-a11y'; +// eslint-disable-next-line import/no-unresolved +import docJson from '../ember-output/storybook-docgen/index.json'; + +setJSONDoc(docJson); +addDecorator(withA11y); +addParameters({ + options: { + showRoots: true, + }, +}); diff --git a/examples/ember-cli/.storybook/webpack.config.js b/examples/ember-cli/.storybook/webpack.config.js deleted file mode 100644 index d382a097d811..000000000000 --- a/examples/ember-cli/.storybook/webpack.config.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); - -module.exports = async ({ config }) => { - config.module.rules.push({ - test: [/\.stories\.js$/, /index\.js$/], - loaders: [require.resolve('@storybook/source-loader')], - include: [path.resolve(__dirname, '../')], - enforce: 'pre', - }); - return config; -}; diff --git a/examples/ember-cli/app/components/welcome-banner.js b/examples/ember-cli/app/components/welcome-banner.js index 55706477346c..5b4bb4e0bb19 100644 --- a/examples/ember-cli/app/components/welcome-banner.js +++ b/examples/ember-cli/app/components/welcome-banner.js @@ -1,3 +1,49 @@ import Component from '@ember/component'; -export default Component.extend({}); +/** + * + * `WelcomeBanner` renders a friendly message and is used to welcome Ember.js users when they first generate an application. + * + * + * ```js + * {{welcome-banner + * backgroundColor=backgroundColor + * titleColor=titleColor + * subTitleColor=subTitleColor + * title=title + * subtitle=subtitle + * click=(action onClick) + * }} + * ``` + * + * @class WelcomeBanner + */ +export default Component.extend({ + /** + * The hex-formatted color code for the background. + * @argument backgroundColor + * @type {string} + */ + backgroundColor: null, + + /** + * The hex-formatted color code for the subtitle. + * @argument subTitleColor + * @type {string} + */ + subTitleColor: null, + + /** + * The title of the banner. + * @argument title + * @type {string} + */ + title: null, + + /** + * The subtitle of the banner. + * @argument subtitle + * @type {string} + */ + subtitle: null, +}); diff --git a/examples/ember-cli/config/optional-features.json b/examples/ember-cli/config/optional-features.json new file mode 100644 index 000000000000..dd50c7cd3a3d --- /dev/null +++ b/examples/ember-cli/config/optional-features.json @@ -0,0 +1,5 @@ +{ + "application-template-wrapper": false, + "jquery-integration": false, + "template-only-glimmer-components": true +} \ No newline at end of file diff --git a/examples/ember-cli/ember-cli-build.js b/examples/ember-cli/ember-cli-build.js index 78a64bb77d1b..de9188ada6c6 100644 --- a/examples/ember-cli/ember-cli-build.js +++ b/examples/ember-cli/ember-cli-build.js @@ -2,7 +2,9 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); module.exports = function build(defaults) { const app = new EmberApp(defaults, { - // Add options here + 'ember-cli-storybook': { + enableAddonDocsIntegration: true, + }, }); // Use `app.import` to add additional libraries to the generated diff --git a/examples/ember-cli/package.json b/examples/ember-cli/package.json index 81eff3eca77e..8cd7a57a410d 100644 --- a/examples/ember-cli/package.json +++ b/examples/ember-cli/package.json @@ -1,50 +1,50 @@ { "name": "ember-example", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { - "build": "ember build", - "prebuild-storybook": "yarn build && shx cp -r public/* dist", - "build-storybook": "build-storybook -s dist", + "build": "ember build --output-path ember-output", + "prebuild-storybook": "yarn build && shx cp -r public/* ember-output", + "build-storybook": "build-storybook -s ember-output", "dev": "ember serve", - "storybook": "yarn build && start-storybook -p 9009 -s dist", - "storybook:dev": "yarn dev & start-storybook -p 9009 -s dist" + "storybook": "yarn build && start-storybook -p 9009 -s ember-output", + "storybook:dev": "yarn dev & start-storybook -p 9009 -s ember-output" }, "dependencies": { "ember-template-compiler": "^1.9.0-alpha" }, "devDependencies": { "@babel/core": "^7.3.4", - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addon-viewport": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/ember": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", + "@ember/optional-features": "^1.3.0", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addon-viewport": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/ember": "6.0.0-alpha.2", + "@storybook/ember-cli-storybook": "^0.2.0", + "@storybook/source-loader": "6.0.0-alpha.2", "babel-loader": "^8", "broccoli-asset-rev": "^3.0.0", "cross-env": "^6.0.3", - "ember-ajax": "^4.0.2", - "ember-cli": "~3.11.0", - "ember-cli-app-version": "^3.0.0", - "ember-cli-babel": "^7.6.0", - "ember-cli-htmlbars": "^3.0.1", - "ember-cli-htmlbars-inline-precompile": "^2.1.0", + "ember-ajax": "^5.0.0", + "ember-cli": "~3.15.2", + "ember-cli-app-version": "^3.2.0", + "ember-cli-babel": "^7.13.2", + "ember-cli-htmlbars": "^4.2.2", "ember-cli-inject-live-reload": "^2.0.1", "ember-cli-shims": "^1.2.0", "ember-cli-sri": "^2.1.0", - "ember-cli-storybook": "^0.1.0", - "ember-cli-uglify": "^2.0.0", + "ember-cli-uglify": "^3.0.0", "ember-load-initializers": "^2.0.0", - "ember-resolver": "^5.1.3", - "ember-source": "~3.9.1", + "ember-resolver": "^7.0.0", + "ember-source": "~3.15.0", "loader.js": "^4.2.3", "webpack": "^4.33.0", "webpack-cli": "^3.3.0" diff --git a/examples/ember-cli/stories/addon-a11y.stories.js b/examples/ember-cli/stories/addon-a11y.stories.js index 66ca80ec35af..baaf1cd8e04c 100644 --- a/examples/ember-cli/stories/addon-a11y.stories.js +++ b/examples/ember-cli/stories/addon-a11y.stories.js @@ -1,8 +1,8 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import { checkA11y } from '@storybook/addon-a11y'; export default { - title: 'Addon|a11y', + title: 'Addon/a11y', decorators: [checkA11y], parameters: { @@ -14,9 +14,9 @@ export const Default = () => hbs``; export const Label = () => hbs``; export const Disabled = () => hbs``; -export const invalidContrast = () => +export const InvalidContrast = () => hbs``; -invalidContrast.story = { +InvalidContrast.story = { name: 'Invalid contrast', }; diff --git a/examples/ember-cli/stories/addon-actions.stories.js b/examples/ember-cli/stories/addon-actions.stories.js index e11f8c8201b7..4151467114ab 100644 --- a/examples/ember-cli/stories/addon-actions.stories.js +++ b/examples/ember-cli/stories/addon-actions.stories.js @@ -1,8 +1,8 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import { action } from '@storybook/addon-actions'; export default { - title: 'Addon|Actions', + title: 'Addon/Actions', parameters: { options: { @@ -11,7 +11,7 @@ export default { }, }; -export const button = () => ({ +export const Button = () => ({ template: hbs``, context: { onClick: action('clicked'), diff --git a/examples/ember-cli/stories/addon-backgrounds.stories.js b/examples/ember-cli/stories/addon-backgrounds.stories.js index ea282fa81c5c..5d822e2ed7a4 100644 --- a/examples/ember-cli/stories/addon-backgrounds.stories.js +++ b/examples/ember-cli/stories/addon-backgrounds.stories.js @@ -1,7 +1,7 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; export default { - title: 'Addon|Backgrounds', + title: 'Addon/Backgrounds', parameters: { backgrounds: [ @@ -11,18 +11,18 @@ export default { }, }; -export const story1 = () => ({ +export const Story1 = () => ({ template: hbs``, }); -story1.story = { +Story1.story = { name: 'story 1', }; -export const story2 = () => ({ +export const Story2 = () => ({ template: hbs``, }); -story2.story = { +Story2.story = { name: 'story 2', }; diff --git a/examples/ember-cli/stories/addon-centered.stories.js b/examples/ember-cli/stories/addon-centered.stories.js index 3cdd11dee89d..8c5edff78e96 100644 --- a/examples/ember-cli/stories/addon-centered.stories.js +++ b/examples/ember-cli/stories/addon-centered.stories.js @@ -1,8 +1,8 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import Centered from '@storybook/addon-centered/ember'; export default { - title: 'Addon|Centered', + title: 'Addon/Centered', decorators: [Centered], parameters: { @@ -10,6 +10,6 @@ export default { }, }; -export const button = () => ({ +export const Button = () => ({ template: hbs``, }); diff --git a/examples/ember-cli/stories/addon-knobs.stories.js b/examples/ember-cli/stories/addon-knobs.stories.js index f0fcbb19c4d4..662974eb20a3 100644 --- a/examples/ember-cli/stories/addon-knobs.stories.js +++ b/examples/ember-cli/stories/addon-knobs.stories.js @@ -1,9 +1,9 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import { withKnobs, text, color, boolean } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; export default { - title: 'Addon|Knobs', + title: 'Addon/Knobs', decorators: [withKnobs], parameters: { @@ -11,7 +11,7 @@ export default { }, }; -export const withText = () => ({ +export const WithText = () => ({ template: hbs` {{welcome-banner style=(if hidden "display: none") @@ -34,6 +34,6 @@ export const withText = () => ({ }, }); -withText.story = { +WithText.story = { name: 'with text', }; diff --git a/examples/ember-cli/stories/addon-links.stories.js b/examples/ember-cli/stories/addon-links.stories.js index 6e1e16610e22..65135dca4af8 100644 --- a/examples/ember-cli/stories/addon-links.stories.js +++ b/examples/ember-cli/stories/addon-links.stories.js @@ -1,17 +1,17 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import { linkTo } from '@storybook/addon-links'; export default { - title: 'Addon|Links', + title: 'Addon/Links', }; -export const goToWelcome = () => ({ +export const GoToWelcome = () => ({ template: hbs``, context: { onClick: linkTo('Welcome'), }, }); -goToWelcome.story = { +GoToWelcome.story = { name: 'Go to welcome', }; diff --git a/examples/ember-cli/stories/addon-notes.stories.js b/examples/ember-cli/stories/addon-notes.stories.js deleted file mode 100644 index ac437cc62135..000000000000 --- a/examples/ember-cli/stories/addon-notes.stories.js +++ /dev/null @@ -1,32 +0,0 @@ -import hbs from 'htmlbars-inline-precompile'; - -export default { - title: 'Addon|Notes', -}; - -export const simpleNote = () => ({ - template: hbs`

Etiam vulputate elit eu venenatis eleifend. Duis nec lectus augue. Morbi egestas diam sed vulputate mollis. Fusce egestas pretium vehicula. Integer sed neque diam. Donec consectetur velit vitae enim varius, ut placerat arcu imperdiet. Praesent sed faucibus arcu. Nullam sit amet nibh a enim eleifend rhoncus. Donec pretium elementum leo at fermentum. Nulla sollicitudin, mauris quis semper tempus, sem metus tristique diam, efficitur pulvinar mi urna id urna.

`, -}); - -simpleNote.story = { - name: 'Simple note', - parameters: { notes: 'My notes on some bold text' }, -}; - -export const noteWithHtml = () => ({ - template: hbs`

🤔😳😯😮
😄😩😓😱
🤓😑😶😊

`, -}); - -noteWithHtml.story = { - name: 'Note with HTML', - - parameters: { - notes: ` -

My notes on emojies

- - It's not all that important to be honest, but.. - - Emojis are great, I love emojis, in fact I like using them in my Component notes too! 😇 - `, - }, -}; diff --git a/examples/ember-cli/stories/index.stories.js b/examples/ember-cli/stories/index.stories.js index 4c194a8a2dde..f2b461b43eac 100644 --- a/examples/ember-cli/stories/index.stories.js +++ b/examples/ember-cli/stories/index.stories.js @@ -1,4 +1,4 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; export default { title: 'Welcome', @@ -8,7 +8,7 @@ export default { }, }; -export const basic = () => ({ +export const Basic = () => ({ template: hbs` {{welcome-page}} `, diff --git a/examples/ember-cli/stories/welcome-banner.stories.js b/examples/ember-cli/stories/welcome-banner.stories.js index 3a914a80b414..eef06b1ae50f 100644 --- a/examples/ember-cli/stories/welcome-banner.stories.js +++ b/examples/ember-cli/stories/welcome-banner.stories.js @@ -1,11 +1,12 @@ -import hbs from 'htmlbars-inline-precompile'; +import { hbs } from 'ember-cli-htmlbars'; import { action } from '@storybook/addon-actions'; export default { title: 'welcome-banner', + component: 'WelcomeBanner', }; -export const basic = () => ({ +export const Basic = () => ({ template: hbs` {{welcome-banner backgroundColor=backgroundColor diff --git a/examples/html-kitchen-sink/.storybook/addons.js b/examples/html-kitchen-sink/.storybook/addons.js deleted file mode 100644 index c38a31c6ea86..000000000000 --- a/examples/html-kitchen-sink/.storybook/addons.js +++ /dev/null @@ -1,11 +0,0 @@ -import '@storybook/addon-a11y/register'; -import '@storybook/addon-actions/register'; -import '@storybook/addon-backgrounds/register'; -import '@storybook/addon-events/register'; -import '@storybook/addon-jest/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-notes/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-storysource/register'; -import '@storybook/addon-viewport/register'; diff --git a/examples/html-kitchen-sink/.storybook/main.js b/examples/html-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..042b87f86daf --- /dev/null +++ b/examples/html-kitchen-sink/.storybook/main.js @@ -0,0 +1,17 @@ +module.exports = { + // this dirname is because we run tests from project root + stories: [`${__dirname}/../stories/*.stories.*`], + addons: [ + '@storybook/addon-docs', + '@storybook/addon-a11y', + '@storybook/addon-actions', + '@storybook/addon-backgrounds', + '@storybook/addon-events', + '@storybook/addon-jest', + '@storybook/addon-knobs', + '@storybook/addon-links', + '@storybook/addon-options', + '@storybook/addon-storysource', + '@storybook/addon-viewport', + ], +}; diff --git a/examples/html-kitchen-sink/.storybook/presets.js b/examples/html-kitchen-sink/.storybook/presets.js deleted file mode 100644 index 7c4d1835bcfa..000000000000 --- a/examples/html-kitchen-sink/.storybook/presets.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['@storybook/addon-docs/html/preset']; diff --git a/examples/html-kitchen-sink/.storybook/config.js b/examples/html-kitchen-sink/.storybook/preview.js similarity index 61% rename from examples/html-kitchen-sink/.storybook/config.js rename to examples/html-kitchen-sink/.storybook/preview.js index 2dfefa732c76..60851dbe04b4 100644 --- a/examples/html-kitchen-sink/.storybook/config.js +++ b/examples/html-kitchen-sink/.storybook/preview.js @@ -1,4 +1,4 @@ -import { configure, addParameters, addDecorator } from '@storybook/html'; +import { addParameters, addDecorator } from '@storybook/html'; import { withA11y } from '@storybook/addon-a11y'; addDecorator(withA11y); @@ -12,11 +12,9 @@ addParameters({ }, }, options: { - hierarchyRootSeparator: /\|/, + showRoots: true, }, docs: { iframeHeight: '200px', }, }); - -configure(require.context('../stories', true, /\.stories\.(js|mdx)$/), module); diff --git a/examples/html-kitchen-sink/package.json b/examples/html-kitchen-sink/package.json index 951b3f2584e9..0c8391b6c76c 100644 --- a/examples/html-kitchen-sink/package.json +++ b/examples/html-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "html-kitchen-sink", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "description": "", "keywords": [], @@ -13,26 +13,25 @@ "storybook": "start-storybook -p 9006" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-docs": "5.3.0-alpha.41", - "@storybook/addon-events": "5.3.0-alpha.41", - "@storybook/addon-jest": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storyshots": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addon-viewport": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/client-api": "5.3.0-alpha.41", - "@storybook/core": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/html": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/addon-events": "6.0.0-alpha.2", + "@storybook/addon-jest": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addon-viewport": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/client-api": "6.0.0-alpha.2", + "@storybook/core": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/html": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", "eventemitter3": "^4.0.0", "format-json": "^1.0.3", "global": "^4.3.2", diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot index 29179f8340f5..d2f9a117a089 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot @@ -1,8 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|a11y Default 1`] = ` `; -exports[`Storyshots Addons|a11y Label 1`] = ` - -`; - -exports[`Storyshots Addons|a11y Story 4 1`] = ` +exports[`Storyshots Addons/a11y Invalid contrast 1`] = ` `; -exports[`Storyshots Addons|a11y Story 5 1`] = `
`; +exports[`Storyshots Addons/a11y Label 1`] = ` + +`; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot index e60f715f988e..262e351ec8f1 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Actions Story 1 1`] = ` +exports[`Storyshots Addons/Actions Decorated actions + config 1`] = ` `; -exports[`Storyshots Addons|Actions Story 2 1`] = ` +exports[`Storyshots Addons/Actions Decorated actions 1`] = ` `; -exports[`Storyshots Addons|Actions Story 3 1`] = ` +exports[`Storyshots Addons/Actions Hello World 1`] = ` `; -exports[`Storyshots Addons|Actions Story 4 1`] = ` +exports[`Storyshots Addons/Actions Multiple actions + config 1`] = ` `; -exports[`Storyshots Addons|Actions Story 5 1`] = ` - - -`; - -exports[`Storyshots Addons|Actions Story 6 1`] = ` +exports[`Storyshots Addons/Actions Multiple actions 1`] = ` `; -exports[`Storyshots Addons|Actions Story 7 1`] = ` +exports[`Storyshots Addons/Actions Multiple actions, object + config 1`] = ` `; -exports[`Storyshots Addons|Actions Story 8 1`] = ` +exports[`Storyshots Addons/Actions Multiple actions, object 1`] = ` `; + +exports[`Storyshots Addons/Actions Multiple actions, selector 1`] = ` + + +`; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot index c6683463f0e3..7a5e0008d7b6 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Backgrounds Story 1 1`] = ` +exports[`Storyshots Addons/Backgrounds story 1 1`] = ` @@ -8,7 +8,7 @@ exports[`Storyshots Addons|Backgrounds Story 1 1`] = ` `; -exports[`Storyshots Addons|Backgrounds Story 2 1`] = ` +exports[`Storyshots Addons/Backgrounds story 2 1`] = ` diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot index 1d660b4925c0..78639e13b3c3 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Centered Story 1 1`] = ` +exports[`Storyshots Addons/Centered button in center 1`] = `
Hello Button `; -exports[`Storyshots Addons|Docs Heading 1`] = ` +exports[`Storyshots Addons/Docs heading 1`] = `

Hello World

diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot index 6887b83c61bd..e87017fba576 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Events Logger 1`] = ` +exports[`Storyshots Addons/Events Logger 1`] = ` `; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot index 346b24a26337..437fd6b56c74 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Jest With Tests 1`] = `This story shows test results`; +exports[`Storyshots Addons/Jest With Tests 1`] = `This story shows test results`; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot index b0680c2196b7..3366b7f3e0bc 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot @@ -1,26 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots Addons|Knobs DOM 1`] = ` -

- John Doe -

-`; - -exports[`Storyshots Addons|Knobs Simple 1`] = ` -
- I am John Doe and I'm 44 years old. -
-`; - -exports[`Storyshots Addons|Knobs Story 3 1`] = ` -

- John Doe -

-`; - -exports[`Storyshots Addons|Knobs Story 4 1`] = ` +exports[`Storyshots Addons/Knobs All knobs 1`] = `
@@ -67,4 +47,24 @@ exports[`Storyshots Addons|Knobs Story 4 1`] = `
`; -exports[`Storyshots Addons|Knobs Story 5 1`] = `<img src=x onerror="alert('XSS Attack')" >`; +exports[`Storyshots Addons/Knobs CSS transitions 1`] = ` +

+ John Doe +

+`; + +exports[`Storyshots Addons/Knobs DOM 1`] = ` +

+ John Doe +

+`; + +exports[`Storyshots Addons/Knobs Simple 1`] = ` +
+ I am John Doe and I'm 44 years old. +
+`; + +exports[`Storyshots Addons/Knobs XSS safety 1`] = `<img src=x onerror="alert('XSS Attack')" >`; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot deleted file mode 100644 index 7267fb23a7fb..000000000000 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots Addons|Notes Story 1 1`] = ` -

- - - - - This is a fragment of HTML - - - - -

-`; diff --git a/examples/html-kitchen-sink/stories/__snapshots__/source-loader.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/source-loader.stories.storyshot new file mode 100644 index 000000000000..8ba05f3c3c96 --- /dev/null +++ b/examples/html-kitchen-sink/stories/__snapshots__/source-loader.stories.storyshot @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Addons/Source loader Button 1`] = ` + +`; + +exports[`Storyshots Addons/Source loader Very simple story 1`] = ` +

+ + + + + This is a fragment of HTML + + + + +

+`; diff --git a/examples/html-kitchen-sink/stories/addon-a11y.stories.js b/examples/html-kitchen-sink/stories/addon-a11y.stories.js index d34e5fa623c8..a811c0cee73f 100644 --- a/examples/html-kitchen-sink/stories/addon-a11y.stories.js +++ b/examples/html-kitchen-sink/stories/addon-a11y.stories.js @@ -4,7 +4,7 @@ import { withA11y } from '@storybook/addon-a11y'; const text = 'Testing the a11y addon'; export default { - title: 'Addons|a11y', + title: 'Addons/a11y', decorators: [withA11y], parameters: { options: { selectedPanel: 'storybook/a11y/panel' }, @@ -14,15 +14,15 @@ export default { export const Default = () => ``; export const Label = () => ``; export const Disabled = () => ``; -export const story4 = () => +export const Story4 = () => ``; -story4.story = { name: 'Invalid contrast' }; +Story4.story = { name: 'Invalid contrast' }; -export const story5 = () => { +export const Story5 = () => { const div = document.createElement('div'); setTimeout(() => { div.innerHTML = ``; }, 1000); return div; }; -story5.story = { name: 'Delayed render' }; +Story5.story = { name: 'Delayed render' }; diff --git a/examples/html-kitchen-sink/stories/addon-actions.stories.js b/examples/html-kitchen-sink/stories/addon-actions.stories.js index 82008bb1115e..40ec7cb6eba0 100644 --- a/examples/html-kitchen-sink/stories/addon-actions.stories.js +++ b/examples/html-kitchen-sink/stories/addon-actions.stories.js @@ -5,22 +5,22 @@ const pickTarget = decorate([args => [args[0].target]]); const button = () => ``; export default { - title: 'Addons|Actions', + title: 'Addons/Actions', }; -export const story1 = () => withActions('click')(button); -story1.story = { name: 'Hello World' }; -export const story2 = () => withActions('click', 'contextmenu')(button); -story2.story = { name: 'Multiple actions' }; +export const Story1 = () => withActions('click')(button); +Story1.story = { name: 'Hello World' }; +export const Story2 = () => withActions('click', 'contextmenu')(button); +Story2.story = { name: 'Multiple actions' }; -export const story3 = () => +export const Story3 = () => withActions('click', 'contextmenu', { clearOnStoryChange: false })(button); -story3.story = { name: 'Multiple actions + config' }; +Story3.story = { name: 'Multiple actions + config' }; -export const story4 = () => withActions({ click: 'clicked', contextmenu: 'right clicked' })(button); -story4.story = { name: 'Multiple actions, object' }; +export const Story4 = () => withActions({ click: 'clicked', contextmenu: 'right clicked' })(button); +Story4.story = { name: 'Multiple actions, object' }; -export const story5 = () => +export const Story5 = () => withActions({ 'click .btn': 'clicked', contextmenu: 'right clicked' })( () => `
@@ -28,17 +28,18 @@ export const story5 = () =>
` ); -story5.story = { name: 'Multiple actions, selector' }; +Story5.story = { name: 'Multiple actions, selector' }; -export const story6 = () => - withActions({ click: 'clicked', contextmenu: 'right clicked' }, { clearOnStoryChange: false })( - button - ); -story6.story = { name: 'Multiple actions, object + config' }; +export const Story6 = () => + withActions( + { click: 'clicked', contextmenu: 'right clicked' }, + { clearOnStoryChange: false } + )(button); +Story6.story = { name: 'Multiple actions, object + config' }; -export const story7 = () => pickTarget.withActions('click', 'contextmenu')(button); -story7.story = { name: 'Decorated actions' }; +export const Story7 = () => pickTarget.withActions('click', 'contextmenu')(button); +Story7.story = { name: 'Decorated actions' }; -export const story8 = () => +export const Story8 = () => pickTarget.withActions('click', 'contextmenu', { clearOnStoryChange: false })(button); -story8.story = { name: 'Decorated actions + config' }; +Story8.story = { name: 'Decorated actions + config' }; diff --git a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js index 4a63ab1fce60..fdfdb6b9954b 100644 --- a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js +++ b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js @@ -1,5 +1,5 @@ export default { - title: 'Addons|Backgrounds', + title: 'Addons/Backgrounds', parameters: { backgrounds: [ { name: 'light', value: '#eeeeee' }, @@ -8,9 +8,9 @@ export default { }, }; -export const story1 = () => +export const Story1 = () => 'You should be able to switch backgrounds for this story'; -story1.story = { name: 'story 1' }; +Story1.story = { name: 'story 1' }; -export const story2 = () => 'This one too!'; -story2.story = { name: 'story 2' }; +export const Story2 = () => 'This one too!'; +Story2.story = { name: 'story 2' }; diff --git a/examples/html-kitchen-sink/stories/addon-centered.stories.js b/examples/html-kitchen-sink/stories/addon-centered.stories.js index 8242764f7fee..611afd934335 100644 --- a/examples/html-kitchen-sink/stories/addon-centered.stories.js +++ b/examples/html-kitchen-sink/stories/addon-centered.stories.js @@ -1,9 +1,9 @@ import centered from '@storybook/addon-centered/html'; export default { - title: 'Addons|Centered', + title: 'Addons/Centered', decorators: [centered], }; -export const story1 = () => ''; -story1.story = { name: 'button in center' }; +export const Story1 = () => ''; +Story1.story = { name: 'button in center' }; diff --git a/examples/html-kitchen-sink/stories/addon-docs.stories.mdx b/examples/html-kitchen-sink/stories/addon-docs.stories.mdx index c7fa6458e679..adcf8cdbbdae 100644 --- a/examples/html-kitchen-sink/stories/addon-docs.stories.mdx +++ b/examples/html-kitchen-sink/stories/addon-docs.stories.mdx @@ -3,7 +3,7 @@ import { action } from '@storybook/addon-actions'; # Storybook Docs for HTML - + ## Story definition diff --git a/examples/html-kitchen-sink/stories/addon-events.stories.js b/examples/html-kitchen-sink/stories/addon-events.stories.js index 32c2bf2796cf..120e24fbf898 100644 --- a/examples/html-kitchen-sink/stories/addon-events.stories.js +++ b/examples/html-kitchen-sink/stories/addon-events.stories.js @@ -17,7 +17,7 @@ const emitter = new EventEmitter(); const emit = emitter.emit.bind(emitter); export default { - title: 'Addons|Events', + title: 'Addons/Events', decorators: [ withEvents({ emit, diff --git a/examples/html-kitchen-sink/stories/addon-jest.stories.js b/examples/html-kitchen-sink/stories/addon-jest.stories.js index d39a86527ed5..eb80009be86b 100644 --- a/examples/html-kitchen-sink/stories/addon-jest.stories.js +++ b/examples/html-kitchen-sink/stories/addon-jest.stories.js @@ -2,9 +2,9 @@ import { withTests as wt } from '@storybook/addon-jest'; import results from './addon-jest.testresults.json'; export default { - title: 'Addons|Jest', + title: 'Addons/Jest', decorators: [wt({ results })], }; -export const withTests = () => 'This story shows test results'; -withTests.parameters = { jest: 'addon-jest' }; +export const WithTests = () => 'This story shows test results'; +WithTests.parameters = { jest: 'addon-jest' }; diff --git a/examples/html-kitchen-sink/stories/addon-knobs.stories.js b/examples/html-kitchen-sink/stories/addon-knobs.stories.js index edae4ae89339..7b529a6c4101 100644 --- a/examples/html-kitchen-sink/stories/addon-knobs.stories.js +++ b/examples/html-kitchen-sink/stories/addon-knobs.stories.js @@ -16,7 +16,7 @@ import { const cachedContainer = document.createElement('p'); export default { - title: 'Addons|Knobs', + title: 'Addons/Knobs', decorators: [withKnobs], }; @@ -34,7 +34,7 @@ export const DOM = () => { return container; }; -export const story3 = () => { +export const Story3 = () => { const name = text('Name', 'John Doe'); const textColor = color('Text color', 'orangered'); cachedContainer.textContent = name; @@ -42,9 +42,9 @@ export const story3 = () => { cachedContainer.style.color = textColor; return cachedContainer; }; -story3.story = { name: 'CSS transitions' }; +Story3.story = { name: 'CSS transitions' }; -export const story4 = () => { +export const Story4 = () => { const name = text('Name', 'Jane'); const stock = number('Stock', 20, { range: true, @@ -85,7 +85,7 @@ export const story4 = () => {
`; }; -story4.story = { name: 'All knobs' }; +Story4.story = { name: 'All knobs' }; -export const story5 = () => text('Rendered string', ''); -story5.story = { name: 'XSS safety' }; +export const Story5 = () => text('Rendered string', ''); +Story5.story = { name: 'XSS safety' }; diff --git a/examples/html-kitchen-sink/stories/addon-notes.stories.js b/examples/html-kitchen-sink/stories/addon-notes.stories.js deleted file mode 100644 index 8fd33be9c37c..000000000000 --- a/examples/html-kitchen-sink/stories/addon-notes.stories.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - title: 'Addons|Notes', -}; - -export const story1 = () => - `

- - This is a fragment of HTML - -

`; -story1.story = { - name: 'Simple note', - parameters: { - notes: 'My notes on some bold text', - }, -}; diff --git a/examples/html-kitchen-sink/stories/button.html b/examples/html-kitchen-sink/stories/button.html new file mode 100644 index 000000000000..112fb4cab1c8 --- /dev/null +++ b/examples/html-kitchen-sink/stories/button.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/html-kitchen-sink/stories/button.stories.js b/examples/html-kitchen-sink/stories/button.stories.js index 72627d513e5f..bf4c9ea5f998 100644 --- a/examples/html-kitchen-sink/stories/button.stories.js +++ b/examples/html-kitchen-sink/stories/button.stories.js @@ -6,18 +6,18 @@ export default { title: 'Demo', }; -export const heading = () => '

Hello World

'; -export const headings = () => +export const Heading = () => '

Hello World

'; +export const Headings = () => '

Hello World

Hello World

Hello World

Hello World

'; -export const button = () => { +export const Button = () => { const btn = document.createElement('button'); btn.innerHTML = 'Hello Button'; btn.addEventListener('click', action('Click')); return btn; }; -export const effect = () => { +export const Effect = () => { useEffect(() => { document.getElementById('button').style.backgroundColor = 'yellow'; }); diff --git a/examples/html-kitchen-sink/stories/source-loader.stories.js b/examples/html-kitchen-sink/stories/source-loader.stories.js new file mode 100644 index 000000000000..65d903c72150 --- /dev/null +++ b/examples/html-kitchen-sink/stories/source-loader.stories.js @@ -0,0 +1,30 @@ +import button from './button.html'; + +const packageName = './button.html'; +const componentSubtitle = `import button from '${packageName}/lib/elements/buttons';`; + +export default { + title: 'Addons/Source loader', + parameters: { + componentSubtitle, + }, +}; + +export const Button = () => button; +Button.story = { + parameters: { + storySource: { + source: button, + }, + }, +}; + +export const SimpleStory = () => + `

+ + This is a fragment of HTML + +

`; +SimpleStory.story = { + name: 'Very simple story', +}; diff --git a/examples/marko-cli/.storybook/addons.js b/examples/marko-cli/.storybook/addons.js deleted file mode 100644 index ac7bf6fce5a2..000000000000 --- a/examples/marko-cli/.storybook/addons.js +++ /dev/null @@ -1,5 +0,0 @@ -import '@storybook/addon-storysource/register'; -import '@storybook/addon-actions/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-a11y/register'; diff --git a/examples/marko-cli/.storybook/config.js b/examples/marko-cli/.storybook/config.js deleted file mode 100644 index 2485daee8bb8..000000000000 --- a/examples/marko-cli/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/marko'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - hierarchyRootSeparator: /\|/, - }, -}); - -configure(require.context('../src/stories', true, /\.stories\.js$/), module); diff --git a/examples/marko-cli/.storybook/main.js b/examples/marko-cli/.storybook/main.js new file mode 100644 index 000000000000..1428a4981607 --- /dev/null +++ b/examples/marko-cli/.storybook/main.js @@ -0,0 +1,21 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/stories/**/*.stories.js'], + addons: [ + '@storybook/addon-storysource', + '@storybook/addon-actions', + '@storybook/addon-knobs', + '@storybook/addon-options', + '@storybook/addon-a11y', + ], + webpackFinal: async config => { + config.module.rules.push({ + test: [/\.stories\.js$/], + loaders: [require.resolve('@storybook/source-loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; + }, +}; diff --git a/examples/marko-cli/.storybook/manager.js b/examples/marko-cli/.storybook/manager.js new file mode 100644 index 000000000000..ee8398728448 --- /dev/null +++ b/examples/marko-cli/.storybook/manager.js @@ -0,0 +1,5 @@ +import { addons } from '@storybook/addons'; + +addons.setConfig({ + showRoots: true, +}); diff --git a/examples/marko-cli/.storybook/preview.js b/examples/marko-cli/.storybook/preview.js new file mode 100644 index 000000000000..673c37b1bf3c --- /dev/null +++ b/examples/marko-cli/.storybook/preview.js @@ -0,0 +1,4 @@ +import { addDecorator } from '@storybook/marko'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); diff --git a/examples/marko-cli/.storybook/webpack.config.js b/examples/marko-cli/.storybook/webpack.config.js deleted file mode 100644 index 5c4768c30ce1..000000000000 --- a/examples/marko-cli/.storybook/webpack.config.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); - -module.exports = async ({ config }) => { - config.module.rules.push({ - test: [/\.stories\.js$/], - loaders: [require.resolve('@storybook/source-loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }); - return config; -}; diff --git a/examples/marko-cli/package.json b/examples/marko-cli/package.json index 8eb7a7a50b4a..a4f6e20c0c62 100644 --- a/examples/marko-cli/package.json +++ b/examples/marko-cli/package.json @@ -1,6 +1,6 @@ { "name": "marko-cli", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "description": "Demo of how to build an app using marko-starter", "repository": { @@ -19,18 +19,18 @@ "test": "npm run lint" }, "dependencies": { - "marko": "^4.16.15", - "marko-starter": "^2.0.4" + "marko": "^4.18.25", + "marko-starter": "^2.1.0" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/marko": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/marko": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", "prettier": "^1.16.4", "webpack": "^4.33.0" } diff --git a/examples/marko-cli/src/stories/addon-actions.stories.js b/examples/marko-cli/src/stories/addon-actions.stories.js index 9f943b71a9e4..9a76a79fd111 100644 --- a/examples/marko-cli/src/stories/addon-actions.stories.js +++ b/examples/marko-cli/src/stories/addon-actions.stories.js @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import Button from '../components/action-button/index.marko'; export default { - title: 'Addons|Actions/Button', + title: 'Addons/Actions/Button', parameters: { component: Button, options: { diff --git a/examples/marko-cli/src/stories/addon-knobs.stories.js b/examples/marko-cli/src/stories/addon-knobs.stories.js index e1849a60aaa1..c55067257f2a 100644 --- a/examples/marko-cli/src/stories/addon-knobs.stories.js +++ b/examples/marko-cli/src/stories/addon-knobs.stories.js @@ -2,7 +2,7 @@ import { withKnobs, text, number } from '@storybook/addon-knobs'; import Hello from '../components/hello/index.marko'; export default { - title: 'Addons|Knobs/Hello', + title: 'Addons/Knobs/Hello', decorators: [withKnobs], parameters: { component: Hello, diff --git a/examples/marko-cli/src/stories/clickcount.stories.js b/examples/marko-cli/src/stories/clickcount.stories.js index d02fe83b40c8..c6557e9772ca 100644 --- a/examples/marko-cli/src/stories/clickcount.stories.js +++ b/examples/marko-cli/src/stories/clickcount.stories.js @@ -1,7 +1,7 @@ import ClickCount from '../components/click-count/index.marko'; export default { - title: 'Main|ClickCount', + title: 'Main/ClickCount', parameters: { component: ClickCount, }, diff --git a/examples/marko-cli/src/stories/hello.stories.js b/examples/marko-cli/src/stories/hello.stories.js index f4114a7901ee..a6a717560ad8 100644 --- a/examples/marko-cli/src/stories/hello.stories.js +++ b/examples/marko-cli/src/stories/hello.stories.js @@ -1,12 +1,12 @@ import Hello from '../components/hello/index.marko'; export default { - title: 'Main|Hello', + title: 'Main/Hello', parameters: { component: Hello, }, }; export const Simple = () => ({ input: { name: 'abc', age: 20 } }); -export const story2 = () => 'NOT A MARKO RENDER_RESULT'; -story2.story = { name: 'with ERROR!' }; +export const Story2 = () => 'NOT A MARKO RENDER_RESULT'; +Story2.story = { name: 'with ERROR!' }; diff --git a/examples/marko-cli/src/stories/stopwatch.stories.js b/examples/marko-cli/src/stories/stopwatch.stories.js index 1f62d6c736d7..5310d01270e3 100644 --- a/examples/marko-cli/src/stories/stopwatch.stories.js +++ b/examples/marko-cli/src/stories/stopwatch.stories.js @@ -1,7 +1,7 @@ import StopWatch from '../components/stop-watch/index.marko'; export default { - title: 'Main|StopWatch', + title: 'Main/StopWatch', parameters: { component: StopWatch, }, diff --git a/examples/marko-cli/src/stories/welcome.stories.js b/examples/marko-cli/src/stories/welcome.stories.js index 62bef2b231b9..4ffce8d5dda7 100644 --- a/examples/marko-cli/src/stories/welcome.stories.js +++ b/examples/marko-cli/src/stories/welcome.stories.js @@ -1,7 +1,7 @@ import Welcome from '../components/welcome/index.marko'; export default { - title: 'Main|Welcome', + title: 'Main/Welcome', parameters: { component: Welcome, }, diff --git a/examples/mithril-kitchen-sink/.storybook/addons.js b/examples/mithril-kitchen-sink/.storybook/addons.js deleted file mode 100644 index 61c6e6570abb..000000000000 --- a/examples/mithril-kitchen-sink/.storybook/addons.js +++ /dev/null @@ -1,9 +0,0 @@ -import '@storybook/addon-storysource/register'; -import '@storybook/addon-actions/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-notes/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-viewport/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-backgrounds/register'; -import '@storybook/addon-a11y/register'; diff --git a/examples/mithril-kitchen-sink/.storybook/config.js b/examples/mithril-kitchen-sink/.storybook/config.js deleted file mode 100644 index 5e73476ab141..000000000000 --- a/examples/mithril-kitchen-sink/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/mithril'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - hierarchyRootSeparator: /\|/, - }, -}); - -configure(require.context('../src/stories', true, /\.stories\.js$/), module); diff --git a/examples/mithril-kitchen-sink/.storybook/main.js b/examples/mithril-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..0a070fa00ad2 --- /dev/null +++ b/examples/mithril-kitchen-sink/.storybook/main.js @@ -0,0 +1,24 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/stories/**/*.stories.js'], + addons: [ + '@storybook/addon-storysource', + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-knobs', + '@storybook/addon-viewport', + '@storybook/addon-options', + '@storybook/addon-backgrounds', + '@storybook/addon-a11y', + ], + webpackFinal: async config => { + config.module.rules.push({ + test: [/\.stories\.js$/], + loaders: [require.resolve('@storybook/source-loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; + }, +}; diff --git a/examples/mithril-kitchen-sink/.storybook/manager.js b/examples/mithril-kitchen-sink/.storybook/manager.js new file mode 100644 index 000000000000..ee8398728448 --- /dev/null +++ b/examples/mithril-kitchen-sink/.storybook/manager.js @@ -0,0 +1,5 @@ +import { addons } from '@storybook/addons'; + +addons.setConfig({ + showRoots: true, +}); diff --git a/examples/mithril-kitchen-sink/.storybook/preview.js b/examples/mithril-kitchen-sink/.storybook/preview.js new file mode 100644 index 000000000000..aaa551ea87eb --- /dev/null +++ b/examples/mithril-kitchen-sink/.storybook/preview.js @@ -0,0 +1,4 @@ +import { addDecorator } from '@storybook/mithril'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); diff --git a/examples/mithril-kitchen-sink/.storybook/webpack.config.js b/examples/mithril-kitchen-sink/.storybook/webpack.config.js deleted file mode 100644 index 5c4768c30ce1..000000000000 --- a/examples/mithril-kitchen-sink/.storybook/webpack.config.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); - -module.exports = async ({ config }) => { - config.module.rules.push({ - test: [/\.stories\.js$/], - loaders: [require.resolve('@storybook/source-loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }); - return config; -}; diff --git a/examples/mithril-kitchen-sink/package.json b/examples/mithril-kitchen-sink/package.json index 0d1501e36e91..841dae795db9 100644 --- a/examples/mithril-kitchen-sink/package.json +++ b/examples/mithril-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "mithril-example", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { "build-storybook": "build-storybook", @@ -10,20 +10,19 @@ "mithril": "^1.1.6" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-storyshots": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addon-viewport": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/mithril": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addon-viewport": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/mithril": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", "webpack": "^4.33.0" } } diff --git a/examples/mithril-kitchen-sink/src/Button.js b/examples/mithril-kitchen-sink/src/Button.js index edec915a04b1..08d3f45a6175 100644 --- a/examples/mithril-kitchen-sink/src/Button.js +++ b/examples/mithril-kitchen-sink/src/Button.js @@ -4,12 +4,12 @@ import m from 'mithril'; const style = { border: '1px solid #eee', - borderRadius: '3px', + borderRadius: 3, backgroundColor: '#FFFFFF', cursor: 'pointer', - fontSize: '15px', + fontSize: 15, padding: '3px 10px', - margin: '10px', + margin: 10, }; const Button = { diff --git a/examples/mithril-kitchen-sink/src/Welcome.js b/examples/mithril-kitchen-sink/src/Welcome.js index 86e832016559..3826e784d534 100644 --- a/examples/mithril-kitchen-sink/src/Welcome.js +++ b/examples/mithril-kitchen-sink/src/Welcome.js @@ -6,7 +6,7 @@ const Main = { view: vnode => (
({ +export const Story1 = () => ({ view: () => , }); -story1.story = { name: 'Action only' }; +Story1.story = { name: 'Action only' }; -export const story2 = () => ({ +export const Story2 = () => ({ view: () => ( ), }); -story2.story = { name: 'Multiple actions' }; +Story2.story = { name: 'Multiple actions' }; -export const story3 = () => ({ +export const Story3 = () => ({ view: () => ( ), }); -story3.story = { name: 'Multiple actions, object' }; +Story3.story = { name: 'Multiple actions, object' }; -export const story4 = () => ({ +export const Story4 = () => ({ view: () => ( ), }); -story4.story = { name: 'Action and method' }; +Story4.story = { name: 'Action and method' }; diff --git a/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js index 2a6e62c39d31..48c158691950 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js @@ -4,7 +4,7 @@ import m from 'mithril'; import BaseButton from '../BaseButton'; export default { - title: 'Addons|Backgrounds', + title: 'Addons/Backgrounds', parameters: { backgrounds: [ { name: 'light', value: '#eeeeee' }, @@ -13,12 +13,12 @@ export default { }, }; -export const story1 = () => ({ +export const Story1 = () => ({ view: () => , }); -story1.story = { name: 'story 1' }; +Story1.story = { name: 'story 1' }; -export const story2 = () => ({ +export const Story2 = () => ({ view: () => , }); -story2.story = { name: 'story 2' }; +Story2.story = { name: 'story 2' }; diff --git a/examples/mithril-kitchen-sink/src/stories/addon-centered.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-centered.stories.js index f646d74b4a23..3fc3e4514e35 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-centered.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-centered.stories.js @@ -5,7 +5,7 @@ import Centered from '@storybook/addon-centered/mithril'; import Button from '../Button'; export default { - title: 'Addons|Centered', + title: 'Addons/Centered', decorators: [Centered], parameters: { component: Centered, diff --git a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js index 6d08bc923f19..4cf79669f6a0 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js @@ -15,7 +15,7 @@ import { } from '@storybook/addon-knobs'; export default { - title: 'Addons|Knobs', + title: 'Addons/Knobs', decorators: [withKnobs], }; @@ -29,7 +29,7 @@ export const Simple = () => { }; }; -export const story2 = () => { +export const Story2 = () => { const name = text('Name', 'Jane'); const stock = number('Stock', 20, { range: true, @@ -75,9 +75,9 @@ export const story2 = () => { ), }; }; -story2.story = { name: 'All knobs' }; +Story2.story = { name: 'All knobs' }; -export const story3 = () => ({ +export const Story3 = () => ({ view: () => text('Rendered string', ''), }); -story3.story = { name: 'XSS safety' }; +Story3.story = { name: 'XSS safety' }; diff --git a/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js index 63ea9c04ab56..ea20da6fa529 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js @@ -6,10 +6,10 @@ import { linkTo } from '@storybook/addon-links'; import Button from '../Button'; export default { - title: 'Addons|Links', + title: 'Addons/Links', }; -export const story1 = () => ({ +export const Story1 = () => ({ view: () => , }); -story1.story = { name: 'Go to welcome' }; +Story1.story = { name: 'Go to welcome' }; diff --git a/examples/mithril-kitchen-sink/src/stories/addon-notes.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-notes.stories.js deleted file mode 100644 index 6af53269ecdb..000000000000 --- a/examples/mithril-kitchen-sink/src/stories/addon-notes.stories.js +++ /dev/null @@ -1,52 +0,0 @@ -/** @jsx m */ - -import m from 'mithril'; - -export default { - title: 'Addons|Notes', -}; - -export const story1 = () => ({ - view: () => ( -

- - Etiam vulputate elit eu venenatis eleifend. Duis nec lectus augue. Morbi egestas diam sed - vulputate mollis. Fusce egestas pretium vehicula. Integer sed neque diam. Donec consectetur - velit vitae enim varius, ut placerat arcu imperdiet. Praesent sed faucibus arcu. Nullam sit - amet nibh a enim eleifend rhoncus. Donec pretium elementum leo at fermentum. Nulla - sollicitudin, mauris quis semper tempus, sem metus tristique diam, efficitur pulvinar mi - urna id urna. - -

- ), -}); - -story1.story = { - name: 'Simple note', - parameters: { notes: 'My notes on some bold text' }, -}; - -export const story2 = () => ({ - view: () => ( -

- 🤔😳😯😮 -
- 😄😩😓😱 -
- 🤓😑😶😊 -

- ), -}); - -story2.story = { - name: 'Note with HTML', - parameters: { - notes: ` -

My notes on emojies

- - It's not all that important to be honest, but.. - - Emojis are great, I love emojis, in fact I like using them in my Component notes too! 😇 - `, - }, -}; diff --git a/examples/mithril-kitchen-sink/src/stories/button.stories.js b/examples/mithril-kitchen-sink/src/stories/button.stories.js index ef051b16dec0..f172c6abe7fc 100644 --- a/examples/mithril-kitchen-sink/src/stories/button.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/button.stories.js @@ -12,12 +12,12 @@ export default { }, }; -export const story1 = () => ({ +export const Story1 = () => ({ view: () => m(Button, { onclick: action('clicked') }, 'Hello Button'), }); -story1.story = { name: 'with text' }; +Story1.story = { name: 'with text' }; -export const story2 = () => ({ +export const Story2 = () => ({ view: () => m( Button, @@ -25,4 +25,4 @@ export const story2 = () => ({ m('span', { role: 'img', ariaLabel: 'so cool' }, '😀 😎 👍 💯') ), }); -story2.story = { name: 'with some emoji' }; +Story2.story = { name: 'with some emoji' }; diff --git a/examples/mithril-kitchen-sink/src/stories/welcome.stories.js b/examples/mithril-kitchen-sink/src/stories/welcome.stories.js index 60ead95a694f..c30203e117c4 100644 --- a/examples/mithril-kitchen-sink/src/stories/welcome.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/welcome.stories.js @@ -7,7 +7,7 @@ export default { component: Welcome, }; -export const story1 = () => ({ +export const Story1 = () => ({ view: () => m(Welcome, { showApp: linkTo('Button') }), }); -story1.story = { name: 'to Storybook' }; +Story1.story = { name: 'to Storybook' }; diff --git a/examples/official-storybook/components/ButtonGroup.js b/examples/official-storybook/components/ButtonGroup.js new file mode 100644 index 000000000000..c86e59ef2786 --- /dev/null +++ b/examples/official-storybook/components/ButtonGroup.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +/** ButtonGroup component description from docgen */ +export const ButtonGroup = ({ background, children }) => ( +
{children}
+); + +ButtonGroup.defaultProps = { + background: '#ff0', + children: null, +}; + +ButtonGroup.propTypes = { + /** + * Background color for the group + */ + background: PropTypes.string, + children: PropTypes.arrayOf(PropTypes.element), +}; + +/** SubGroup component description from docgen */ +export const SubGroup = ({ background, children }) =>
{children}
; + +SubGroup.defaultProps = { + background: '#0f0', + children: null, +}; + +SubGroup.propTypes = { + /** + * Background color for the sub-group + */ + background: PropTypes.string, + children: PropTypes.arrayOf(PropTypes.element), +}; diff --git a/examples/official-storybook/components/DocgenButton.js b/examples/official-storybook/components/DocgenButton.js index 3abebb2bf3a9..8570c43f4b3d 100644 --- a/examples/official-storybook/components/DocgenButton.js +++ b/examples/official-storybook/components/DocgenButton.js @@ -1,7 +1,23 @@ import React from 'react'; import PropTypes from 'prop-types'; -/** DocgenButton component description imported from comments inside the component file */ +/** + * DocgenButton component description imported from comments inside the component file, + * + * *Important Note*: Unlike with normal `` elements, setting this will + * not validate the input contents. This is because in this project we use + * Formik and Yup to validate fields at the form-level, not at the individual + * input level. It is still very important to set this value properly for + * accessibility and user experience. + * + * Here's a list to test out formatting. + * + * * `"number"` Any number not represented by a more specific type. + * * `"password"` A password. + * * `"email"` An email address. + * * `"tel"` A phone or fax number. Shows the phone number keypad on + * mobile keyboards. + */ export const DocgenButton = ({ disabled, label, onClick }) => ( + ); +}; + +export default Button; diff --git a/examples/official-storybook/image-snapshots/storyshots-image.runner.js b/examples/official-storybook/image-snapshots/storyshots-image.runner.js deleted file mode 100644 index 71df59607983..000000000000 --- a/examples/official-storybook/image-snapshots/storyshots-image.runner.js +++ /dev/null @@ -1,34 +0,0 @@ -/* This file is not suffixed by ".test.js" to not being run with all other test files. - * This test needs the static build of the storybook to run. - * `yarn run image-snapshots` generates the static build & uses the image snapshots behavior of storyshots. - * */ -import path from 'path'; -import fs from 'fs'; -import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -import { logger } from '@storybook/node-logger'; - -// Image snapshots -// We do screenshots against the static build of the storybook. -// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. -const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); - -if (!fs.existsSync(pathToStorybookStatic)) { - logger.error( - 'You are running image snapshots without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' - ); -} else { - initStoryshots({ - suite: 'Image snapshots', - storyKindRegex: /^Addons\|Storyshots/, - framework: 'react', - configPath: path.join(__dirname, '..'), - test: imageSnapshot({ - storybookUrl: `file://${pathToStorybookStatic}`, - getMatchOptions: () => ({ - failureThreshold: 0.02, // 2% threshold, - failureThresholdType: 'percent', - }), - }), - }); -} diff --git a/examples/official-storybook/intro.stories.mdx b/examples/official-storybook/intro.stories.mdx new file mode 100644 index 000000000000..b192db9fded5 --- /dev/null +++ b/examples/official-storybook/intro.stories.mdx @@ -0,0 +1,5 @@ + + +# Official-storybook + +Welcome to `official-storybook`, a collection of test cases and demos for `@storybook/react` and all its addons. diff --git a/examples/official-storybook/main.js b/examples/official-storybook/main.js index 29b9882f4489..0e2cf6ee5151 100644 --- a/examples/official-storybook/main.js +++ b/examples/official-storybook/main.js @@ -1,25 +1,28 @@ module.exports = { - presets: ['@storybook/addon-docs/react/preset'], - addons: existing => [ - ...existing, - '@storybook/addon-storysource/register', - '@storybook/addon-design-assets/register', - '@storybook/addon-docs/register', - '@storybook/addon-actions/register', - '@storybook/addon-links/register', - '@storybook/addon-events/register', - '@storybook/addon-notes/register', - '@storybook/addon-options/register', - '@storybook/addon-knobs/register', - '@storybook/addon-cssresources/register', - '@storybook/addon-backgrounds/register', - '@storybook/addon-a11y/register', - '@storybook/addon-jest/register', - '@storybook/addon-viewport/register', - '@storybook/addon-graphql/register', - '@storybook/addon-contexts/register', + stories: [ + // FIXME: Breaks e2e tests './intro.stories.mdx', + '../../lib/ui/src/**/*.stories.(js|tsx|mdx)', + '../../lib/components/src/**/*.stories.(js|tsx|mdx)', + './stories/**/*.stories.(js|tsx|mdx)', ], - webpack: async (config, { configType }) => ({ + addons: [ + '@storybook/addon-docs', + '@storybook/addon-storysource', + '@storybook/addon-design-assets', + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-events', + '@storybook/addon-options', + '@storybook/addon-knobs', + '@storybook/addon-cssresources', + '@storybook/addon-backgrounds', + '@storybook/addon-a11y', + '@storybook/addon-jest', + '@storybook/addon-viewport', + '@storybook/addon-graphql', + '@storybook/addon-contexts', + ], + webpackFinal: async (config, { configType }) => ({ ...config, module: { ...config.module, diff --git a/examples/official-storybook/manager.js b/examples/official-storybook/manager.js index 009efad1ff51..8b470c7850f7 100644 --- a/examples/official-storybook/manager.js +++ b/examples/official-storybook/manager.js @@ -1,3 +1,16 @@ +import { addons } from '@storybook/addons'; + import addHeadWarning from './head-warning'; addHeadWarning('manager-head-not-loaded', 'Manager head not loaded'); + +addons.setConfig({ + previewTabs: { + canvas: null, + 'storybook/docs/panel': null, + 'storybookjs/notes/panel': { title: 'Annotations', hidden: true }, + graphiql: { + hidden: true, + }, + }, +}); diff --git a/examples/official-storybook/package.json b/examples/official-storybook/package.json index 9cab6feb8741..334c149947f1 100644 --- a/examples/official-storybook/package.json +++ b/examples/official-storybook/package.json @@ -1,46 +1,45 @@ { "name": "official-storybook", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "private": true, "scripts": { "build-storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true build-storybook -c ./", - "do-image-snapshots": "../../node_modules/.bin/jest --projects=./image-snapshots", + "debug": "cross-env NODE_OPTIONS=--inspect-brk STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll", + "do-storyshots-puppeteer": "../../node_modules/.bin/jest --projects=./storyshots-puppeteer", "generate-addon-jest-testresults": "jest --config=tests/addon-jest.config.json --json --outputFile=stories/addon-jest.testresults.json", "graphql": "node ./graphql-server/index.js", - "image-snapshots": "yarn run build-storybook && yarn run do-image-snapshots", "packtracker": "yarn storybook --smoke-test --quiet && cross-env PT_PROJECT_TOKEN=1af1d41b-d737-41d4-ac00-53c8f3913b53 packtracker-upload --stats=./node_modules/.cache/storybook/manager-stats.json", - "storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll" + "storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll", + "storyshots-puppeteer": "yarn run build-storybook && yarn run do-storyshots-puppeteer" }, "devDependencies": { "@packtracker/webpack-plugin": "^2.0.1", - "@storybook/addon-a11y": "5.3.0-alpha.41", - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-backgrounds": "5.3.0-alpha.41", - "@storybook/addon-centered": "5.3.0-alpha.41", - "@storybook/addon-contexts": "5.3.0-alpha.41", - "@storybook/addon-cssresources": "5.3.0-alpha.41", - "@storybook/addon-design-assets": "5.3.0-alpha.41", - "@storybook/addon-docs": "5.3.0-alpha.41", - "@storybook/addon-events": "5.3.0-alpha.41", - "@storybook/addon-graphql": "5.3.0-alpha.41", - "@storybook/addon-info": "5.3.0-alpha.41", - "@storybook/addon-jest": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "@storybook/addon-links": "5.3.0-alpha.41", - "@storybook/addon-notes": "5.3.0-alpha.41", - "@storybook/addon-options": "5.3.0-alpha.41", - "@storybook/addon-queryparams": "5.3.0-alpha.41", - "@storybook/addon-storyshots": "5.3.0-alpha.41", - "@storybook/addon-storyshots-puppeteer": "5.3.0-alpha.41", - "@storybook/addon-storysource": "5.3.0-alpha.41", - "@storybook/addon-viewport": "5.3.0-alpha.41", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/node-logger": "5.3.0-alpha.41", - "@storybook/react": "5.3.0-alpha.41", - "@storybook/source-loader": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addon-a11y": "6.0.0-alpha.2", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-backgrounds": "6.0.0-alpha.2", + "@storybook/addon-centered": "6.0.0-alpha.2", + "@storybook/addon-contexts": "6.0.0-alpha.2", + "@storybook/addon-cssresources": "6.0.0-alpha.2", + "@storybook/addon-design-assets": "6.0.0-alpha.2", + "@storybook/addon-docs": "6.0.0-alpha.2", + "@storybook/addon-events": "6.0.0-alpha.2", + "@storybook/addon-graphql": "6.0.0-alpha.2", + "@storybook/addon-jest": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "@storybook/addon-links": "6.0.0-alpha.2", + "@storybook/addon-options": "6.0.0-alpha.2", + "@storybook/addon-queryparams": "6.0.0-alpha.2", + "@storybook/addon-storyshots": "6.0.0-alpha.2", + "@storybook/addon-storyshots-puppeteer": "6.0.0-alpha.2", + "@storybook/addon-storysource": "6.0.0-alpha.2", + "@storybook/addon-viewport": "6.0.0-alpha.2", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/node-logger": "6.0.0-alpha.2", + "@storybook/react": "6.0.0-alpha.2", + "@storybook/source-loader": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "cors": "^2.8.5", "cross-env": "^6.0.3", "enzyme-to-json": "^3.4.1", @@ -54,6 +53,7 @@ "lodash": "^4.17.15", "paths.macro": "^2.0.2", "prop-types": "^15.7.2", + "puppeteer": "^2.0.0", "react": "^16.8.3", "react-dom": "^16.8.3", "storybook-chromatic": "^3.0.0", diff --git a/examples/official-storybook/preview.js b/examples/official-storybook/preview.js index 32628d68c6fa..074c294f18c0 100644 --- a/examples/official-storybook/preview.js +++ b/examples/official-storybook/preview.js @@ -1,9 +1,8 @@ import React from 'react'; -import { configure, addDecorator, addParameters } from '@storybook/react'; +import { addDecorator, addParameters } from '@storybook/react'; import { Global, ThemeProvider, themes, createReset, convert } from '@storybook/theming'; import { withCssResources } from '@storybook/addon-cssresources'; import { withA11y } from '@storybook/addon-a11y'; -import { withNotes } from '@storybook/addon-notes'; import { DocsPage } from '@storybook/addon-docs/blocks'; import addHeadWarning from './head-warning'; @@ -27,7 +26,6 @@ addHeadWarning('dotenv-file-not-loaded', 'Dotenv file not loaded'); addDecorator(withCssResources); addDecorator(withA11y); -addDecorator(withNotes); addDecorator(storyFn => ( @@ -45,11 +43,10 @@ addParameters({ }, }, options: { - hierarchySeparator: /\/|\./, - hierarchyRootSeparator: '|', + showRoots: true, theme: themes.light, // { base: 'dark', brandTitle: 'Storybook!' }, storySort: (a, b) => - a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, { numeric: true }), + a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), }, backgrounds: [ { name: 'storybook app', value: themes.light.appBg, default: true }, @@ -57,21 +54,6 @@ addParameters({ { name: 'dark', value: '#222222' }, ], docs: { - // eslint-disable-next-line react/prop-types - page: ({ context }) => ( - `Subtitle: ${selectedKind}`} - /> - ), + page: () => `Subtitle: ${selectedKind}`} />, }, }); - -configure( - [ - require.context('../../lib/ui/src', true, /\.stories\.(js|tsx?|mdx)$/), - require.context('../../lib/components/src', true, /\.stories\.(js|tsx?|mdx)$/), - require.context('./stories', true, /\.stories\.(js|tsx?|mdx)$/), - ], - module -); diff --git a/examples/official-storybook/stories/addon-a11y/base-button.stories.js b/examples/official-storybook/stories/addon-a11y/base-button.stories.js index a882bf92a4e2..64f1aded15ca 100644 --- a/examples/official-storybook/stories/addon-a11y/base-button.stories.js +++ b/examples/official-storybook/stories/addon-a11y/base-button.stories.js @@ -5,7 +5,7 @@ import DelayedRender from '../../components/DelayedRender'; const text = 'Testing the a11y addon'; export default { - title: 'Addons|A11y/BaseButton', + title: 'Addons/A11y/BaseButton', component: BaseButton, parameters: { options: { selectedPanel: 'storybook/a11y/panel' }, @@ -16,11 +16,11 @@ export const Default = () => ; export const Label = () => ; export const Disabled = () => ; -export const invalidContrast = () => ( +export const InvalidContrast = () => ( // FIXME: has no effect on score ); -invalidContrast.story = { +InvalidContrast.story = { name: 'Invalid contrast', }; diff --git a/examples/official-storybook/stories/addon-a11y/button.stories.js b/examples/official-storybook/stories/addon-a11y/button.stories.js index dc7863262dbb..a59bd7a48603 100644 --- a/examples/official-storybook/stories/addon-a11y/button.stories.js +++ b/examples/official-storybook/stories/addon-a11y/button.stories.js @@ -4,7 +4,7 @@ import Button from '../../components/addon-a11y/Button'; const text = 'Testing the a11y addon'; export default { - title: 'Addons|A11y/Button', + title: 'Addons/A11y/Button', component: Button, parameters: { options: { selectedPanel: 'storybook/a11y/panel' }, @@ -15,7 +15,7 @@ export const Default = () => ; +export const BasicExample = () => ; -basicExample.story = { +BasicExample.story = { name: 'Basic example', }; -export const multipleActions = () => ( +export const MultipleActions = () => ( ); -multipleActions.story = { +MultipleActions.story = { name: 'Multiple actions', }; -export const multipleActionsConfig = () => ( +export const MultipleActionsConfig = () => ( ); -multipleActionsConfig.story = { +MultipleActionsConfig.story = { name: 'Multiple actions + config', }; -export const multipleActionsAsObject = () => ( +export const MultipleActionsAsObject = () => ( ); -multipleActionsAsObject.story = { +MultipleActionsAsObject.story = { name: 'Multiple actions as object', }; -export const multipleActionsObjectConfig = () => ( +export const MultipleActionsObjectConfig = () => ( ); -multipleActionsObjectConfig.story = { +MultipleActionsObjectConfig.story = { name: 'Multiple actions, object + config', }; -export const decoratedAction = () => ( +export const DecoratedAction = () => ( ); -decoratedAction.story = { +DecoratedAction.story = { name: 'Decorated action', }; -export const decoratedActionConfig = () => ( +export const DecoratedActionConfig = () => ( ); -decoratedActionConfig.story = { +DecoratedActionConfig.story = { name: 'Decorated action + config', }; -export const decoratedActions = () => ( +export const DecoratedActions = () => ( ); -decoratedActions.story = { +DecoratedActions.story = { name: 'Decorated actions', }; -export const decoratedActionsConfig = () => ( +export const DecoratedActionsConfig = () => ( ); -decoratedActionsConfig.story = { +DecoratedActionsConfig.story = { name: 'Decorated actions + config', }; -export const circularPayload = () => { +export const CircularPayload = () => { const circular = { foo: {} }; circular.foo.circular = circular; return ; }; -circularPayload.story = { +CircularPayload.story = { name: 'Circular Payload', }; -export const reservedKeywordAsName = () => ; +export const ReservedKeywordAsName = () => ; -reservedKeywordAsName.story = { +ReservedKeywordAsName.story = { name: 'Reserved keyword as name', }; -export const allTypes = () => { +export const AllTypes = () => { function A() {} function B() {} @@ -185,11 +185,11 @@ export const allTypes = () => { ); }; -allTypes.story = { +AllTypes.story = { name: 'All types', }; -export const configureActionsDepth = () => { +export const ConfigureActionsDepth = () => { configureActions({ depth: 2, }); @@ -201,7 +201,7 @@ export const configureActionsDepth = () => { ); }; -export const persistingTheActionLogger = () => ( +export const PersistingTheActionLogger = () => (

Moving away from this story will persist the action logger

); -primaryLargeButton.story = { +PrimaryLargeButton.story = { name: 'Primary Large Button', parameters: { cssresources: [ @@ -30,8 +30,8 @@ primaryLargeButton.story = { }, }; -export const cameraIcon = () => Camera Icon; -cameraIcon.story = { +export const CameraIcon = () => Camera Icon; +CameraIcon.story = { name: 'Camera Icon', parameters: { cssresources: [ diff --git a/examples/official-storybook/stories/addon-design-assets.stories.js b/examples/official-storybook/stories/addon-design-assets.stories.js index 9b5185fcea5d..6d858132d683 100644 --- a/examples/official-storybook/stories/addon-design-assets.stories.js +++ b/examples/official-storybook/stories/addon-design-assets.stories.js @@ -1,7 +1,7 @@ import React from 'react'; export default { - title: 'Addons|Design assets', + title: 'Addons/Design assets', parameters: { options: { @@ -10,9 +10,9 @@ export default { }, }; -export const singleImage = () =>
This story should a single image in the assets panel
; +export const SingleImage = () =>
This story should a single image in the assets panel
; -singleImage.story = { +SingleImage.story = { name: 'single image', parameters: { @@ -20,9 +20,9 @@ singleImage.story = { }, }; -export const singleWebpage = () =>
This story should a single image in the assets panel
; +export const SingleWebpage = () =>
This story should a single image in the assets panel
; -singleWebpage.story = { +SingleWebpage.story = { name: 'single webpage', parameters: { @@ -30,9 +30,9 @@ singleWebpage.story = { }, }; -export const youtubeVideo = () =>
This story should a single image in the assets panel
; +export const YoutubeVideo = () =>
This story should a single image in the assets panel
; -youtubeVideo.story = { +YoutubeVideo.story = { name: 'youtube video', parameters: { @@ -40,11 +40,11 @@ youtubeVideo.story = { }, }; -export const multipleImages = () => ( +export const MultipleImages = () => (
This story should a multiple images in the assets panel
); -multipleImages.story = { +MultipleImages.story = { name: 'multiple images', parameters: { @@ -55,9 +55,9 @@ multipleImages.story = { }, }; -export const namedAssets = () =>
This story should a single image in the assets panel
; +export const NamedAssets = () =>
This story should a single image in the assets panel
; -namedAssets.story = { +NamedAssets.story = { name: 'named assets', parameters: { @@ -74,11 +74,11 @@ namedAssets.story = { }, }; -export const urlReplacement = () => ( +export const UrlReplacement = () => (
This story should have a webpge, with within it's url the storyId
); -urlReplacement.story = { +UrlReplacement.story = { name: 'url replacement', parameters: { diff --git a/examples/official-storybook/stories/addon-docs/addon-docs-blocks.stories.js b/examples/official-storybook/stories/addon-docs/addon-docs-blocks.stories.js new file mode 100644 index 000000000000..55a593714c4b --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/addon-docs-blocks.stories.js @@ -0,0 +1,177 @@ +import React from 'react'; +import { + Title, + Subtitle, + Description, + Primary, + Props, + Stories, +} from '@storybook/addon-docs/blocks'; +import { DocgenButton } from '../../components/DocgenButton'; +import BaseButton from '../../components/BaseButton'; +import { ButtonGroup, SubGroup } from '../../components/ButtonGroup'; + +export default { + title: 'Addons/Docs/stories docs blocks', + component: DocgenButton, + parameters: { + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Props /> + <Stories /> + </> + ), + }, + }, +}; + +export const defDocsPage = () => <div>Default docs page</div>; + +export const smallDocsPage = () => <div>Just primary story, </div>; +smallDocsPage.story = { + parameters: { + docs: { + page: () => ( + <> + <Title /> + <Primary /> + </> + ), + }, + }, +}; + +export const checkBoxProps = () => <div>Primary props displayed with a check box </div>; +checkBoxProps.story = { + parameters: { + docs: { + page: () => { + const [showProps, setShowProps] = React.useState(false); + return ( + <> + <Title /> + <Subtitle /> + <Description /> + <Primary /> + <label> + <input + type="checkbox" + checked={showProps} + onChange={() => setShowProps(!showProps)} + /> + <span>display props</span> + </label> + {showProps && <Props />} + </> + ); + }, + }, + }, +}; + +export const customLabels = () => <div>Display custom title, Subtitle, Description</div>; +customLabels.story = { + parameters: { + docs: { + page: () => ( + <> + <Title>Custom title + Custom sub title + Custom description + + + + + ), + }, + }, +}; + +export const customStoriesFilter = () =>
Displays ALL stories (not excluding first one)
; +customStoriesFilter.story = { + parameters: { + docs: { + page: () => ( + <> + stories} /> + + ), + }, + }, +}; + +export const descriptionSlot = () =>
Adds markdown to the description
; +descriptionSlot.story = { + parameters: { + docs: { + page: () => ( + <> + `${description}`} /> + + ), + }, + }, +}; + +export const multipleComponents = () => ( + + + + + +); + +multipleComponents.story = { + name: 'Many Components', + parameters: { + component: ButtonGroup, + subcomponents: { + SubGroup, + 'Docgen Button': DocgenButton, + 'Base Button': BaseButton, + }, + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary slot={stories => stories.find(story => story.story === 'Many Components')} /> + <Props /> + </> + ), + }, + }, +}; + +export const componentsProps = () => <div>Display multiple prop tables in tabs</div>; +componentsProps.story = { + subcomponents: { + 'Docgen Button': DocgenButton, + 'Base Button': BaseButton, + }, + parameters: { + docs: { + page: () => ( + <> + <Title>Multiple prop tables + + Here's what happens when your component has some related components + + + + ), + }, + }, +}; diff --git a/examples/official-storybook/stories/addon-docs/addon-docs.stories.js b/examples/official-storybook/stories/addon-docs/addon-docs.stories.js index 6a2f2ae2e0fd..81e99d1aecd7 100644 --- a/examples/official-storybook/stories/addon-docs/addon-docs.stories.js +++ b/examples/official-storybook/stories/addon-docs/addon-docs.stories.js @@ -3,52 +3,56 @@ import notes from '../notes/notes.md'; import mdxNotes from '../notes/notes.mdx'; import { DocgenButton } from '../../components/DocgenButton'; +const docsTitle = title => `Docs/${title}`; + export default { - title: 'Addons|Docs/stories', + title: `Addons/${docsTitle('stories')}`, component: DocgenButton, }; -export const basic = () =>
Click docs tab to see basic docs
; +export const Basic = () =>
Click docs tab to see basic docs
; -export const noDocs = () =>
Click docs tab to see no docs error
; -noDocs.story = { +export const NoDocs = () =>
Click docs tab to see no docs error
; +NoDocs.story = { name: 'no docs', parameters: { docs: { page: null } }, }; -export const withNotes = () =>
Click docs tab to see DocsPage docs
; -withNotes.story = { +export const WithNotes = () =>
Click docs tab to see DocsPage docs
; +WithNotes.story = { name: 'with notes', parameters: { notes }, }; -export const withInfo = () =>
Click docs tab to see DocsPage docs
; -withInfo.story = { +export const WithInfo = () =>
Click docs tab to see DocsPage docs
; +WithInfo.story = { name: 'with info', parameters: { info: 'some user info string', }, }; -export const mdxOverride = () =>
Click docs tab to see MDX-overridden docs
; -mdxOverride.story = { +export const MdxOverride = () =>
Click docs tab to see MDX-overridden docs
; +MdxOverride.story = { name: 'mdx override', parameters: { docs: { page: mdxNotes }, }, }; -export const jsxOverride = () =>
Click docs tab to see JSX-overridden docs
; -jsxOverride.story = { +export const JsxOverride = () =>
Click docs tab to see JSX-overridden docs
; +JsxOverride.story = { name: 'jsx override', parameters: { docs: { page: () =>
Hello docs
}, }, }; -export const docsDisable = () =>
This story shouldn't show up in DocsPage
; -docsDisable.story = { +export const DocsDisable = () =>
This story shouldn't show up in DocsPage
; +DocsDisable.story = { parameters: { docs: { disable: true }, }, }; + +export const LargerThanPreview = () =>
HUGE
; diff --git a/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx b/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx index b9a11b4a4832..970bdea292a1 100644 --- a/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx @@ -8,15 +8,17 @@ import { ColorItem, Meta, } from '@storybook/addon-docs/blocks'; +import { withKnobs, text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { Button } from '@storybook/react/demo'; import FlowTypeButton from '../../components/FlowTypeButton'; import { DocgenButton } from '../../components/DocgenButton';
{storyFn()}
]} + id="addons-docs-mdx-id" + decorators={[storyFn =>
{storyFn()}
, withKnobs]} parameters={{ notes: 'component notes' }} /> @@ -64,7 +66,7 @@ export const nonStory2 = () => ; // another one - + @@ -86,6 +88,10 @@ export const nonStory2 = () => ; // another one <>This is an iframe! +
{storyFn()}
]}> + <>Story decorators +
+

Hello Hugh

diff --git a/examples/official-storybook/stories/addon-docs/container-override.stories.mdx b/examples/official-storybook/stories/addon-docs/container-override.stories.mdx new file mode 100644 index 000000000000..6982003fc907 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/container-override.stories.mdx @@ -0,0 +1,17 @@ +import { Meta, DocsContainer } from '@storybook/addon-docs/blocks'; + + ( + +
{children}
+
+ ), + }, + }} +/> + +
some content
diff --git a/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.js b/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.js index c699766b551f..d9f894195c07 100644 --- a/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.js +++ b/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import { Button } from '@storybook/react/demo'; export default { - title: 'Addons|Docs/csf-with-mdx-docs', + title: 'Addons/Docs/csf-with-mdx-docs', component: Button, includeStories: [], // or don't load this file at all }; diff --git a/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.mdx b/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.mdx index 485a6dbd6b49..f6418af2eb0c 100644 --- a/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/csf-with-mdx-docs.stories.mdx @@ -1,7 +1,7 @@ import { Meta, Story } from '@storybook/addon-docs/blocks'; import * as stories from './csf-with-mdx-docs.stories'; - + # Button diff --git a/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx b/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx index 606558f5fa6e..cc72c988134d 100644 --- a/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx @@ -1,9 +1,16 @@ import { Meta } from '@storybook/addon-docs/blocks'; - + # Documentation-only MDX This file is a documentation-only MDX file, i.e. it doesn't contain any `` definitions. Therefore, it shows up in the navigation UI as a document icon. + +


















+


















+ +## Bottom + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc faucibus urna id nibh mollis, varius facilisis sapien scelerisque. Pellentesque lobortis convallis mi, at accumsan nisl sollicitudin id. Aliquam vitae elementum libero. Nulla blandit est turpis, a consectetur ante rhoncus a. Integer eu quam eu mauris pharetra elementum. Donec ex nisl, tincidunt ut tincidunt id, bibendum ut sem. Sed in congue tortor, a congue dolor. Fusce a magna vel nulla laoreet sagittis. diff --git a/examples/official-storybook/stories/addon-docs/forward-ref.stories.js b/examples/official-storybook/stories/addon-docs/forward-ref.stories.js new file mode 100644 index 000000000000..f6f2b2a2d968 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/forward-ref.stories.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { DocgenButton } from '../../components/DocgenButton'; + +const ForwardedButton = React.forwardRef((props, ref) => ); + +export default { + title: 'Addons|Docs/ForwardRef', + component: ForwardedButton, + parameters: { chromatic: { disable: true } }, +}; + +export const displaysCorrectly = () => Hello World!; +displaysCorrectly.story = { name: 'Displays forwarded ref components correctly' }; diff --git a/examples/official-storybook/stories/addon-docs/markdown.stories.mdx b/examples/official-storybook/stories/addon-docs/markdown.stories.mdx index 3431243f0ccb..64f27167a12b 100644 --- a/examples/official-storybook/stories/addon-docs/markdown.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/markdown.stories.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/addon-docs/blocks'; - + # h1 Heading @@ -129,9 +129,19 @@ Right aligned columns ## Links -[link text](https://hichroma.com) +[external link](https://hichroma.com) -[link with title](https://hichroma.com 'Insert title!') +[external link with title](https://hichroma.com 'Insert title!') + +[link to in page anchor](#h1-heading) + +[link to another story (docs)](?path=/docs/addons-docs-docs-only--page) + +[link to another story (canvas)](?path=/story/addons-docs-buttongroup--basic) + +[link to about page](?path=/settings/about) + +[link to absolute local url](/absolute) ## Images diff --git a/examples/official-storybook/stories/addon-docs/mdx.stories.js b/examples/official-storybook/stories/addon-docs/mdx.stories.js index 8d558fb190b2..e2563d4825fe 100644 --- a/examples/official-storybook/stories/addon-docs/mdx.stories.js +++ b/examples/official-storybook/stories/addon-docs/mdx.stories.js @@ -3,12 +3,12 @@ import { DocsContainer } from '@storybook/addon-docs/blocks'; import markdown from './markdown.stories.mdx'; export default { - title: 'Addons|Docs/mdx-in-story', + title: 'Addons/Docs/mdx-in-story', decorators: [storyFn => {storyFn()}], }; // This renders the contents of the docs panel into story content -export const typography = () => { +export const Typography = () => { const Docs = markdown.parameters.docs.page; return ; }; diff --git a/examples/official-storybook/stories/addon-docs/meta-string-template.stories.mdx b/examples/official-storybook/stories/addon-docs/meta-string-template.stories.mdx new file mode 100644 index 000000000000..33eff79835bc --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/meta-string-template.stories.mdx @@ -0,0 +1,12 @@ +import { Meta, Story } from '@storybook/addon-docs/blocks'; +import { titleFunction } from '@storybook/addon-docs/dist/mdx/title-generators'; + + + +# Meta title from a string template + +## Stories + + + <>hello + \ No newline at end of file diff --git a/examples/official-storybook/stories/addon-docs/meta-title-quotes.stories.mdx b/examples/official-storybook/stories/addon-docs/meta-title-quotes.stories.mdx new file mode 100644 index 000000000000..d89cdab9ab95 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/meta-title-quotes.stories.mdx @@ -0,0 +1,7 @@ +import { Meta } from '@storybook/addon-docs/blocks'; + + + +# Quotes in the title \ No newline at end of file diff --git a/examples/official-storybook/stories/addon-docs/mixed-leaves-component.stories.js b/examples/official-storybook/stories/addon-docs/mixed-leaves-component.stories.js new file mode 100644 index 000000000000..3d6a49874f1c --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/mixed-leaves-component.stories.js @@ -0,0 +1,13 @@ +// This example exists solely to demonstrate nav hierarchy +// in --docs mode when a folder contains both a component and +// individual stories +// +// See also ./mixed-leaves-folder.stories.js + +export default { + title: 'Addons/Docs/Mixed Leaves/Component', + parameters: { chromatic: { disable: true } }, +}; + +export const B = () => 'b'; +export const C = () => 'c'; diff --git a/examples/official-storybook/stories/addon-docs/mixed-leaves-folder.stories.js b/examples/official-storybook/stories/addon-docs/mixed-leaves-folder.stories.js new file mode 100644 index 000000000000..2f9216606fe9 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/mixed-leaves-folder.stories.js @@ -0,0 +1,12 @@ +// This example exists solely to demonstrate nav hierarchy +// in --docs mode when a folder contains both a component and +// individual stories +// +// See also ./mixed-leaves-component.stories.js + +export default { + title: 'Addons/Docs/Mixed Leaves', + parameters: { chromatic: { disable: true } }, +}; + +export const A = () => 'a'; diff --git a/examples/official-storybook/stories/addon-docs/props.stories.mdx b/examples/official-storybook/stories/addon-docs/props.stories.mdx index d6a03b4ee0ac..ba40711117ab 100644 --- a/examples/official-storybook/stories/addon-docs/props.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/props.stories.mdx @@ -3,7 +3,7 @@ import { DocgenButton } from '../../components/DocgenButton'; import { ForwardRefButton } from '../../components/ForwardRefButton'; import { MemoButton } from '../../components/MemoButton'; - + ## Docgen diff --git a/examples/official-storybook/stories/addon-docs/react-memo.stories.js b/examples/official-storybook/stories/addon-docs/react-memo.stories.js new file mode 100644 index 000000000000..f91bcc18851e --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/react-memo.stories.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { DocgenButton } from '../../components/DocgenButton'; + +const ButtonWithMemo = React.memo(props => ); + +export default { + title: 'Addons|Docs/ButtonWithMemo', + component: ButtonWithMemo, + parameters: { chromatic: { disable: true } }, +}; + +export const displaysCorrectly = () => Hello World!; +displaysCorrectly.story = { name: 'Displays components with memo correctly' }; diff --git a/examples/official-storybook/stories/addon-docs/subcomponents.stories.js b/examples/official-storybook/stories/addon-docs/subcomponents.stories.js new file mode 100644 index 000000000000..0a282af957e7 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/subcomponents.stories.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { DocgenButton } from '../../components/DocgenButton'; +import { ButtonGroup } from '../../components/ButtonGroup'; + +export default { + title: 'Addons/Docs/ButtonGroup', + component: ButtonGroup, + parameters: { viewMode: 'docs' }, + subcomponents: { DocgenButton }, +}; + +export const basic = () => ( + + + + +); diff --git a/examples/official-storybook/stories/addon-docs/ts-button.stories.tsx b/examples/official-storybook/stories/addon-docs/ts-button.stories.tsx new file mode 100644 index 000000000000..6b8800c31864 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/ts-button.stories.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { action } from '@storybook/addon-actions'; +import { radios } from '@storybook/addon-knobs'; +import Button, { Type } from '../../components/TsButton'; + +export default { + title: 'Addons/Docs/TsButton', + component: Button, + parameters: { + viewMode: 'story', + }, +}; + +type Story = () => any; + +export const SimpleButton: Story = () => { + const x = 0; + return ; +}; + +const typeOptions = { + Default: 'default', + Action: 'action', +}; + +export const WithType = () => ( + +); diff --git a/examples/official-storybook/stories/addon-events.stories.js b/examples/official-storybook/stories/addon-events.stories.js index 7f280c366b01..6a358119afc7 100644 --- a/examples/official-storybook/stories/addon-events.stories.js +++ b/examples/official-storybook/stories/addon-events.stories.js @@ -67,7 +67,7 @@ const events = [ ]; export default { - title: 'Addons|Events', + title: 'Addons/Events', decorators: [withEvents({ emit, events })], parameters: { options: { diff --git a/examples/official-storybook/stories/addon-graphql.stories.js b/examples/official-storybook/stories/addon-graphql.stories.js index 6a175d206e50..e33908de1ad5 100644 --- a/examples/official-storybook/stories/addon-graphql.stories.js +++ b/examples/official-storybook/stories/addon-graphql.stories.js @@ -1,12 +1,17 @@ import React from 'react'; export default { - title: 'Addons|GraphQL', + title: 'Addons/GraphQL', + parameters: { + previewTabs: { + graphiql: { hidden: false, title: 'GraphiQL' }, + }, + }, }; -export const getPikachu = () =>
hello
; +export const GetPikachu = () =>
hello
; -getPikachu.story = { +GetPikachu.story = { name: 'get Pikachu', parameters: { graphiql: { diff --git a/examples/official-storybook/stories/addon-info/EXAMPLE.md b/examples/official-storybook/stories/addon-info/EXAMPLE.md deleted file mode 100644 index fc965844dda0..000000000000 --- a/examples/official-storybook/stories/addon-info/EXAMPLE.md +++ /dev/null @@ -1,3 +0,0 @@ -# external -## markdown -file diff --git a/examples/official-storybook/stories/addon-info/decorators.stories.js b/examples/official-storybook/stories/addon-info/decorators.stories.js deleted file mode 100644 index fc0d763ee9f0..000000000000 --- a/examples/official-storybook/stories/addon-info/decorators.stories.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { withInfo } from '@storybook/addon-info'; -import BaseButton from '../../components/BaseButton'; - -export default { - title: 'Addons|Info/Decorator', - decorators: [withInfo('Info can take options via the global or local decorator as well.')], -}; - -export const useInfo = () => ; -useInfo.story = { name: 'Use Info as story decorator' }; diff --git a/examples/official-storybook/stories/addon-info/forward-ref.stories.js b/examples/official-storybook/stories/addon-info/forward-ref.stories.js deleted file mode 100644 index c4e674be1795..000000000000 --- a/examples/official-storybook/stories/addon-info/forward-ref.stories.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { withInfo } from '@storybook/addon-info'; -import ForwardedRefButton from '../../components/ForwardedRefButton'; -import ForwardedRefButtonWDisplayName from '../../components/ForwardedRefButtonWDisplayName'; - -export default { - title: 'Addons|Info/ForwardRef', - decorators: [withInfo], -}; - -export const displaysCorrectly = () => ; -displaysCorrectly.story = { name: 'Displays forwarded ref components correctly' }; - -export const displayName = () => ( - -); -displayName.story = { name: 'Uses forwardRef displayName if available' }; diff --git a/examples/official-storybook/stories/addon-info/github-issues.js b/examples/official-storybook/stories/addon-info/github-issues.js deleted file mode 100644 index c3832af3fb02..000000000000 --- a/examples/official-storybook/stories/addon-info/github-issues.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { withInfo } from '@storybook/addon-info'; - -const hoc = WrapComponent => ({ ...props }) => ; - -const Input = hoc(() => ); - -const TextArea = hoc(({ children }) => ); - -export default { - title: 'Addons|Info/GitHub issues', - decorators: [withInfo], -}; - -export const issue1814 = () => ( -
- - ); -storiesOf('Addons|Info.GitHub issues', module).addParameters({ storySource: { source: __STORY__, locationsMap: __ADDS_MAP__ } }).addDecorator(withSourceLoader(__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__)).add( +storiesOf('Addons|Info.GitHub issues', module).addParameters({ storySource: { source: __STORY__, locationsMap: __LOCATIONS_MAP__ } }).add( '#1814', withInfo('Allow Duplicate DisplayNames for HOC #1814')(() => (
diff --git a/lib/source-loader/src/server/abstract-syntax-tree/default-options.js b/lib/source-loader/src/abstract-syntax-tree/default-options.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/default-options.js rename to lib/source-loader/src/abstract-syntax-tree/default-options.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/generate-helpers.js b/lib/source-loader/src/abstract-syntax-tree/generate-helpers.js similarity index 82% rename from lib/source-loader/src/server/abstract-syntax-tree/generate-helpers.js rename to lib/source-loader/src/abstract-syntax-tree/generate-helpers.js index 4f1ec7914273..22c875d885a5 100644 --- a/lib/source-loader/src/server/abstract-syntax-tree/generate-helpers.js +++ b/lib/source-loader/src/abstract-syntax-tree/generate-helpers.js @@ -3,7 +3,6 @@ import getParser from './parsers'; import { splitSTORYOF, findAddsMap, - findDependencies, splitExports, popParametersObjectFromDefaultExport, findExportsMap as generateExportsMap, @@ -63,22 +62,20 @@ function prettifyCode(source, { prettierConfig, parser, filepath }) { } } -const STORY_DECORATOR_STATEMENT = - '.addDecorator(withSourceLoader(__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__))'; const ADD_PARAMETERS_STATEMENT = - '.addParameters({ storySource: { source: __STORY__, locationsMap: __ADDS_MAP__ } })'; + '.addParameters({ storySource: { source: __STORY__, locationsMap: __LOCATIONS_MAP__ } })'; const applyExportDecoratorStatement = part => - ` addSourceDecorator(${part}, {__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__});`; + part.declaration.isVariableDeclaration + ? ` ${part.source};` + : ` const ${part.declaration.ident} = ${part.source};`; -export function generateSourceWithDecorators(source, ast, withParameters) { +export function generateSourceWithDecorators(source, ast) { const { comments = [] } = ast; const partsUsingStoryOfToken = splitSTORYOF(ast, source); if (partsUsingStoryOfToken.length > 1) { - const newSource = partsUsingStoryOfToken.join( - (withParameters ? ADD_PARAMETERS_STATEMENT : '') + STORY_DECORATOR_STATEMENT - ); + const newSource = partsUsingStoryOfToken.join(ADD_PARAMETERS_STATEMENT); return { storyOfTokenFound: true, @@ -91,7 +88,7 @@ export function generateSourceWithDecorators(source, ast, withParameters) { const partsUsingExports = splitExports(ast, source); const newSource = partsUsingExports - .map((part, i) => (i % 2 === 0 ? part : applyExportDecoratorStatement(part))) + .map((part, i) => (i % 2 === 0 ? part.source : applyExportDecoratorStatement(part))) .join(''); return { @@ -118,7 +115,7 @@ export function generateAddsMap(ast, storiesOfIdentifiers) { export function generateStoriesLocationsMap(ast, storiesOfIdentifiers) { const usingAddsMap = generateAddsMap(ast, storiesOfIdentifiers); - const { addsMap } = usingAddsMap; + const addsMap = usingAddsMap; if (Object.keys(addsMap).length > 0) { return usingAddsMap; @@ -128,10 +125,6 @@ export function generateStoriesLocationsMap(ast, storiesOfIdentifiers) { return usingExportsMap || usingAddsMap; } -export function generateDependencies(ast) { - return findDependencies(ast); -} - export function generateStorySource({ source, ...options }) { let storySource = source; diff --git a/lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.csf.test.js b/lib/source-loader/src/abstract-syntax-tree/inject-decorator.csf.test.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.csf.test.js rename to lib/source-loader/src/abstract-syntax-tree/inject-decorator.csf.test.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.js b/lib/source-loader/src/abstract-syntax-tree/inject-decorator.js similarity index 75% rename from lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.js rename to lib/source-loader/src/abstract-syntax-tree/inject-decorator.js index f5c994b54684..bef8e44c2294 100644 --- a/lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.js +++ b/lib/source-loader/src/abstract-syntax-tree/inject-decorator.js @@ -6,7 +6,6 @@ import { generateSourceWithoutDecorators, generateStorySource, generateStoriesLocationsMap, - generateDependencies, generateSourcesInExportedParameters, } from './generate-helpers'; @@ -21,7 +20,7 @@ function extendOptions(source, comments, filepath, options) { } function inject(source, filepath, options = {}, log = message => {}) { - const { injectDecorator = true, injectParameters = true, inspectDependencies } = options; + const { injectDecorator = true, injectParameters = false } = options; const obviouslyNotCode = ['md', 'txt', 'json'].includes(options.parser); let parser = null; try { @@ -36,22 +35,18 @@ function inject(source, filepath, options = {}, log = message => {}) { storySource: {}, addsMap: {}, changed: false, - dependencies: [], }; } const ast = parser.parse(source); const { changed, source: cleanedSource, comments, exportTokenFound } = injectDecorator === true - ? generateSourceWithDecorators(source, ast, injectParameters) + ? generateSourceWithDecorators(source, ast) : generateSourceWithoutDecorators(source, ast); const storySource = generateStorySource(extendOptions(source, comments, filepath, options)); const newAst = parser.parse(storySource); - const { dependencies, storiesOfIdentifiers } = inspectDependencies - ? generateDependencies(newAst) - : { dependencies: [], storiesOfIdentifiers: {} }; - const { addsMap, idsToFrameworks } = generateStoriesLocationsMap(newAst, storiesOfIdentifiers); + const addsMap = generateStoriesLocationsMap(newAst, []); let newSource = cleanedSource; if (exportTokenFound) { @@ -68,8 +63,6 @@ function inject(source, filepath, options = {}, log = message => {}) { storySource, addsMap: {}, changed, - dependencies, - idsToFrameworks: idsToFrameworks || {}, }; } @@ -78,8 +71,6 @@ function inject(source, filepath, options = {}, log = message => {}) { storySource, addsMap, changed, - dependencies, - idsToFrameworks: idsToFrameworks || {}, }; } diff --git a/lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.test.js b/lib/source-loader/src/abstract-syntax-tree/inject-decorator.test.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/inject-decorator.test.js rename to lib/source-loader/src/abstract-syntax-tree/inject-decorator.test.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/parse-helpers.js b/lib/source-loader/src/abstract-syntax-tree/parse-helpers.js similarity index 98% rename from lib/source-loader/src/server/abstract-syntax-tree/parse-helpers.js rename to lib/source-loader/src/abstract-syntax-tree/parse-helpers.js index a96ed14b094c..7da7f744c788 100644 --- a/lib/source-loader/src/server/abstract-syntax-tree/parse-helpers.js +++ b/lib/source-loader/src/abstract-syntax-tree/parse-helpers.js @@ -1,4 +1,4 @@ -const { toId } = require('@storybook/router/utils'); +const { toId } = require('@storybook/csf'); const STORIES_OF = 'storiesOf'; diff --git a/lib/source-loader/src/server/abstract-syntax-tree/parsers/index.js b/lib/source-loader/src/abstract-syntax-tree/parsers/index.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/parsers/index.js rename to lib/source-loader/src/abstract-syntax-tree/parsers/index.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-flow.js b/lib/source-loader/src/abstract-syntax-tree/parsers/parser-flow.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-flow.js rename to lib/source-loader/src/abstract-syntax-tree/parsers/parser-flow.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-js.js b/lib/source-loader/src/abstract-syntax-tree/parsers/parser-js.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-js.js rename to lib/source-loader/src/abstract-syntax-tree/parsers/parser-js.js diff --git a/lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-ts.js b/lib/source-loader/src/abstract-syntax-tree/parsers/parser-ts.js similarity index 100% rename from lib/source-loader/src/server/abstract-syntax-tree/parsers/parser-ts.js rename to lib/source-loader/src/abstract-syntax-tree/parsers/parser-ts.js diff --git a/lib/source-loader/src/abstract-syntax-tree/traverse-helpers.js b/lib/source-loader/src/abstract-syntax-tree/traverse-helpers.js new file mode 100644 index 000000000000..adb2712751da --- /dev/null +++ b/lib/source-loader/src/abstract-syntax-tree/traverse-helpers.js @@ -0,0 +1,274 @@ +import { storyNameFromExport, isExportStory } from '@storybook/csf'; +import { handleADD, handleSTORYOF, patchNode, handleExportedName } from './parse-helpers'; + +const estraverse = require('estraverse'); + +export function splitSTORYOF(ast, source) { + let lastIndex = 0; + const parts = [source]; + + estraverse.traverse(ast, { + fallback: 'iteration', + enter: node => { + patchNode(node); + + if (node.type === 'CallExpression') { + lastIndex = handleSTORYOF(node, parts, source, lastIndex); + } + }, + }); + + return parts; +} + +function isFunctionVariable(declarations, includeExclude) { + return ( + declarations && + declarations.length === 1 && + declarations[0].type === 'VariableDeclarator' && + declarations[0].id && + declarations[0].id.name && + declarations[0].init && + ['CallExpression', 'ArrowFunctionExpression', 'FunctionExpression'].includes( + declarations[0].init.type + ) && + isExportStory(declarations[0].id.name, includeExclude) + ); +} + +function isFunctionDeclaration(declaration, includeExclude) { + return ( + declaration.type === 'FunctionDeclaration' && + declaration.id && + declaration.id.name && + isExportStory(declaration.id.name, includeExclude) + ); +} + +function getDescriptor(metaDeclaration, propertyName) { + const property = + metaDeclaration && + metaDeclaration.declaration && + metaDeclaration.declaration.properties.find(p => p.key && p.key.name === propertyName); + if (!property) { + return undefined; + } + + const { type } = property.value; + + switch (type) { + case 'ArrayExpression': + return property.value.elements.map(t => { + if (!['StringLiteral', 'Literal'].includes(t.type)) { + throw new Error(`Unexpected descriptor element: ${t.type}`); + } + return t.value; + }); + case 'Literal': + case 'RegExpLiteral': + return property.value.value; + default: + throw new Error(`Unexpected descriptor: ${type}`); + } +} + +function findIncludeExclude(ast) { + const program = (ast && ast.program) || ast; + const metaDeclaration = + program && + program.body && + program.body.find( + d => + d.type === 'ExportDefaultDeclaration' && + d.declaration.type === 'ObjectExpression' && + (d.declaration.properties || []).length + ); + + const includeStories = getDescriptor(metaDeclaration, 'includeStories'); + const excludeStories = getDescriptor(metaDeclaration, 'excludeStories'); + + return { + includeStories, + excludeStories, + }; +} + +export function splitExports(ast, source) { + const parts = []; + let lastIndex = 0; + + const includeExclude = findIncludeExclude(ast); + + estraverse.traverse(ast, { + fallback: 'iteration', + enter: node => { + patchNode(node); + + const isNamedExport = node.type === 'ExportNamedDeclaration' && node.declaration; + + const isFunctionVariableExport = + isNamedExport && isFunctionVariable(node.declaration.declarations, includeExclude); + const isFunctionDeclarationExport = + isNamedExport && isFunctionDeclaration(node.declaration, includeExclude); + + if (isFunctionDeclarationExport || isFunctionVariableExport) { + const functionNode = isFunctionVariableExport + ? node.declaration.declarations[0].init + : node.declaration; + parts.push({ + source: source.substring(lastIndex, functionNode.start - 1), + }); + parts.push({ + source: source.substring(functionNode.start, functionNode.end), + declaration: { + isVariableDeclaration: isFunctionVariableExport, + ident: isFunctionVariableExport + ? node.declaration.declarations[0].id.name + : functionNode.id.name, + }, + }); + lastIndex = functionNode.end; + } + }, + }); + + if (source.length > lastIndex + 1) parts.push({ source: source.substring(lastIndex + 1) }); + if (parts.length === 1) return [source]; + return parts; +} + +export function findAddsMap(ast, storiesOfIdentifiers) { + const addsMap = {}; + + estraverse.traverse(ast, { + fallback: 'iteration', + enter: (node, parent) => { + patchNode(node); + + if (node.type === 'MemberExpression') { + const { toAdd, idToFramework } = handleADD(node, parent, storiesOfIdentifiers); + Object.assign(addsMap, toAdd); + } + }, + }); + + return addsMap; +} + +export function findExportsMap(ast) { + const addsMap = {}; + const program = (ast && ast.program) || ast; + const metaDeclaration = + program && + program.body && + program.body.find( + d => + d.type === 'ExportDefaultDeclaration' && + d.declaration.type === 'ObjectExpression' && + (d.declaration.properties || []).length + ); + + const titleProperty = + metaDeclaration && + metaDeclaration.declaration && + metaDeclaration.declaration.properties.find(p => p.key && p.key.name === 'title'); + + if (!titleProperty) { + return addsMap; + } + const titleValue = titleProperty.value; + let title; + if (titleValue.type === 'TemplateLiteral' && titleValue.quasis.length > 0) { + // if a tagged template string. + title = titleValue.quasis[0].value.raw; + } else { + // if title is string: 'StringLiteral' + title = titleProperty.value.value; + } + if (title) { + estraverse.traverse(ast, { + fallback: 'iteration', + enter: node => { + patchNode(node); + + const isNamedExport = node.type === 'ExportNamedDeclaration' && node.declaration; + + const isFunctionVariableExport = + isNamedExport && + node.declaration.declarations && + node.declaration.declarations.length === 1 && + node.declaration.declarations[0].type === 'VariableDeclarator' && + node.declaration.declarations[0].id && + node.declaration.declarations[0].id.name && + node.declaration.declarations[0].init && + ['CallExpression', 'ArrowFunctionExpression', 'FunctionExpression'].includes( + node.declaration.declarations[0].init.type + ); + + const isFunctionDeclarationExport = + isNamedExport && + node.declaration.type === 'FunctionDeclaration' && + node.declaration.id && + node.declaration.id.name; + + if (isFunctionDeclarationExport || isFunctionVariableExport) { + const exportDeclaration = isFunctionVariableExport + ? node.declaration.declarations[0] + : node.declaration; + const storyName = storyNameFromExport(exportDeclaration.id.name); + const toAdd = handleExportedName( + title, + storyName, + exportDeclaration.init || exportDeclaration + ); + Object.assign(addsMap, toAdd); + } + }, + }); + } + return addsMap; +} + +export function popParametersObjectFromDefaultExport(source, ast) { + let splicedSource = source; + let parametersSliceOfCode = ''; + let indexWhereToAppend = -1; + let foundParametersProperty = false; + estraverse.traverse(ast, { + fallback: 'iteration', + enter: node => { + patchNode(node); + + if ( + node.type === 'ExportDefaultDeclaration' && + node.declaration.type === 'ObjectExpression' && + (node.declaration.properties || []).length + ) { + const parametersProperty = node.declaration.properties.find( + p => p.key.name === 'parameters' && p.value.type === 'ObjectExpression' + ); + + foundParametersProperty = !!parametersProperty; + if (foundParametersProperty) { + patchNode(parametersProperty.value); + } else { + patchNode(node.declaration); + } + + splicedSource = parametersProperty + ? source.substring(0, parametersProperty.value.start) + + source.substring(parametersProperty.value.end + 1) + : splicedSource; + + parametersSliceOfCode = parametersProperty + ? source.substring(parametersProperty.value.start, parametersProperty.value.end) + : '{}'; + + indexWhereToAppend = parametersProperty + ? parametersProperty.value.start + : node.declaration.start + 1; + } + }, + }); + return { splicedSource, parametersSliceOfCode, indexWhereToAppend, foundParametersProperty }; +} diff --git a/lib/source-loader/src/build.js b/lib/source-loader/src/build.js new file mode 100644 index 000000000000..004e28108dd8 --- /dev/null +++ b/lib/source-loader/src/build.js @@ -0,0 +1,23 @@ +import { readStory } from './dependencies-lookup/readAsObject'; + +export function transform(inputSource) { + return readStory(this, inputSource).then(sourceObject => { + // if source-loader had trouble parsing the story exports, return the original story + // example is + // const myStory = () => xxx + // export { myStory } + if (!sourceObject.source || sourceObject.source.length === 0) { + return inputSource; + } + const { source, sourceJson, addsMap } = sourceObject; + const preamble = ` + /* eslint-disable */ + // @ts-nocheck + // @ts-ignore + var __STORY__ = ${sourceJson}; + // @ts-ignore + var __LOCATIONS_MAP__ = ${JSON.stringify(addsMap)}; + `; + return `${preamble}\n${source}`; + }); +} diff --git a/lib/source-loader/src/client/events.js b/lib/source-loader/src/client/events.js deleted file mode 100644 index ebeb7da51bde..000000000000 --- a/lib/source-loader/src/client/events.js +++ /dev/null @@ -1,2 +0,0 @@ -export const ADDON_ID = 'storybook/source-loader'; -export const STORY_EVENT_ID = `${ADDON_ID}/set`; diff --git a/lib/source-loader/src/client/preview.js b/lib/source-loader/src/client/preview.js deleted file mode 100644 index 7a03d3f576bd..000000000000 --- a/lib/source-loader/src/client/preview.js +++ /dev/null @@ -1,91 +0,0 @@ -import addons from '@storybook/addons'; -import { STORY_EVENT_ID } from './events'; - -const getLocation = (context, locationsMap) => locationsMap[context.id]; - -function sendEvent( - context, - source, - locationsMap, - mainFileLocation, - dependencies, - localDependencies, - prefix, - idsToFrameworks -) { - const channel = addons.getChannel(); - const currentLocation = getLocation(context, locationsMap); - - channel.emit(STORY_EVENT_ID, { - edition: { - source, - mainFileLocation, - dependencies, - localDependencies, - prefix, - idsToFrameworks, - }, - story: { - kind: context.kind, - story: context.story, - }, - location: { - currentLocation, - locationsMap, - }, - }); -} - -export function addSource(storyFn, sourceContext) { - const { - __STORY__: source, - __ADDS_MAP__: locationsMap = {}, - __MAIN_FILE_LOCATION__: mainFileLocation = '/index.js', - __MODULE_DEPENDENCIES__: dependencies = [], - __LOCAL_DEPENDENCIES__: localDependencies = {}, - __SOURCE_PREFIX__: prefix, - __IDS_TO_FRAMEWORKS__: idsToFrameworks, - } = sourceContext; - const decorated = (context = {}) => { - sendEvent( - context, - source, - locationsMap, - mainFileLocation, - dependencies, - localDependencies, - prefix, - idsToFrameworks - ); - if (typeof storyFn === 'function') { - return storyFn(context); - } - return storyFn; - }; - decorated.story = (storyFn || {}).story; - return decorated; -} - -export function withSource( - source, - locationsMap = {}, - mainFileLocation = '/index.js', - dependencies = [], - localDependencies = {}, - prefix, - idsToFrameworks -) { - return (storyFn, context) => { - sendEvent( - context, - source, - locationsMap, - mainFileLocation, - dependencies, - localDependencies, - prefix, - idsToFrameworks - ); - return storyFn(context); - }; -} diff --git a/lib/source-loader/src/dependencies-lookup/readAsObject.js b/lib/source-loader/src/dependencies-lookup/readAsObject.js new file mode 100644 index 000000000000..b8da44d25b3f --- /dev/null +++ b/lib/source-loader/src/dependencies-lookup/readAsObject.js @@ -0,0 +1,34 @@ +import { getOptions } from 'loader-utils'; +import injectDecorator from '../abstract-syntax-tree/inject-decorator'; + +function readAsObject(classLoader, inputSource, mainFile) { + const options = getOptions(classLoader) || {}; + const result = injectDecorator( + inputSource, + classLoader.resourcePath, + { + ...options, + parser: options.parser || classLoader.extension, + }, + classLoader.emitWarning.bind(classLoader) + ); + + const sourceJson = JSON.stringify(result.storySource || inputSource) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029'); + + const addsMap = result.addsMap || {}; + const source = mainFile ? result.source : inputSource; + + return new Promise(resolve => + resolve({ + source, + sourceJson, + addsMap, + }) + ); +} + +export function readStory(classLoader, inputSource) { + return readAsObject(classLoader, inputSource, true); +} diff --git a/lib/source-loader/src/server/index.js b/lib/source-loader/src/index.ts similarity index 61% rename from lib/source-loader/src/server/index.js rename to lib/source-loader/src/index.ts index 42166bb122ce..ce664491534a 100644 --- a/lib/source-loader/src/server/index.js +++ b/lib/source-loader/src/index.ts @@ -1,3 +1,6 @@ +// @ts-ignore import { transform } from './build'; export default transform; + +export * from './types'; diff --git a/lib/source-loader/src/server/__snapshots__/build.test.js.snap b/lib/source-loader/src/server/__snapshots__/build.test.js.snap deleted file mode 100644 index 1e30b863dd27..000000000000 --- a/lib/source-loader/src/server/__snapshots__/build.test.js.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`insertAfterImports addon-notes 1`] = ` -"import React from 'react'; - -import BaseButton from '../components/BaseButton'; -import markdownNotes from './notes/notes.md'; -INSERT - - - -const markdownString = '...';" -`; - -exports[`insertAfterImports imports 1`] = ` -"import './foo'; -import './bar'; -INSERT - - -whatever;" -`; - -exports[`insertAfterImports multi-line imports 1`] = ` -"import 'foo'; -import { - bar -} from 'baz'; -INSERT - - -whatever;" -`; - -exports[`insertAfterImports no imports 1`] = ` -" -INSERT - -foo bar; -baz;" -`; - -exports[`insertAfterImports single-line imports 1`] = ` -"import 'foo'; -import { bar } from 'baz'; -INSERT - - -whatever;" -`; diff --git a/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.csf.test.js.snap b/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.csf.test.js.snap deleted file mode 100644 index 3721312e035d..000000000000 --- a/lib/source-loader/src/server/abstract-syntax-tree/__snapshots__/inject-decorator.csf.test.js.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`inject-decorator positive - ts - csf includes storySource parameter in the default exported object 1`] = ` -"import React from \\"react\\"; -import { action } from \\"@storybook/addon-actions\\"; -import { Button } from \\"@storybook/react/demo\\"; - -export default {parameters: {\\"storySource\\":{\\"source\\":\\"import React from \\\\\\"react\\\\\\";\\\\nimport { action } from \\\\\\"@storybook/addon-actions\\\\\\";\\\\nimport { Button } from \\\\\\"@storybook/react/demo\\\\\\";\\\\n\\\\nexport default {\\\\n title: \\\\\\"Button\\\\\\"\\\\n};\\\\n\\\\nexport const text = () => (\\\\n \\\\n);\\\\n\\\\nexport const emoji = () => (\\\\n \\\\n);\\\\n\\",\\"locationsMap\\":{\\"button--text\\":{\\"startLoc\\":{\\"col\\":20,\\"line\\":9},\\"endLoc\\":{\\"col\\":1,\\"line\\":11},\\"startBody\\":{\\"col\\":20,\\"line\\":9},\\"endBody\\":{\\"col\\":1,\\"line\\":11}},\\"button--emoji\\":{\\"startLoc\\":{\\"col\\":21,\\"line\\":13},\\"endLoc\\":{\\"col\\":1,\\"line\\":19},\\"startBody\\":{\\"col\\":21,\\"line\\":13},\\"endBody\\":{\\"col\\":1,\\"line\\":19}}}},}, - title: \\"Button\\" -}; - -export const text = addSourceDecorator(() => ( - -), {__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__});; - -export const emoji = addSourceDecorator(() => ( - -), {__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__}); -" -`; diff --git a/lib/source-loader/src/server/abstract-syntax-tree/traverse-helpers.js b/lib/source-loader/src/server/abstract-syntax-tree/traverse-helpers.js deleted file mode 100644 index 744e8dc5f76e..000000000000 --- a/lib/source-loader/src/server/abstract-syntax-tree/traverse-helpers.js +++ /dev/null @@ -1,210 +0,0 @@ -import { storyNameFromExport } from '@storybook/router/utils'; -import { handleADD, handleSTORYOF, patchNode, handleExportedName } from './parse-helpers'; - -const estraverse = require('estraverse'); - -export function splitSTORYOF(ast, source) { - let lastIndex = 0; - const parts = [source]; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - - if (node.type === 'CallExpression') { - lastIndex = handleSTORYOF(node, parts, source, lastIndex); - } - }, - }); - - return parts; -} -export function splitExports(ast, source) { - const parts = []; - let lastIndex = 0; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - if ( - node.type === 'ExportNamedDeclaration' && - node.declaration && - node.declaration.declarations && - node.declaration.declarations.length === 1 && - node.declaration.declarations[0].type === 'VariableDeclarator' && - node.declaration.declarations[0].id && - node.declaration.declarations[0].id.name && - node.declaration.declarations[0].init && - ['CallExpression', 'ArrowFunctionExpression', 'FunctionExpression'].includes( - node.declaration.declarations[0].init.type - ) - ) { - const functionNode = node.declaration.declarations[0].init; - parts.push(source.substring(lastIndex, functionNode.start - 1)); - parts.push(source.substring(functionNode.start, functionNode.end)); - lastIndex = functionNode.end; - } - }, - }); - - if (source.length > lastIndex + 1) parts.push(source.substring(lastIndex + 1)); - if (parts.length === 1) return [source]; - return parts; -} - -export function findAddsMap(ast, storiesOfIdentifiers) { - const addsMap = {}; - const idsToFrameworks = {}; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: (node, parent) => { - patchNode(node); - - if (node.type === 'MemberExpression') { - const { toAdd, idToFramework } = handleADD(node, parent, storiesOfIdentifiers); - Object.assign(addsMap, toAdd); - Object.assign(idsToFrameworks, idToFramework); - } - }, - }); - - return { addsMap, idsToFrameworks }; -} - -export function findExportsMap(ast) { - const addsMap = {}; - const idsToFrameworks = {}; - const program = (ast && ast.program) || ast; - const metaDeclaration = - program && - program.body && - program.body.find( - d => - d.type === 'ExportDefaultDeclaration' && - d.declaration.type === 'ObjectExpression' && - (d.declaration.properties || []).length - ); - - const titleProperty = - metaDeclaration && - metaDeclaration.declaration && - metaDeclaration.declaration.properties.find(p => p.key && p.key.name === 'title'); - - if (!titleProperty) { - return { addsMap, idsToFrameworks }; - } - const title = titleProperty.value.value; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - if ( - node.type === 'ExportNamedDeclaration' && - node.declaration && - node.declaration.declarations && - node.declaration.declarations.length === 1 && - node.declaration.declarations[0].type === 'VariableDeclarator' && - node.declaration.declarations[0].id && - node.declaration.declarations[0].id.name && - node.declaration.declarations[0].init && - ['CallExpression', 'ArrowFunctionExpression', 'FunctionExpression'].includes( - node.declaration.declarations[0].init.type - ) - ) { - const storyName = storyNameFromExport(node.declaration.declarations[0].id.name); - const toAdd = handleExportedName(title, storyName, node.declaration.declarations[0].init); - Object.assign(addsMap, toAdd); - } - }, - }); - return { addsMap, idsToFrameworks }; -} - -export function findDependencies(ast) { - const dependencies = []; - const storiesOfIdentifiers = {}; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - - if (node.type === 'ImportDeclaration') { - const candidateSpecifier = (node.specifiers || []).find( - s => (s.imported || {}).name === 'storiesOf' - ); - if (node.source.value.startsWith('@storybook/') && candidateSpecifier) { - Object.assign(storiesOfIdentifiers, { - [candidateSpecifier.local.name]: node.source.value, - }); - } - dependencies.push(node.source.value); - } - }, - }); - return { dependencies, storiesOfIdentifiers }; -} - -export function endOfImports(ast) { - let end = 0; - - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - - if (node.type === 'ImportDeclaration') { - end = Math.max(node.end, end); - } - }, - }); - return end; -} - -export function popParametersObjectFromDefaultExport(source, ast) { - let splicedSource = source; - let parametersSliceOfCode = ''; - let indexWhereToAppend = -1; - let foundParametersProperty = false; - estraverse.traverse(ast, { - fallback: 'iteration', - enter: node => { - patchNode(node); - - if ( - node.type === 'ExportDefaultDeclaration' && - node.declaration.type === 'ObjectExpression' && - (node.declaration.properties || []).length - ) { - const parametersProperty = node.declaration.properties.find( - p => p.key.name === 'parameters' && p.value.type === 'ObjectExpression' - ); - - foundParametersProperty = !!parametersProperty; - if (foundParametersProperty) { - patchNode(parametersProperty.value); - } else { - patchNode(node.declaration); - } - - splicedSource = parametersProperty - ? source.substring(0, parametersProperty.value.start) + - source.substring(parametersProperty.value.end + 1) - : splicedSource; - - parametersSliceOfCode = parametersProperty - ? source.substring(parametersProperty.value.start, parametersProperty.value.end) - : '{}'; - - indexWhereToAppend = parametersProperty - ? parametersProperty.value.start - : node.declaration.start + 1; - } - }, - }); - return { splicedSource, parametersSliceOfCode, indexWhereToAppend, foundParametersProperty }; -} diff --git a/lib/source-loader/src/server/build.js b/lib/source-loader/src/server/build.js deleted file mode 100644 index 50999c595355..000000000000 --- a/lib/source-loader/src/server/build.js +++ /dev/null @@ -1,65 +0,0 @@ -import { getOptions } from 'loader-utils'; -import { readStory } from './dependencies-lookup/readAsObject'; -import { getRidOfUselessFilePrefixes } from './dependencies-lookup/getRidOfUselessFilePrefixes'; -import getParser from './abstract-syntax-tree/parsers'; -import { endOfImports } from './abstract-syntax-tree/traverse-helpers'; - -export function insertAfterImports(classLoader, insert, source) { - const options = getOptions(classLoader) || {}; - let ast; - try { - ast = getParser(options.parser || classLoader.extension || 'javascript').parse(source); - } catch (e) { - // if not working, then we will fallback to not adding anything - // perhaps the code was not written in javascript - return source; - } - if (!ast) return `${insert}${source}`; - const endOfImportsIndex = endOfImports(ast); - const result = `${source.substring(0, endOfImportsIndex)}\n${insert}\n${source.substring( - endOfImportsIndex - )}`; - return result; -} - -export function transform(inputSource) { - return readStory(this, inputSource) - .then(getRidOfUselessFilePrefixes) - .then( - ({ - prefix, - resource, - source, - sourceJson, - addsMap, - dependencies, - localDependencies, - idsToFrameworks, - }) => { - const preamble = ` -/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */ -// @ts-ignore -var withSourceLoader = require('@storybook/source-loader/preview').withSource; -// @ts-ignore -var addSourceDecorator = require("@storybook/source-loader/preview").addSource; -// @ts-ignore -var __SOURCE_PREFIX__ = "${prefix.replace(/\\([^\\ ])/g, '\\\\$1')}"; -// @ts-ignore -var __STORY__ = ${sourceJson}; -// @ts-ignore -var __ADDS_MAP__ = ${JSON.stringify(addsMap)}; -// @ts-ignore -var __MAIN_FILE_LOCATION__ = ${JSON.stringify(resource)}; -// @ts-ignore -var __MODULE_DEPENDENCIES__ = ${JSON.stringify(dependencies)}; -// @ts-ignore -var __LOCAL_DEPENDENCIES__ = ${JSON.stringify(localDependencies)}; -// @ts-ignore -var __IDS_TO_FRAMEWORKS__ = ${JSON.stringify(idsToFrameworks)}; -/* eslint-enable no-unused-vars,@typescript-eslint/no-unused-vars */ - `; - return insertAfterImports(this, preamble, source); - // return `${preamble}${source}`; - } - ); -} diff --git a/lib/source-loader/src/server/build.test.js b/lib/source-loader/src/server/build.test.js deleted file mode 100644 index 7b4a15fb73a4..000000000000 --- a/lib/source-loader/src/server/build.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { insertAfterImports } from './build'; - -const insert = 'INSERT\n'; - -describe('insertAfterImports', () => { - it('no imports', () => { - const noImports = ` -foo bar; -baz; - `.trim(); - expect(insertAfterImports({}, insert, noImports)).toMatchSnapshot(); - }); - - it('imports', () => { - const hasImports = ` -import './foo'; -import './bar'; -whatever; - `.trim(); - expect(insertAfterImports({}, insert, hasImports)).toMatchSnapshot(); - }); - - it('single-line imports', () => { - const hasImports = ` -import 'foo'; -import { bar } from 'baz'; -whatever; - `.trim(); - expect(insertAfterImports({}, insert, hasImports)).toMatchSnapshot(); - }); - - it('multi-line imports', () => { - const hasImports = ` -import 'foo'; -import { - bar -} from 'baz'; -whatever; - `.trim(); - expect(insertAfterImports({}, insert, hasImports)).toMatchSnapshot(); - }); - - it('addon-notes', () => { - const notesStory = ` - import React from 'react'; - -import BaseButton from '../components/BaseButton'; -import markdownNotes from './notes/notes.md'; - -const markdownString = '...'; - `.trim(); - expect(insertAfterImports({}, insert, notesStory)).toMatchSnapshot(); - }); -}); diff --git a/lib/source-loader/src/server/dependencies-lookup/getRidOfUselessFilePrefixes.js b/lib/source-loader/src/server/dependencies-lookup/getRidOfUselessFilePrefixes.js deleted file mode 100644 index 1f0ca22ce8e1..000000000000 --- a/lib/source-loader/src/server/dependencies-lookup/getRidOfUselessFilePrefixes.js +++ /dev/null @@ -1,46 +0,0 @@ -import path from 'path'; - -function commonDir(...resources) { - const firstResource = (resources[0] || '').split(path.sep); - let i = 1; - while ( - i < firstResource.length && - // eslint-disable-next-line no-loop-func - resources.every(resource => resource.startsWith(firstResource.slice(0, i).join(path.sep))) - ) { - i += 1; - } - return firstResource.slice(0, i - 1).join(path.sep); -} - -export function getRidOfUselessFilePrefixes({ - resource, - source, - sourceJson, - addsMap, - dependencies, - localDependencies, - idsToFrameworks, -}) { - const commondir = commonDir(resource, ...Object.keys(localDependencies || {})); - return { - prefix: commondir, - source, - sourceJson, - addsMap, - dependencies, - idsToFrameworks, - resource: - commondir === resource - ? '/index.js' - : resource.substring(commondir.length).replace(path.sep === '\\' ? /\\/g : /\//g, '/'), - localDependencies: Object.assign( - {}, - ...Object.entries(localDependencies || {}).map(([depFileName, dependency]) => ({ - [depFileName - .substring(commondir.length) - .replace(new RegExp(path.sep === '\\' ? /\\/g : /\//g, 'g'), '/')]: dependency, - })) - ), - }; -} diff --git a/lib/source-loader/src/server/dependencies-lookup/readAsObject.js b/lib/source-loader/src/server/dependencies-lookup/readAsObject.js deleted file mode 100644 index 4e8f11c0852d..000000000000 --- a/lib/source-loader/src/server/dependencies-lookup/readAsObject.js +++ /dev/null @@ -1,121 +0,0 @@ -import { getOptions } from 'loader-utils'; -import path from 'path'; -import injectDecorator from '../abstract-syntax-tree/inject-decorator'; - -function extractDependenciesFrom(tree) { - return !Object.entries(tree || {}).length - ? [] - : Object.entries(tree) - .map(([, value]) => - (value.dependencies || []).concat(extractDependenciesFrom(value.localDependencies)) - ) - .reduce((acc, value) => acc.concat(value), []); -} - -function extractLocalDependenciesFrom(tree) { - return Object.assign( - {}, - ...Object.entries(tree || {}).map(([thisPath, value]) => ({ - [thisPath]: { code: value.source || value.code }, - ...extractLocalDependenciesFrom(value.localDependencies), - })) - ); -} - -function readAsObject(classLoader, inputSource, mainFile) { - const options = getOptions(classLoader) || {}; - const { inspectLocalDependencies } = options; - const result = injectDecorator( - inputSource, - classLoader.resourcePath, - { - ...options, - parser: options.parser || classLoader.extension, - }, - classLoader.emitWarning.bind(classLoader) - ); - - const sourceJson = JSON.stringify(result.storySource || inputSource) - .replace(/\u2028/g, '\\u2028') - .replace(/\u2029/g, '\\u2029'); - - const addsMap = result.addsMap || {}; - const dependencies = result.dependencies || []; - const source = mainFile ? result.source : inputSource; - const idsToFrameworks = result.idsToFrameworks || {}; - const resource = classLoader.resourcePath || classLoader.resource; - - const moduleDependencies = (result.dependencies || []).filter(d => d[0] === '.' || d[0] === '/'); - const workspaceFileNames = inspectLocalDependencies - ? moduleDependencies.map(d => path.join(path.dirname(resource), d)) - : []; - - return Promise.all( - workspaceFileNames.map( - d => - new Promise(resolve => - classLoader.loadModule(d, (err1, compiledSource, sourceMap, theModule) => { - if (err1) { - classLoader.emitError(err1); - } - classLoader.fs.readFile(theModule.resource, (err2, dependencyInputData) => { - if (err2) { - classLoader.emitError(err2); - } - resolve({ - d, - err: err1 || err2, - inputSource: dependencyInputData.toString(), - compiledSource, - sourceMap, - theModule, - }); - }); - }) - ) - ) - ) - .then(data => - Promise.all( - data.map(({ inputSource: dependencyInputSource, theModule }) => - readAsObject( - { - ...classLoader, - resourcePath: theModule.resourcePath, - resource: theModule.resource, - extension: (theModule.resource || '').split('.').slice(-1)[0], - }, - dependencyInputSource - ) - ) - ).then(moduleObjects => - Object.assign( - {}, - ...moduleObjects.map(asObject => ({ - [asObject.resource]: asObject, - })) - ) - ) - ) - .then(localDependencies => ({ - resource, - source, - sourceJson, - addsMap, - idsToFrameworks, - dependencies: dependencies - .concat(extractDependenciesFrom(localDependencies)) - .filter(d => d[0] !== '.' && d[0] !== '/') - .map(d => (d[0] === '@' ? `${d.split('/')[0]}/${d.split('/')[1]}` : d.split('/')[0])), - localDependencies: Object.assign( - ...Object.entries(localDependencies).map(([name, value]) => ({ - [name]: { code: value.source }, - })), - extractLocalDependenciesFrom(localDependencies) - ), - })); -} - -export function readStory(classLoader, inputSource) { - return readAsObject(classLoader, inputSource, true); -} diff --git a/lib/source-loader/src/types.ts b/lib/source-loader/src/types.ts new file mode 100644 index 000000000000..0319646364a0 --- /dev/null +++ b/lib/source-loader/src/types.ts @@ -0,0 +1,13 @@ +export interface SourceLoc { + line: number; + col: number; +} + +export interface SourceBlock { + startLoc: SourceLoc; + endLoc: SourceLoc; +} + +export interface LocationsMap { + [key: string]: SourceBlock; +} diff --git a/addons/ondevice-notes/tsconfig.json b/lib/source-loader/tsconfig.json similarity index 90% rename from addons/ondevice-notes/tsconfig.json rename to lib/source-loader/tsconfig.json index 24ef9bc68916..48a78502378b 100644 --- a/addons/ondevice-notes/tsconfig.json +++ b/lib/source-loader/tsconfig.json @@ -1,13 +1,14 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/__tests__/**/*" - ] -} +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/tests/**/*", + "src/__tests__/**/*" + ] +} diff --git a/lib/theming/package.json b/lib/theming/package.json index 5696a16e0e7f..456a79c9f783 100644 --- a/lib/theming/package.json +++ b/lib/theming/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/theming", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Core Storybook Components", "keywords": [ "storybook" @@ -28,8 +28,9 @@ }, "dependencies": { "@emotion/core": "^10.0.20", + "@emotion/is-prop-valid": "^0.8.6", "@emotion/styled": "^10.0.17", - "@storybook/client-logger": "5.3.0-alpha.41", + "@storybook/client-logger": "6.0.0-alpha.2", "core-js": "^3.0.1", "deep-object-diff": "^1.1.0", "emotion-theming": "^10.0.19", @@ -46,5 +47,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/lib/theming/src/index.ts b/lib/theming/src/index.ts index 4a8dd64f646e..7c6760d5ee45 100644 --- a/lib/theming/src/index.ts +++ b/lib/theming/src/index.ts @@ -8,6 +8,7 @@ export * from './types'; export * from '@emotion/core'; export * from 'emotion-theming'; +export { default as isPropValid } from '@emotion/is-prop-valid'; export { createGlobal, createReset } from './global'; export * from './create'; diff --git a/lib/theming/tsconfig.json b/lib/theming/tsconfig.json index 8876bb6737a1..30079a2f7435 100644 --- a/lib/theming/tsconfig.json +++ b/lib/theming/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./src", - "types": ["webpack-env"] + "rootDir": "./src" }, "include": [ "src/**/*" diff --git a/lib/ui/README.md b/lib/ui/README.md index adf4b6925344..92d6ce188024 100644 --- a/lib/ui/README.md +++ b/lib/ui/README.md @@ -173,7 +173,7 @@ TODO: state we use reach/router customized to query params ### Story Order -Stories are sorted in the order in which they were imported. This can be overridden by adding storySort to the Parameters for the stories in `.storybook/config.js`: +Stories are sorted in the order in which they were imported. This can be overridden by adding storySort to the Parameters for the stories in `.storybook/preview.js`: ```js addParameters({ diff --git a/lib/ui/package.json b/lib/ui/package.json index 0707496b8415..5ae0a6000c75 100644 --- a/lib/ui/package.json +++ b/lib/ui/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ui", - "version": "5.3.0-alpha.41", + "version": "6.0.0-alpha.2", "description": "Core Storybook UI", "keywords": [ "storybook" @@ -28,20 +28,20 @@ }, "dependencies": { "@emotion/core": "^10.0.20", - "@storybook/addons": "5.3.0-alpha.41", - "@storybook/api": "5.3.0-alpha.41", - "@storybook/channels": "5.3.0-alpha.41", - "@storybook/client-logger": "5.3.0-alpha.41", - "@storybook/components": "5.3.0-alpha.41", - "@storybook/core-events": "5.3.0-alpha.41", - "@storybook/router": "5.3.0-alpha.41", - "@storybook/theming": "5.3.0-alpha.41", + "@storybook/addons": "6.0.0-alpha.2", + "@storybook/api": "6.0.0-alpha.2", + "@storybook/channels": "6.0.0-alpha.2", + "@storybook/client-logger": "6.0.0-alpha.2", + "@storybook/components": "6.0.0-alpha.2", + "@storybook/core-events": "6.0.0-alpha.2", + "@storybook/router": "6.0.0-alpha.2", + "@storybook/theming": "6.0.0-alpha.2", "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", "core-js-pure": "^3.0.1", "emotion-theming": "^10.0.19", "fast-deep-equal": "^2.0.1", - "fuse.js": "^3.4.4", + "fuse.js": "^3.4.6", "global": "^4.3.2", "lodash": "^4.17.15", "markdown-to-jsx": "^6.9.3", @@ -59,18 +59,19 @@ "resolve-from": "^5.0.0", "semver": "^6.0.0", "store2": "^2.7.1", - "telejson": "^3.0.3", + "telejson": "^3.2.0", "util-deprecate": "^1.0.2" }, "devDependencies": { - "@storybook/addon-actions": "5.3.0-alpha.41", - "@storybook/addon-knobs": "5.3.0-alpha.41", - "corejs-upgrade-webpack-plugin": "^2.2.0", + "@storybook/addon-actions": "6.0.0-alpha.2", + "@storybook/addon-knobs": "6.0.0-alpha.2", + "corejs-upgrade-webpack-plugin": "^3.0.1", "flush-promises": "^1.0.2", "terser-webpack-plugin": "^2.1.2", "webpack": "^4.33.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" } diff --git a/lib/ui/src/app.js b/lib/ui/src/app.js index 2023efb4f5f1..39459c5437a8 100644 --- a/lib/ui/src/app.js +++ b/lib/ui/src/app.js @@ -41,7 +41,7 @@ const View = styled.div({ width: '100vw', }); -const App = React.memo(({ viewMode, layout, panelCount, size: { width, height } }) => { +const App = React.memo(({ viewMode, docsOnly, layout, panelCount, size: { width, height } }) => { const props = createProps(); let content; @@ -60,6 +60,7 @@ const App = React.memo(({ viewMode, layout, panelCount, size: { width, height } {...props} viewMode={viewMode} options={layout} + docsOnly={docsOnly} {...{ width, height }} panelCount={panelCount} /> @@ -74,16 +75,18 @@ const App = React.memo(({ viewMode, layout, panelCount, size: { width, height } ); }); App.propTypes = { - viewMode: PropTypes.oneOf(['story', 'info', 'docs', 'settings']), + viewMode: PropTypes.string, panelCount: PropTypes.number.isRequired, layout: PropTypes.shape({}).isRequired, size: PropTypes.shape({ width: PropTypes.number, height: PropTypes.number, }).isRequired, + docsOnly: PropTypes.bool, }; App.defaultProps = { viewMode: undefined, + docsOnly: false, }; const SizedApp = sizeMe({ monitorHeight: true })(App); diff --git a/lib/ui/src/app.stories.js b/lib/ui/src/app.stories.js index db6eb8e57c28..d4c5b163d891 100644 --- a/lib/ui/src/app.stories.js +++ b/lib/ui/src/app.stories.js @@ -1,9 +1,16 @@ import React from 'react'; +import { styled } from '@storybook/theming'; import { storiesOf } from '@storybook/react'; import addons from '@storybook/addons'; import { Root as App, Provider } from './index'; +const CustomApp = styled.div({ + '#preview-loader': { + display: 'none', + }, +}); + class FakeProvider extends Provider { constructor() { super(); @@ -34,8 +41,19 @@ class FakeProvider extends Provider { } } -storiesOf('UI|Layout/App', module) +class FakeLoadingProvider extends FakeProvider { + renderPreview() { + return

Switch between Desktop and Mobile viewport to see how the loading state behaves.

; + } +} + +storiesOf('UI/Layout/App', module) .addParameters({ component: App, }) - .add('default', () => ); + .add('default', () => ( + + + + )) + .add('loading state', () => ); diff --git a/lib/ui/src/components/layout/container.js b/lib/ui/src/components/layout/container.js index 2deb7e16ecc1..72fbf2cf33fc 100644 --- a/lib/ui/src/components/layout/container.js +++ b/lib/ui/src/components/layout/container.js @@ -88,7 +88,7 @@ const Paper = styled.div( isFullscreen ? { boxShadow: 'none', - borderRadius: '0', + borderRadius: 0, } : { background: theme.background.content, @@ -405,13 +405,17 @@ class Layout extends Component { }; render() { - const { children, bounds, options, theme, viewMode, panelCount } = this.props; + const { children, bounds, options, theme, viewMode, docsOnly, panelCount } = this.props; const { isDragging, resizerNav, resizerPanel } = this.state; const margin = theme.layoutMargin; const isNavHidden = options.isFullscreen || !options.showNav; const isPanelHidden = - options.isFullscreen || !options.showPanel || viewMode !== 'story' || panelCount === 0; + options.isFullscreen || + !options.showPanel || + docsOnly || + viewMode !== 'story' || + panelCount === 0; const isFullscreen = options.isFullscreen || (isNavHidden && isPanelHidden); const { isToolshown } = options; @@ -494,6 +498,7 @@ class Layout extends Component { }, previewProps: { viewMode, + docsOnly, animate: !isDragging, isFullscreen, isToolshown, @@ -553,11 +558,13 @@ Layout.propTypes = { panelPosition: PropTypes.string.isRequired, isToolshown: PropTypes.bool.isRequired, }).isRequired, - viewMode: PropTypes.oneOf(['story', 'info', 'docs', 'settings']), + viewMode: PropTypes.string, + docsOnly: PropTypes.bool, theme: PropTypes.shape({ layoutMargin: PropTypes.number }).isRequired, }; Layout.defaultProps = { viewMode: undefined, + docsOnly: false, }; const ThemedLayout = withTheme(Layout); diff --git a/lib/ui/src/components/layout/desktop.js b/lib/ui/src/components/layout/desktop.js index 60dc6075f72a..b9c4f52ad9c6 100644 --- a/lib/ui/src/components/layout/desktop.js +++ b/lib/ui/src/components/layout/desktop.js @@ -4,7 +4,19 @@ import PropTypes from 'prop-types'; import * as S from './container'; const Desktop = React.memo( - ({ Panel, Nav, Preview, Notifications, pages, options, viewMode, width, height, panelCount }) => ( + ({ + Panel, + Nav, + Preview, + Notifications, + pages, + options, + viewMode, + width, + height, + panelCount, + docsOnly, + }) => ( {({ navProps, mainProps, panelProps, previewProps }) => ( @@ -30,7 +43,7 @@ const Desktop = React.memo( -