diff --git a/.circleci/config.yml b/.circleci/config.yml index ed8d38cfe754..211098ee8415 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,7 +51,55 @@ 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 polymer-cli example + command: yarn chromatic --storybook-build-dir="built-storybooks/polymer-cli" --app-code="o6jl9kmh0qd" + - run: + name: Run chromatic on the pre-built preact-kitchen-sink example + command: yarn chromatic --storybook-build-dir="built-storybooks/preact-kitchen-sink" --app-code="ls0ikhnwqt" + - run: + name: Run chromatic on the pre-built rax-kitchen-sink example + command: yarn chromatic --storybook-build-dir="built-storybooks/rax-kitchen-sink" --app-code="4co6vptx8qo" + - run: + name: Run chromatic on the pre-built riot-kitchen-sink example + command: yarn chromatic --storybook-build-dir="built-storybooks/riot-kitchen-sink" --app-code="g2dp3lnr34a" + - run: + name: Run chromatic on the pre-built svelte-kitchen-sink example + command: yarn chromatic --storybook-build-dir="built-storybooks/svelte-kitchen-sink" --app-code="8ob73wgl995" + - run: + name: Run chromatic on the pre-built vue-kitchen-sink example + command: yarn chromatic --storybook-build-dir="built-storybooks/vue-kitchen-sink" --app-code="cyxj0e38bqj" + - 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 diff --git a/.eslintignore b/.eslintignore index 4b9216815225..ad542e107759 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,8 +15,9 @@ 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/* !.remarkrc.js !.babelrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 37080fa43ba5..3c53e6faad79 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,25 +1,21 @@ -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', 'react/sort-comp': [ - error, + 'error', { order: [ 'staticLifecycle', @@ -38,7 +34,7 @@ module.exports = { }, }, ], - 'max-classes-per-file': ignore, + 'max-classes-per-file': 'off', }, overrides: [ { @@ -51,30 +47,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/.github/automention.yml b/.github/automention.yml index 177c7086be99..4e3688c40d4e 100644 --- a/.github/automention.yml +++ b/.github/automention.yml @@ -7,7 +7,7 @@ 'app: react-native': ['benoitdion', 'gongreg'] 'app: react-native-server': ['benoitdion', 'gongreg'] '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..ffe460ac8a41 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ node_modules *.log .idea *.iml -.vscode +.vscode/* +!.vscode/launch.json *.sw* npm-shrinkwrap.json dist diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000000..50ef06713aca --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // 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": [ + "/**" + ] + }, + ] +} \ No newline at end of file diff --git a/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index 167feddffcf0..a87bcfb68bc5 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -12,7 +12,7 @@ |[graphql](addons/graphql) |+| | | | | | | | | | | | |[google-analytics](addons/google-analytics) |+|+|+|+|+|+|+|+|+|+|+|+| |[info](addons/info) |+| | | | | | | | | | | | -|[jest](addons/jest) |+| | |+| | |+| | | | | | +|[jest](addons/jest) |+|+|+|+|+|+|+|+|+|+|+|+| |[knobs](addons/knobs) |+|+*|+|+|+|+|+|+|+|+|+|+| |[links](addons/links) |+|+|+|+|+|+|+| |+|+|+|+| |[notes](addons/notes) |+|+*|+|+|+|+|+| |+|+|+|+| diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a80a59e9563..5e34b8bb52cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,414 @@ +## 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5235cf76da43..2b591ce9d790 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 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 1f421b9a6e60..ae7149b87b86 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -2,12 +2,13 @@ - [Migration](#migration) - [From version 5.2.x to 5.3.x](#from-version-52x-to-53x) + - [To tri-config configuration](#to-triconfig-configuration) - [Create React App preset](#create-react-app-preset) - [Description doc block](#description-doc-block) - [React Native Async Storage](#react-native-async-storage) - [Deprecate displayName parameter](#deprecate-displayname-parameter) - [Unified docs preset](#unified-docs-preset) - - [Simplified hierarchy separators](#simplified-heirarchy-separators) + - [Simplified hierarchy separators](#simplified-hierarchy-separators) - [From version 5.1.x to 5.2.x](#from-version-51x-to-52x) - [Source-loader](#source-loader) - [Default viewports](#default-viewports) @@ -74,8 +75,76 @@ - [Packages renaming](#packages-renaming) - [Deprecated embedded addons](#deprecated-embedded-addons) + ## From version 5.2.x to 5.3.x +### To tri-config 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*): + +- `config.js` has been renamed to `preview.js`. +- `addons.js` has been renamed to `manager.js`. +- `presets.js` has been renamed to `main.js`. + +#### 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-docs/register', + ], +}; +``` + +You remove all "register" import from `addons.js` and place them inside the array. 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'], + presets: ['@storybook/addon-docs/preset'], +}; +``` + +#### 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({ + showRoots: true, + 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! + ### 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. @@ -100,14 +169,14 @@ 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 }); ``` @@ -125,15 +194,19 @@ Addon-docs configuration gets simpler in 5.3. In 5.2, each framework had its own 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 will soon provide) a codemod that can be used to rename all your components. +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" behaviour, use the `showRoots: true` option to re-enable roots: +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 behaviour, simply add a root called "Others" to all your previously unrooted stories. +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. ## From version 5.1.x to 5.2.x @@ -183,7 +256,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/__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 d4d2638a9e6c..aa141c5c94c1 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,26 +58,32 @@ import { storiesOf, addDecorator, addParameters } from '@storybook/react'; import { withA11y } from '@storybook/addon-a11y'; -addDecorator(withA11y) -addParameters({ - a11y: { - 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: {}, + }, }, -}); - -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 cfe07b220cdc..772ba9dfdadf 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "a11y addon for storybook", "keywords": [ "a11y", @@ -33,12 +33,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "axe-core": "^3.3.2", "core-js": "^3.0.1", "global": "^4.3.2", @@ -55,5 +55,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index 194acca7be6d..a2d7a7fc73d8 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -22,9 +22,9 @@ export enum RuleType { const Icon = styled(Icons)( { - height: '12px', - width: '12px', - marginRight: '4px', + height: 12, + width: 12, + marginRight: 4, }, ({ status, theme }: any) => status === 'running' diff --git a/addons/a11y/src/components/ColorBlindness.tsx b/addons/a11y/src/components/ColorBlindness.tsx index 9d643a797f4a..35edc2bd15c4 100644 --- a/addons/a11y/src/components/ColorBlindness.tsx +++ b/addons/a11y/src/components/ColorBlindness.tsx @@ -87,9 +87,7 @@ export const ColorBlindness: FunctionComponent = () => { 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..2aa554641e39 100644 --- a/addons/a11y/src/components/Report/HighlightToggle.tsx +++ b/addons/a11y/src/components/Report/HighlightToggle.tsx @@ -185,7 +185,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..48fe2dd924aa 100644 --- a/addons/a11y/src/components/Report/Rules.tsx +++ b/addons/a11y/src/components/Report/Rules.tsx @@ -16,9 +16,9 @@ const impactColors = { const List = styled.div({ display: 'flex', flexDirection: 'column', - paddingBottom: '4px', - paddingRight: '4px', - paddingTop: '4px', + paddingBottom: 4, + paddingRight: 4, + paddingTop: 4, fontWeight: '400', } as any); @@ -26,23 +26,23 @@ const Item = styled.div(({ elementWidth }: { elementWidth: number }) => { 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 }) => ({ 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 }) => ({ @@ -51,8 +51,8 @@ const Status = styled.div(({ passes, impact }: { passes: boolean; impact: string alignItems: 'center', color: passes ? impactColors.success : (impactColors as any)[impact], '& > svg': { - height: '16px', - width: '16px', + height: 16, + width: 16, }, })); 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..26b6febaa040 100644 --- a/addons/a11y/src/components/Tabs.tsx +++ b/addons/a11y/src/components/Tabs.tsx @@ -18,8 +18,8 @@ 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, })); @@ -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,10 +39,10 @@ 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, }, }; }); diff --git a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap b/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap index 2ac873d47536..db6f667c93a6 100644 --- a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap +++ b/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap @@ -229,7 +229,6 @@ exports[`A11YPanel should render report 1`] = ` "15paq49": true, "1977chw": true, "19mcg9j": true, - "1ez3l8h": true, "1imo1gr": true, "1kbt4a0": true, "1l7fvsg": true, @@ -239,6 +238,7 @@ exports[`A11YPanel should render report 1`] = ` "6z0ulf": true, "animation-u07e3c": true, "aq4p19": true, + "f68qle": true, "fg630j": true, "iau1th": true, "jb2puo": true, @@ -620,13 +620,13 @@ exports[`A11YPanel should render report 1`] = ` data-emotion="css" > - .css-1ez3l8h{cursor:pointer;font-size:14px;padding:12px 0px 3px 12px;height:40px;border:none;margin-top:0px;float:left;display:block;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;border-bottom:1px solid rgba(0,0,0,.1);} + .css-f68qle{cursor:pointer;font-size:14px;padding:12px 0px 3px 12px;height:40px;border:none;margin-top:0;float:left;display:block;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;border-bottom:1px solid rgba(0,0,0,.1);} , , `, - 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, + }, + ], + }, + decorators: [withCssResources], +}; + +export const defaultView = () => ( +
+); ``` diff --git a/addons/cssresources/package.json b/addons/cssresources/package.json index b8895a0c3f8c..9f6d809e780b 100644 --- a/addons/cssresources/package.json +++ b/addons/cssresources/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-cssresources", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "A storybook addon to switch between css resources at runtime for your story", "keywords": [ "addon", @@ -32,10 +32,10 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3" @@ -45,5 +45,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 1612b935dbd1..0c4fe08b4945 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.45", + "version": "5.3.0-rc.0", "description": "Design asset preview for storybook", "keywords": [ "addon", @@ -34,12 +34,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -48,5 +48,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/docs/README.md b/addons/docs/README.md index ed73384d36f5..c7e702ca5193 100644 --- a/addons/docs/README.md +++ b/addons/docs/README.md @@ -1,5 +1,5 @@
- +
# Storybook Docs @@ -32,7 +32,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). @@ -68,7 +68,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 +77,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 | Polymer | Riot | Mithril | Marko | +| ----------------- | :---: | :-: | :-----: | :---: | :------------: | :--: | :----: | :----: | :-----: | :--: | :-----: | :---: | +| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + | +| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + | +| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + | +| Source | + | + | + | + | + | + | + | + | + | + | + | + | +| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + | +| Props table | + | + | + | + | + | | | | | | | | +| Description | + | + | + | + | + | | | | | | | | +| Inline stories | + | + | | | + | | | | | | | | **Note:** `#` = WIP support @@ -106,22 +106,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: +Then add the following to your `.storybook/main.js`: ```js -module.exports = ['@storybook/addon-docs/preset']; -``` - -**Configure.** If you're migrating from an earlier version of Storybook and want to use `MDX`, you need to upgrade your Storybook config: - -```js -import { configure } from '@storybook/react'; - -configure(require.context('../src', true, /\.stories\.(js|mdx)$/), module); +module.exports = { + presets: ['@storybook/addon-docs/preset'], + stories: ['../src/**/*.stories.(js|mdx)'], +}; ``` -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: @@ -138,8 +131,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 @@ -147,65 +142,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 = { + presets: [ + { + name: '@storybook/addon-docs/preset', + 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`. -`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`: +If you don't want to use the preset, and prefer to configure "the long way", first register the addon in `.storybook/main.js`: ```js -import '@storybook/addon-docs/register'; +module.exports = { + addons: ['@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`: +Then configure Storybook's webpack loader in `.storybook/main.js` to understand MDX story files and annotate TS/JS story files with source code using `source-loader`: ```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 = { + webpack: 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'], + }, }, - }, - { - 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; + ], + }); + 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 3648eda12040..41a54018f582 100644 --- a/addons/docs/angular/README.md +++ b/addons/docs/angular/README.md @@ -22,10 +22,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/preset']; +module.exports = { + presets: ['@storybook/addon-docs/preset'], +}; ``` ## DocsPage @@ -55,7 +57,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 +97,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 +135,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/docs/docspage.md b/addons/docs/docs/docspage.md index 5e286be9ee44..7a51bdab5a8f 100644 --- a/addons/docs/docs/docspage.md +++ b/addons/docs/docs/docspage.md @@ -8,6 +8,7 @@ When you install [Storybook Docs](../README.md), `DocsPage` is the zero-config d - [Motivation](#motivation) - [Component parameter](#component-parameter) +- [Subcomponents parameter](#subcomponents-parameter) - [DocsPage slots](#docspage-slots) - [Replacing DocsPage](#replacing-docspage) - [Story file names](#story-file-names) @@ -54,6 +55,26 @@ 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. @@ -204,7 +225,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/mdx.md b/addons/docs/docs/mdx.md index fe8f46592132..c3dc2c570015 100644 --- a/addons/docs/docs/mdx.md +++ b/addons/docs/docs/mdx.md @@ -165,7 +165,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'; 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 79d33d802f0e..427b11a5eb91 100644 --- a/addons/docs/docs/recipes.md +++ b/addons/docs/docs/recipes.md @@ -157,7 +157,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'; diff --git a/addons/docs/docs/theming.md b/addons/docs/docs/theming.md index 924f81ef1052..23c12ce159bd 100644 --- a/addons/docs/docs/theming.md +++ b/addons/docs/docs/theming.md @@ -10,7 +10,7 @@ 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`: +For example, 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 +45,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 +60,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..6c3b110cb81f --- /dev/null +++ b/addons/docs/ember/README.md @@ -0,0 +1,150 @@ +# Storybook Docs for Ember + +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` presets: + +```js +module.exports = { + presets: ['@storybook/addon-docs/preset'], +}; +``` + +## 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 'htmlbars-inline-precompile' + + + +# 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 78ab1220635f..9923324c623d 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Superior documentation for your components", "keywords": [ "addon", @@ -42,16 +42,16 @@ "@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.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/postinstall": "5.3.0-alpha.45", - "@storybook/router": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@mdx-js/loader": "^1.5.1", + "@mdx-js/mdx": "^1.5.1", + "@mdx-js/react": "^1.5.1", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/csf": "0.0.1", + "@storybook/postinstall": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "acorn": "^7.1.0", "acorn-jsx": "^5.1.0", "acorn-walk": "^7.0.0", @@ -63,10 +63,13 @@ "js-string-escape": "^1.0.1", "lodash": "^4.17.15", "prop-types": "^15.7.2", + "react-element-to-jsx-string": "^14.1.0", + "remark-external-links": "^5.0.0", + "remark-slug": "^5.1.2", "ts-dedent": "^1.1.0", "util-deprecate": "^1.0.2", - "vue-docgen-api": "^3.26.0", - "vue-docgen-loader": "^1.0.1" + "vue-docgen-api": "^4.1.0", + "vue-docgen-loader": "^1.3.0-beta.0" }, "devDependencies": { "@types/doctrine": "^0.0.3", @@ -84,5 +87,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/docs/react/README.md b/addons/docs/react/README.md new file mode 100644 index 000000000000..afd9ea0cc20c --- /dev/null +++ b/addons/docs/react/README.md @@ -0,0 +1,120 @@ +
+ +
+ +# Storybook Docs for React + +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 `presets`: + +```js +module.exports = { + // other settings + presets: ['@storybook/addon-docs/preset']; +} +``` + +## 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 c242d4327d85..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/docgen/utils'; +import { Component, CURRENT_SELECTION, DescriptionSlot } from './shared'; +import { str } from '../lib/docgen'; export enum DescriptionType { INFO = 'info', @@ -16,9 +16,11 @@ type Notes = string | any; type Info = string | any; interface DescriptionProps { + slot?: DescriptionSlot; of?: '.' | Component; type?: DescriptionType; markdown?: string; + children?: string; } const getNotes = (notes?: Notes) => @@ -29,11 +31,11 @@ const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str( 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..cedf8e18f4de 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,40 @@ 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)); - } - 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 + const url = new URL(window.parent.location); + 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/DocsPage.test.ts b/addons/docs/src/blocks/DocsPage.test.ts index af743cf4e15b..92e7264a40cf 100644 --- a/addons/docs/src/blocks/DocsPage.test.ts +++ b/addons/docs/src/blocks/DocsPage.test.ts @@ -1,4 +1,4 @@ -import { defaultTitleSlot } from './DocsPage'; +import { defaultTitleSlot } from './Title'; describe('defaultTitleSlot', () => { it('showRoots', () => { @@ -15,4 +15,10 @@ describe('defaultTitleSlot', () => { 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 63ad87499546..a19a7038867a 100644 --- a/addons/docs/src/blocks/DocsPage.tsx +++ b/addons/docs/src/blocks/DocsPage.tsx @@ -1,153 +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; -} - -export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => { - const { - showRoots, - hierarchyRootSeparator: rootSeparator, - hierarchySeparator: groupSeparator, - } = (parameters && parameters.options) || { - showRoots: undefined, - hierarchyRootSeparator: '|', - hierarchySeparator: /\/|\./, - }; - - 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; -}; - -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 && {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/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 c5794f78c440..21a4d573852a 100644 --- a/addons/docs/src/blocks/Props.tsx +++ b/addons/docs/src/blocks/Props.tsx @@ -1,7 +1,18 @@ -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/docgen/types'; import { extractProps as reactExtractProps } from '../frameworks/react/extractProps'; @@ -9,7 +20,11 @@ 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..4464c02a6293 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 = { @@ -76,7 +77,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..c1b585e82db7 --- /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) { + 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/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..ca5225602199 --- /dev/null +++ b/addons/docs/src/blocks/mdx.tsx @@ -0,0 +1,209 @@ +import React, { FC, SyntheticEvent } from 'react'; +import { Source } from '@storybook/components'; +import { Code, components } from '@storybook/components/html'; +import { document, window } from 'global'; +import { isNil } from 'lodash'; +import { styled } from '@storybook/theming'; +import { DocsContext, DocsContextProps } from './DocsContext'; +import { scrollToElement } from './utils'; + +// Hacky utility for asserting identifiers in MDX Story elements +export const assertIsFn = (val: any) => { + 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 generateHrefWithHash(hash: string): string { + const url = new URL(window.parent.location); + const href = `${url.origin}/${url.search}#${hash}`; + + return href; +} + +// @ts-ignore +const A = components.a; + +interface AnchorInPageProps { + hash: string; +} + +const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => ( + <A + href={hash} + onClick={(event: SyntheticEvent) => { + event.preventDefault(); + + const hashValue = hash.substring(1); + const element = document.getElementById(hashValue); + if (!isNil(element)) { + window.parent.history.replaceState(null, '', generateHrefWithHash(hashValue)); + scrollToElement(element); + } + }} + > + {children} + </A> +); + +interface AnchorMdxProps { + href: string; + target: string; +} + +export const AnchorMdx: FC<AnchorMdxProps> = props => { + const { href, target, children, ...rest } = props; + + if (!isNil(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') { + const parentUrl = new URL(window.parent.location.href); + const newHref = `${parentUrl.origin}${href}`; + + return ( + <A href={newHref} 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]; + + return ( + <OcticonHeader id={id} {...rest}> + <OcticonAnchor + aria-hidden="true" + href={generateHrefWithHash(id)} + tabIndex={-1} + onClick={() => { + const element = document.getElementById(id); + if (!isNil(element)) { + scrollToElement(element); + } + }} + > + <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 (!isNil(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..92129cca1801 --- /dev/null +++ b/addons/docs/src/blocks/utils.ts @@ -0,0 +1,42 @@ +/* eslint-disable no-underscore-dangle */ +import { DocsContextProps } from './DocsContext'; +import { StoryData, Component } from './shared'; + +export const getDocsStories = (context: DocsContextProps): StoryData[] => { + const { storyStore, selectedKind } = context; + 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 319e50c781e1..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; @@ -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/index.ts b/addons/docs/src/frameworks/common/index.ts deleted file mode 100644 index 3de17860d84d..000000000000 --- a/addons/docs/src/frameworks/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../../lib/docgen'; diff --git a/addons/docs/src/frameworks/common/preset.ts b/addons/docs/src/frameworks/common/preset.ts index 002e5555f282..ce48d65c65f5 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) { @@ -26,6 +28,10 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { sourceLoaderOptions = {}, } = options; + const mdxLoaderOptions = { + remarkPlugins: [remarkSlug, remarkExternalLinks], + }; + // set `sourceLoaderOptions` to `null` to disable for manual configuration const sourceLoader = sourceLoaderOptions ? [ @@ -44,6 +50,18 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ...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: [ @@ -55,6 +73,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { loader: '@mdx-js/loader', options: { compilers: [createCompiler(options)], + ...mdxLoaderOptions, }, }, ], @@ -69,6 +88,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { }, { loader: '@mdx-js/loader', + options: mdxLoaderOptions, }, ], }, 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/extractProps.ts b/addons/docs/src/frameworks/react/extractProps.ts index d05f6e27d6ef..20d3032054c6 100644 --- a/addons/docs/src/frameworks/react/extractProps.ts +++ b/addons/docs/src/frameworks/react/extractProps.ts @@ -1,9 +1,10 @@ import PropTypes from 'prop-types'; import { isForwardRef, isMemo } from 'react-is'; import { PropDef } from '@storybook/components'; -import { hasDocgen, extractPropsFromDocgen, PropsExtractor, TypeSystem } from '../../lib/docgen'; +import { hasDocgen, extractComponentProps, PropsExtractor, TypeSystem } from '../../lib/docgen'; import { Component } from '../../blocks/shared'; -import { enhancePropTypesProp } from './propTypes/handleProp'; +import { enhancePropTypesProps } from './propTypes/handleProp'; +import { enhanceTypeScriptProps } from './typeScript/handleProp'; export interface PropDefMap { [p: string]: PropDef; @@ -32,16 +33,19 @@ function getPropDefs(component: Component, section: string): PropDef[] { } } - const extractedProps = extractPropsFromDocgen(processedComponent, section); + const extractedProps = extractComponentProps(processedComponent, section); if (extractedProps.length === 0) { return []; } - if (extractedProps[0].typeSystem === TypeSystem.JAVASCRIPT) { - return extractedProps.map(enhancePropTypesProp); + switch (extractedProps[0].typeSystem) { + case TypeSystem.JAVASCRIPT: + return enhancePropTypesProps(extractedProps, component); + case TypeSystem.TYPESCRIPT: + return enhanceTypeScriptProps(extractedProps); + default: + return extractedProps.map(x => x.propDef); } - - return extractedProps.map(x => x.propDef); } export const extractProps: PropsExtractor = component => ({ diff --git a/addons/docs/src/frameworks/react/inspection/types.ts b/addons/docs/src/frameworks/react/inspection/types.ts deleted file mode 100644 index e8272f0584d1..000000000000 --- a/addons/docs/src/frameworks/react/inspection/types.ts +++ /dev/null @@ -1,79 +0,0 @@ -export enum InspectionType { - IDENTIFIER = 'Identifier', - LITERAL = 'Literal', - OBJECT = 'Object', - ARRAY = 'Array', - FUNCTION = 'Function', - CLASS = 'Class', - ELEMENT = 'Element', - UNKNOWN = 'Unknown', -} - -export interface BaseInspectionInferedType { - type: InspectionType; -} - -// TODO: Fix this. -// export interface OptionalIdentifierInspectionType extends BaseInspectionInferedType { -// identifier?: string; -// } - -// export interface RequiredIdentifierInspectionType extends BaseInspectionInferedType { -// identifier: string; -// } - -// export type IdentifiableInspectionType = -// | OptionalIdentifierInspectionType -// | RequiredIdentifierInspectionType; - -export interface InspectionIdentifier extends BaseInspectionInferedType { - type: InspectionType.IDENTIFIER; - identifier: string; -} - -export interface InspectionLiteral extends BaseInspectionInferedType { - type: InspectionType.LITERAL; -} - -export interface InspectionObject extends BaseInspectionInferedType { - type: InspectionType.OBJECT; -} - -export interface InspectionArray extends BaseInspectionInferedType { - type: InspectionType.ARRAY; -} - -export interface InspectionClass extends BaseInspectionInferedType { - type: InspectionType.CLASS; - identifier: string; -} - -export interface InspectionFunction extends BaseInspectionInferedType { - type: InspectionType.FUNCTION; - identifier?: string; - hasArguments: boolean; -} - -export interface InspectionElement extends BaseInspectionInferedType { - type: InspectionType.ELEMENT; - identifier?: string; -} - -export interface InspectionUnknown extends BaseInspectionInferedType { - type: InspectionType.UNKNOWN; -} - -export type InspectionInferedType = - | InspectionIdentifier - | InspectionLiteral - | InspectionObject - | InspectionArray - | InspectionClass - | InspectionFunction - | InspectionElement - | InspectionUnknown; - -export interface InspectionResult { - inferedType: InspectionInferedType; - ast?: any; -} diff --git a/addons/docs/src/frameworks/react/propTypes/captions.ts b/addons/docs/src/frameworks/react/lib/captions.ts similarity index 100% rename from addons/docs/src/frameworks/react/propTypes/captions.ts rename to addons/docs/src/frameworks/react/lib/captions.ts 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/inspection/acornParser.test.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts similarity index 75% rename from addons/docs/src/frameworks/react/inspection/acornParser.test.ts rename to addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts index ad10948e6878..8ffe6fad77a4 100644 --- a/addons/docs/src/frameworks/react/inspection/acornParser.test.ts +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.test.ts @@ -67,14 +67,34 @@ describe('parse', () => { 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: PropTypes.string })'); + 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(); }); @@ -83,6 +103,7 @@ describe('parse', () => { const inferedType = result.inferedType as InspectionObject; expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); expect(result.ast).toBeDefined(); }); @@ -95,6 +116,25 @@ describe('parse', () => { 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(); }); @@ -103,6 +143,7 @@ describe('parse', () => { const inferedType = result.inferedType as InspectionObject; expect(inferedType.type).toBe(InspectionType.OBJECT); + expect(inferedType.depth).toBe(1); expect(result.ast).toBeDefined(); }); @@ -111,6 +152,16 @@ describe('parse', () => { 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(); }); @@ -129,7 +180,8 @@ describe('parse', () => { expect(inferedType.type).toBe(InspectionType.FUNCTION); expect(inferedType.identifier).toBeUndefined(); - expect(inferedType.hasArguments).toBeFalsy(); + expect(inferedType.hasParams).toBeFalsy(); + expect(inferedType.params.length).toBe(0); expect(result.ast).toBeDefined(); }); @@ -139,7 +191,8 @@ describe('parse', () => { expect(inferedType.type).toBe(InspectionType.FUNCTION); expect(inferedType.identifier).toBeUndefined(); - expect(inferedType.hasArguments).toBeTruthy(); + expect(inferedType.hasParams).toBeTruthy(); + expect(inferedType.params.length).toBe(2); expect(result.ast).toBeDefined(); }); @@ -149,7 +202,8 @@ describe('parse', () => { expect(inferedType.type).toBe(InspectionType.FUNCTION); expect(inferedType.identifier).toBe('concat'); - expect(inferedType.hasArguments).toBeFalsy(); + expect(inferedType.hasParams).toBeFalsy(); + expect(inferedType.params.length).toBe(0); expect(result.ast).toBeDefined(); }); @@ -159,7 +213,8 @@ describe('parse', () => { expect(inferedType.type).toBe(InspectionType.FUNCTION); expect(inferedType.identifier).toBe('concat'); - expect(inferedType.hasArguments).toBeTruthy(); + expect(inferedType.hasParams).toBeTruthy(); + expect(inferedType.params.length).toBe(2); expect(result.ast).toBeDefined(); }); diff --git a/addons/docs/src/frameworks/react/inspection/acornParser.ts b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts similarity index 84% rename from addons/docs/src/frameworks/react/inspection/acornParser.ts rename to addons/docs/src/frameworks/react/lib/inspection/acornParser.ts index bd845993a88f..60b552abc2fc 100644 --- a/addons/docs/src/frameworks/react/inspection/acornParser.ts +++ b/addons/docs/src/frameworks/react/lib/inspection/acornParser.ts @@ -7,7 +7,6 @@ import estree from 'estree'; import * as acornWalk from 'acorn-walk'; import { InspectionType, - InspectionInferedType, InspectionLiteral, InspectionElement, InspectionFunction, @@ -16,6 +15,7 @@ import { InspectionUnknown, InspectionIdentifier, InspectionArray, + InspectionInferedType, } from './types'; interface ParsingResult<T> { @@ -35,6 +35,29 @@ 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: { @@ -72,7 +95,8 @@ function parseFunction( const inferedType: InspectionFunction | InspectionElement = { type: isJsx ? InspectionType.ELEMENT : InspectionType.FUNCTION, - hasArguments: funcNode.params.length !== 0, + params: funcNode.params, + hasParams: funcNode.params.length !== 0, }; const identifierName = extractIdentifierName((funcNode as estree.FunctionExpression).id); @@ -135,10 +159,7 @@ function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObj const identifierName = extractIdentifierName(identifierNode); if (identifierName === 'shape') { - return { - inferedType: { type: InspectionType.OBJECT }, - ast: callNode.arguments[0], - }; + return parseObject(callNode.arguments[0] as estree.ObjectExpression); } return null; @@ -146,14 +167,14 @@ function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObj function parseObject(objectNode: estree.ObjectExpression): ParsingResult<InspectionObject> { return { - inferedType: { type: InspectionType.OBJECT }, + inferedType: { type: InspectionType.OBJECT, depth: calculateNodeDepth(objectNode) }, ast: objectNode, }; } function parseArray(arrayNode: estree.ArrayExpression): ParsingResult<InspectionArray> { return { - inferedType: { type: InspectionType.ARRAY }, + inferedType: { type: InspectionType.ARRAY, depth: calculateNodeDepth(arrayNode) }, ast: arrayNode, }; } 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/inspection/inspectValue.ts b/addons/docs/src/frameworks/react/lib/inspection/inspectValue.ts similarity index 100% rename from addons/docs/src/frameworks/react/inspection/inspectValue.ts rename to addons/docs/src/frameworks/react/lib/inspection/inspectValue.ts 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/propTypes/isHtmlTag.ts b/addons/docs/src/frameworks/react/lib/isHtmlTag.ts similarity index 100% rename from addons/docs/src/frameworks/react/propTypes/isHtmlTag.ts rename to addons/docs/src/frameworks/react/lib/isHtmlTag.ts diff --git a/addons/docs/src/frameworks/react/propTypes/createDefaultValue.ts b/addons/docs/src/frameworks/react/propTypes/createDefaultValue.ts deleted file mode 100644 index 3ca17796955d..000000000000 --- a/addons/docs/src/frameworks/react/propTypes/createDefaultValue.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { isNil } from 'lodash'; -// @ts-ignore -import { PropDefaultValue, PropSummaryValue } from '@storybook/components'; -import { inspectValue } from '../inspection/inspectValue'; -import { OBJECT_CAPTION, FUNCTION_CAPTION, ELEMENT_CAPTION, ARRAY_CAPTION } from './captions'; -import { generateCode } from './generateCode'; -import { - InspectionFunction, - InspectionResult, - InspectionType, - InspectionElement, -} from '../inspection/types'; -import { isHtmlTag } from './isHtmlTag'; - -const MAX_SUMMARY_LENGTH = 50; - -function isTooLongForSummary(value: string): boolean { - return value.length > MAX_SUMMARY_LENGTH; -} - -// TODO: Fix this any type. -function getPrettyIdentifier(inferedType: any): string { - const { type, identifier } = inferedType; - - switch (type) { - case InspectionType.FUNCTION: - return inferedType.hasArguments ? `${identifier}( ... )` : `${identifier}()`; - case InspectionType.ELEMENT: - return `<${identifier} />`; - default: - return identifier; - } -} - -function createSummaryValue(summary: string, detail?: string): PropSummaryValue { - return { summary, detail }; -} - -function generateObject({ ast }: InspectionResult): PropDefaultValue { - let prettyCaption = generateCode(ast, true); - - // Cannot get escodegen to add a space before the last } with the compact mode settings. - // This fix it until a better solution is found. - if (!prettyCaption.endsWith(' }')) { - prettyCaption = `${prettyCaption.slice(0, -1)} }`; - } - - return !isTooLongForSummary(prettyCaption) - ? createSummaryValue(prettyCaption) - : createSummaryValue(OBJECT_CAPTION, generateCode(ast)); -} - -function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue { - const { identifier } = inferedType as InspectionFunction; - - if (!isNil(identifier)) { - return createSummaryValue(getPrettyIdentifier(inferedType), generateCode(ast)); - } - - const prettyCaption = generateCode(ast, true); - - return !isTooLongForSummary(prettyCaption) - ? createSummaryValue(prettyCaption) - : createSummaryValue(FUNCTION_CAPTION, generateCode(ast)); -} - -// All elements are JSX elements. -// JSX elements cannot 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); - - return createSummaryValue( - prettyIdentifier, - prettyIdentifier !== defaultValue ? defaultValue : undefined - ); - } - } - - return !isTooLongForSummary(defaultValue) - ? createSummaryValue(defaultValue) - : createSummaryValue(ELEMENT_CAPTION, defaultValue); -} - -function generateArray({ ast }: InspectionResult): PropDefaultValue { - const prettyCaption = generateCode(ast, true); - - return !isTooLongForSummary(prettyCaption) - ? createSummaryValue(prettyCaption) - : createSummaryValue(ARRAY_CAPTION, generateCode(ast)); -} - -export function createDefaultValue(defaultValue: string): PropDefaultValue { - 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; - } -} diff --git a/addons/docs/src/frameworks/react/propTypes/createType.ts b/addons/docs/src/frameworks/react/propTypes/createType.ts index cad45030a411..84a9e55f5620 100644 --- a/addons/docs/src/frameworks/react/propTypes/createType.ts +++ b/addons/docs/src/frameworks/react/propTypes/createType.ts @@ -1,9 +1,12 @@ import { isNil } from 'lodash'; -import { PropSummaryValue, PropType } from '@storybook/components'; +import { PropType } from '@storybook/components'; +import { createSummaryValue, isTooLongForTypeSummary } from '../../../lib'; import { ExtractedProp, DocgenPropType } from '../../../lib/docgen'; -import { inspectValue } from '../inspection/inspectValue'; -import { generateCode } from './generateCode'; -import { generateFuncSignature } from './generateFuncSignature'; +import { + generateFuncSignature, + generateShortFuncSignature, + toMultilineSignature, +} from './generateFuncSignature'; import { OBJECT_CAPTION, ARRAY_CAPTION, @@ -11,11 +14,19 @@ import { FUNCTION_CAPTION, ELEMENT_CAPTION, CUSTOM_CAPTION, -} from './captions'; -import { InspectionType } from '../inspection/types'; -import { isHtmlTag } from './isHtmlTag'; + isHtmlTag, + generateObjectCode, + generateCode, +} from '../lib'; +import { + InspectionType, + inspectValue, + InspectionElement, + InspectionObject, + InspectionArray, +} from '../lib/inspection'; -const MAX_SUMMARY_LENGTH = 35; +const MAX_FUNC_LENGTH = 150; enum PropTypesType { CUSTOM = 'custom', @@ -40,27 +51,30 @@ interface EnumValue { interface TypeDef { name: string; - value: PropSummaryValue; + short: string; + compact: string; + full: string; inferedType?: InspectionType; } function createTypeDef({ name, - summary, - detail, + short, + compact, + full, inferedType, }: { name: string; - summary: string; - detail?: string; + short: string; + compact: string; + full?: string; inferedType?: InspectionType; }): TypeDef { return { name, - value: { - summary, - detail: !isNil(detail) ? detail : summary, - }, + short, + compact, + full: !isNil(full) ? full : short, inferedType, }; } @@ -69,11 +83,19 @@ 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 getCaptionFromInspectionType(type: InspectionType): string { +function getCaptionForInspectionType(type: InspectionType): string { switch (type) { case InspectionType.OBJECT: return OBJECT_CAPTION; @@ -90,63 +112,70 @@ function getCaptionFromInspectionType(type: InspectionType): string { } } -function isTooLongForSummary(value: string): boolean { - return value.length > MAX_SUMMARY_LENGTH; -} +function generateTypeFromString(value: string, originalTypeName: string): TypeDef { + const { inferedType, ast } = inspectValue(value); + const { type } = inferedType; -function generateValuesForObjectAst(ast: any): [string, string] { - let summary = prettyObject(ast, true); - let detail; + let short; + let compact; + let full; - if (!isTooLongForSummary(summary)) { - detail = summary; - } else { - summary = OBJECT_CAPTION; - detail = prettyObject(ast); + 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 [summary, detail]; + return createTypeDef({ + name: originalTypeName, + short, + compact, + full, + inferedType: type, + }); } function generateCustom({ raw }: DocgenPropType): TypeDef { if (!isNil(raw)) { - const { inferedType, ast } = inspectValue(raw); - const { type, identifier } = inferedType as any; - - let summary; - let detail; - - switch (type) { - case InspectionType.IDENTIFIER: - case InspectionType.LITERAL: - summary = raw; - break; - case InspectionType.OBJECT: { - const [objectCaption, objectValue] = generateValuesForObjectAst(ast); - - summary = objectCaption; - detail = objectValue; - break; - } - case InspectionType.ELEMENT: - summary = !isNil(identifier) && !isHtmlTag(identifier) ? identifier : ELEMENT_CAPTION; - detail = raw; - break; - default: - summary = getCaptionFromInspectionType(type); - detail = raw; - break; - } - - return createTypeDef({ - name: PropTypesType.CUSTOM, - summary, - detail, - inferedType: type, - }); + return generateTypeFromString(raw, PropTypesType.CUSTOM); } - return createTypeDef({ name: PropTypesType.CUSTOM, summary: CUSTOM_CAPTION }); + return createTypeDef({ + name: PropTypesType.CUSTOM, + short: CUSTOM_CAPTION, + compact: CUSTOM_CAPTION, + }); } function generateFunc(extractedProp: ExtractedProp): TypeDef { @@ -156,47 +185,48 @@ function generateFunc(extractedProp: ExtractedProp): TypeDef { if (!isNil(jsDocTags.params) || !isNil(jsDocTags.returns)) { return createTypeDef({ name: PropTypesType.FUNC, - summary: FUNCTION_CAPTION, - detail: generateFuncSignature(jsDocTags.params, jsDocTags.returns), + short: generateShortFuncSignature(jsDocTags.params, jsDocTags.returns), + compact: null, + full: generateFuncSignature(jsDocTags.params, jsDocTags.returns), }); } } - return createTypeDef({ name: PropTypesType.FUNC, summary: FUNCTION_CAPTION }); + 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).value.detail}`) + .map((key: string) => `${key}: ${generateType(type.value[key], extractedProp).full}`) .join(', '); - const { ast } = inspectValue(`{ ${fields} }`); - const [summary, detail] = generateValuesForObjectAst(ast); + const { inferedType, ast } = inspectValue(`{ ${fields} }`); + const { depth } = inferedType as InspectionObject; return createTypeDef({ name: PropTypesType.SHAPE, - summary, - detail, + short: OBJECT_CAPTION, + compact: depth === 1 ? prettyObject(ast, true) : null, + full: prettyObject(ast), }); } -function generateObjectOf(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { - const format = (of: string) => `objectOf(${of})`; - - const { name, value } = generateType(type.value, extractedProp); - // eslint-disable-next-line prefer-const - let { summary, detail } = value; +function objectOf(of: string): string { + return `objectOf(${of})`; +} - if (name === PropTypesType.SHAPE) { - if (!isTooLongForSummary(detail)) { - summary = detail; - } - } +function generateObjectOf(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { + const { short, compact, full } = generateType(type.value, extractedProp); return createTypeDef({ name: PropTypesType.OBJECTOF, - summary: format(summary), - detail: format(detail), + short: objectOf(short), + compact: !isNil(compact) ? objectOf(compact) : null, + full: objectOf(full), }); } @@ -204,76 +234,58 @@ function generateUnion(type: DocgenPropType, extractedProp: ExtractedProp): Type if (Array.isArray(type.value)) { const values = type.value.reduce( (acc: any, v: any) => { - const { summary, detail } = generateType(v, extractedProp).value; + const { short, compact, full } = generateType(v, extractedProp); - acc.summary.push(summary); - acc.detail.push(detail); + acc.short.push(short); + acc.compact.push(compact); + acc.full.push(full); return acc; }, - { summary: [], detail: [] } + { short: [], compact: [], full: [] } ); return createTypeDef({ name: PropTypesType.UNION, - summary: values.summary.join(' | '), - detail: values.detail.join(' | '), + 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, summary: type.value }); + return createTypeDef({ name: PropTypesType.UNION, short: type.value, compact: null }); } function generateEnumValue({ value, computed }: EnumValue): TypeDef { - if (computed) { - const { inferedType, ast } = inspectValue(value) as any; - const { type } = inferedType; - - let caption = getCaptionFromInspectionType(type); - - if ( - type === InspectionType.FUNCTION || - type === InspectionType.CLASS || - type === InspectionType.ELEMENT - ) { - if (!isNil(inferedType.identifier)) { - caption = inferedType.identifier; - } - } - - return createTypeDef({ - name: 'enumvalue', - summary: caption, - detail: type === InspectionType.OBJECT ? prettyObject(ast) : value, - inferedType: type, - }); - } - - return createTypeDef({ name: 'enumvalue', summary: value }); + 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 { summary, detail } = generateEnumValue(v).value; + const { short, compact, full } = generateEnumValue(v); - acc.summary.push(summary); - acc.detail.push(detail); + acc.short.push(short); + acc.compact.push(compact); + acc.full.push(full); return acc; }, - { summary: [], detail: [] } + { short: [], compact: [], full: [] } ); return createTypeDef({ name: PropTypesType.ENUM, - summary: values.summary.join(' | '), - detail: values.detail.join(' | '), + 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, summary: type.value }); + return createTypeDef({ name: PropTypesType.ENUM, short: type.value, compact: type.value }); } function braceAfter(of: string): string { @@ -284,27 +296,31 @@ function braceAround(of: string): string { return `[${of}]`; } -function createArrayOfObjectTypeDef(summary: string, detail: string): TypeDef { +function createArrayOfObjectTypeDef(short: string, compact: string, full: string): TypeDef { return createTypeDef({ name: PropTypesType.ARRAYOF, - summary: summary === OBJECT_CAPTION ? braceAfter(summary) : braceAround(summary), - detail: braceAround(detail), + short: braceAfter(short), + compact: !isNil(compact) ? braceAround(compact) : null, + full: braceAround(full), }); } function generateArray(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { - const { name, value, inferedType } = generateType(type.value, extractedProp); - const { summary, detail } = value; + const { name, short, compact, full, inferedType } = generateType(type.value, extractedProp); if (name === PropTypesType.CUSTOM) { if (inferedType === InspectionType.OBJECT) { - return createArrayOfObjectTypeDef(summary, detail); + return createArrayOfObjectTypeDef(short, compact, full); } } else if (name === PropTypesType.SHAPE) { - return createArrayOfObjectTypeDef(summary, detail); + return createArrayOfObjectTypeDef(short, compact, full); } - return createTypeDef({ name: PropTypesType.ARRAYOF, summary: braceAfter(detail) }); + return createTypeDef({ + name: PropTypesType.ARRAYOF, + short: braceAfter(short), + compact: braceAfter(short), + }); } function generateType(type: DocgenPropType, extractedProp: ExtractedProp): TypeDef { @@ -317,7 +333,11 @@ function generateType(type: DocgenPropType, extractedProp: ExtractedProp): TypeD case PropTypesType.SHAPE: return generateShape(type, extractedProp); case PropTypesType.INSTANCEOF: - return createTypeDef({ name: PropTypesType.INSTANCEOF, summary: type.value }); + return createTypeDef({ + name: PropTypesType.INSTANCEOF, + short: type.value, + compact: type.value, + }); case PropTypesType.OBJECTOF: return generateObjectOf(type, extractedProp); case PropTypesType.UNION: @@ -327,40 +347,64 @@ function generateType(type: DocgenPropType, extractedProp: ExtractedProp): TypeD case PropTypesType.ARRAYOF: return generateArray(type, extractedProp); default: - return createTypeDef({ name: type.name, summary: type.name }); + 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', summary: 'unknown' }); + return createTypeDef({ name: 'unknown', short: 'unknown', compact: 'unknown' }); } export function createType(extractedProp: ExtractedProp): PropType { const { type } = extractedProp.docgenInfo; - 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 { summary, detail } = generateType(type, extractedProp).value; - - return { - summary, - detail: summary !== detail ? detail : undefined, - }; - } - case PropTypesType.FUNC: { - const { detail } = generateType(type, extractedProp).value; + // 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; - return { summary: detail }; + if (full.length < MAX_FUNC_LENGTH) { + summary = full; + } else { + detail = toMultilineSignature(full); + } + + return createSummaryValue(summary, detail); + } + default: + return null; } - 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/generateCode.ts b/addons/docs/src/frameworks/react/propTypes/generateCode.ts deleted file mode 100644 index 092860023c80..000000000000 --- a/addons/docs/src/frameworks/react/propTypes/generateCode.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { generate } from 'escodegen'; - -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); -} diff --git a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts index d76588798445..92f6575c9374 100644 --- a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts +++ b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.test.ts @@ -1,156 +1,187 @@ -import { generateFuncSignature } from './generateFuncSignature'; +import { generateFuncSignature, generateShortFuncSignature } from './generateFuncSignature'; import { parseJsDoc } from '../../../lib/jsdocParser'; -it('should return an empty string with there is no @params and @returns tags', () => { - const result = generateFuncSignature(null, null); +describe('generateFuncSignature', () => { + it('should return an empty string when there is no @params and @returns tags', () => { + const result = generateFuncSignature(null, null); - expect(result).toBe(''); -}); + 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); + 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)'); -}); + 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); + 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)'); -}); + 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); + 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)'); -}); + 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); + 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}))'); -}); + 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); + 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))'); -}); + 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); + 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[])'); -}); + 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); + 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)'); -}); + 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); + 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)'); -}); + expect(result).toBe('(event: number)'); + }); -it('should support optional @param with []', () => { - const { params, returns } = parseJsDoc('@param {number} [event]').extractedTags; - const result = generateFuncSignature(params, returns); + it('should support optional @param with []', () => { + const { params, returns } = parseJsDoc('@param {number} [event]').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('(event: number)'); -}); + expect(result).toBe('(event: number)'); + }); -it('should support optional @param with =', () => { - const { params, returns } = parseJsDoc('@param {number=} event').extractedTags; - const result = generateFuncSignature(params, returns); + it('should support optional @param with =', () => { + const { params, returns } = parseJsDoc('@param {number=} event').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('(event: number)'); -}); + expect(result).toBe('(event: number)'); + }); -it('should support @param of type any', () => { - const { params, returns } = parseJsDoc('@param {*} event').extractedTags; - const result = generateFuncSignature(params, returns); + it('should support @param of type any', () => { + const { params, returns } = parseJsDoc('@param {*} event').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('(event: any)'); -}); + 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); + 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)'); -}); + 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); + 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'); -}); + 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); + 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})'); -}); + 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); + it('should support @returns of array type', () => { + const { params, returns } = parseJsDoc('@returns {integer[]}').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('() => integer[]'); -}); + expect(result).toBe('() => integer[]'); + }); -it('should support @returns of union type', () => { - const { params, returns } = parseJsDoc('@returns {(number|boolean)}').extractedTags; - const result = generateFuncSignature(params, returns); + 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)'); -}); + expect(result).toBe('() => (number|boolean)'); + }); -it('should support @returns type any', () => { - const { params, returns } = parseJsDoc('@returns {*}').extractedTags; - const result = generateFuncSignature(params, returns); + it('should support @returns type any', () => { + const { params, returns } = parseJsDoc('@returns {*}').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('() => any'); -}); + expect(result).toBe('() => any'); + }); -it('should support @returns of type void', () => { - const { params, returns } = parseJsDoc('@returns {void}').extractedTags; - const result = generateFuncSignature(params, returns); + it('should support @returns of type void', () => { + const { params, returns } = parseJsDoc('@returns {void}').extractedTags; + const result = generateFuncSignature(params, returns); - expect(result).toBe('() => void'); -}); + 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 single @param tag and a @returns', () => { - const { params, returns } = parseJsDoc( - '@param {SyntheticEvent} event - React event.\n@returns {string}' - ).extractedTags; - const result = generateFuncSignature(params, returns); + 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) => string'); + expect(result).toBe('(event: SyntheticEvent, data: string) => 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); +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('(event: SyntheticEvent, data: string) => string'); + expect(result).toBe('() => string'); + }); }); diff --git a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts index 68cc0a5217ae..63624c418958 100644 --- a/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts +++ b/addons/docs/src/frameworks/react/propTypes/generateFuncSignature.ts @@ -37,3 +37,33 @@ export function generateFuncSignature( 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.ts b/addons/docs/src/frameworks/react/propTypes/handleProp.test.ts deleted file mode 100644 index e2884c70edab..000000000000 --- a/addons/docs/src/frameworks/react/propTypes/handleProp.test.ts +++ /dev/null @@ -1,673 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { PropDef } from '@storybook/components'; -import { Component } from '../../../blocks/shared'; -import { extractPropsFromDocgen } from '../../../lib/docgen'; -import { enhancePropTypesProp } from './handleProp'; - -const DOCGEN_SECTION = 'props'; -const PROP_NAME = 'propName'; - -function createComponent(docgenInfo: Record<string, any>): Component { - const component = () => {}; - // @ts-ignore - component.__docgenInfo = { - [DOCGEN_SECTION]: { - [PROP_NAME]: { - required: false, - ...docgenInfo, - }, - }, - }; - - return component; -} - -function extractPropDef(component: Component): PropDef { - return enhancePropTypesProp(extractPropsFromDocgen(component, DOCGEN_SECTION)[0]); -} - -describe('prop type', () => { - describe('custom', () => { - describe('when raw value is available', () => { - it('should support literal', () => { - const component = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - type: { - name: 'custom', - raw: - '{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}', - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('object'); - - const expectedDetail = `{ - text: string, - value: string - }`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should use identifier of a React element when available', () => { - const component = createComponent({ - 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 = createComponent({ - type: { - name: 'custom', - raw: '<div>Hello world!</div>', - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('element'); - - const expectedDetail = '<div>Hello world!</div>'; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should support element without identifier', () => { - const component = createComponent({ - type: { - name: 'custom', - raw: '() => {\n return <div>Inlined FunctionnalComponent!</div>;\n}', - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('element'); - - const expectedDetail = `() => { - return <div>Inlined FunctionnalComponent!</div>; - }`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should return "custom" when it is not a known type', () => { - const component = createComponent({ - type: { - name: 'custom', - raw: 'Symbol("Hey!")', - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('custom'); - }); - }); - - it("should return 'custom' when there is no raw value", () => { - const component = createComponent({ - 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 = createComponent({ - type: { - name: x, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe(x); - }); - } - ); - - it('should support short shape', () => { - const component = createComponent({ - 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 = createComponent({ - type: { - name: 'shape', - value: { - foo: { - name: 'string', - required: false, - }, - bar: { - name: 'string', - required: false, - }, - another: { - name: 'string', - required: false, - }, - }, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('object'); - - const expectedDetail = `{ - foo: string, - bar: string, - another: string - }`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should support enum of string', () => { - const component = createComponent({ - 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 = createComponent({ - 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}', - computed: true, - }, - ], - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('object | object'); - - const expectedDetail = `{ - text: string, - value: string - } | { - foo: string, - bar: string - }`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should support enum of element', () => { - const component = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - type: { - name: 'instanceOf', - value: 'Set', - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('Set'); - }); - - describe('objectOf', () => { - it('should support objectOf primitive', () => { - const component = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - type: { - name: 'objectOf', - value: { - name: 'custom', - raw: - '{\n foo: PropTypes.string,\n bar: PropTypes.string,\n another: PropTypes.string,\n}', - }, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('objectOf(object)'); - - const expectedDetail = `objectOf({ - foo: string, - bar: string, - another: string - })`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should support objectOf short shape', () => { - const component = createComponent({ - 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 = createComponent({ - type: { - name: 'objectOf', - value: { - name: 'shape', - value: { - foo: { - name: 'string', - required: false, - }, - bar: { - name: 'string', - required: false, - }, - another: { - name: 'string', - required: false, - }, - }, - }, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('objectOf(object)'); - - const expectedDetail = `objectOf({ - foo: string, - bar: string, - another: string - })`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - }); - - it('should support union', () => { - const component = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - 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 = createComponent({ - type: { - name: 'arrayOf', - value: { - name: 'custom', - raw: - '{\n text: PropTypes.string.isRequired,\n value: PropTypes.string.isRequired,\n}', - }, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('object[]'); - - const expectedDetail = `[{ - text: string, - value: string - }]`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - - it('should support array of short shape', () => { - const component = createComponent({ - 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 = createComponent({ - type: { - name: 'arrayOf', - value: { - name: 'shape', - value: { - foo: { - name: 'string', - required: false, - }, - bar: { - name: 'string', - required: false, - }, - another: { - name: 'string', - required: false, - }, - }, - }, - }, - }); - - const { type } = extractPropDef(component); - - expect(type.summary).toBe('object[]'); - - const expectedDetail = `[{ - foo: string, - bar: string, - another: string - }]`; - - expect(type.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, '')); - }); - }); -}); 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 index e8602850f5cc..819b223cbdbb 100644 --- a/addons/docs/src/frameworks/react/propTypes/handleProp.ts +++ b/addons/docs/src/frameworks/react/propTypes/handleProp.ts @@ -2,9 +2,12 @@ import { isNil } from 'lodash'; import { PropDef } from '@storybook/components'; import { ExtractedProp } from '../../../lib/docgen'; import { createType } from './createType'; -import { createDefaultValue } from './createDefaultValue'; +import { createDefaultValue, createDefaultValueFromRawDefaultProp } from '../lib/defaultValues'; +import { Component } from '../../../blocks/shared'; +import { keepOriginalDefinitionOrder } from './sortProps'; +import { rawDefaultPropTypeResolvers } from './rawDefaultPropResolvers'; -export function enhancePropTypesProp(extractedProp: ExtractedProp): PropDef { +export function enhancePropTypesProp(extractedProp: ExtractedProp, rawDefaultProp?: any): PropDef { const { propDef } = extractedProp; const newtype = createType(extractedProp); @@ -13,8 +16,19 @@ export function enhancePropTypesProp(extractedProp: ExtractedProp): PropDef { } const { defaultValue } = extractedProp.docgenInfo; - if (!isNil(defaultValue)) { + 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; } @@ -22,3 +36,15 @@ export function enhancePropTypesProp(extractedProp: ExtractedProp): PropDef { 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 e08d34308775..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/docgen/utils'; +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 495dd8b0c24a..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, hasDocgen, extractPropsFromDocgen } from '../../lib/docgen'; +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).map(x => x.propDef); + sections[section] = extractComponentProps(component, section).map(x => x.propDef); }); return { sections }; }; diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index a26c871ce277..76083ba119f6 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -11,7 +11,7 @@ function mapData(data) { type: { summary: item.type }, required: '', description: item.description, - defaultValue: { summary: item.default }, + defaultValue: { summary: item.defaultValue }, })); } diff --git a/addons/docs/src/lib/docgen/createDefaultValue.ts b/addons/docs/src/lib/docgen/createDefaultValue.ts deleted file mode 100644 index 36fdc32f7113..000000000000 --- a/addons/docs/src/lib/docgen/createDefaultValue.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isNil } from 'lodash'; -import { PropDefaultValue } from '@storybook/components'; -import { DocgenPropDefaultValue } from './types'; - -const BLACKLIST = ['null', 'undefined']; - -function isDefaultValueBlacklisted(value: string) { - return BLACKLIST.some(x => x === value); -} - -export function createDefaultValue(defaultValue: DocgenPropDefaultValue): PropDefaultValue { - if (!isNil(defaultValue)) { - const { value } = defaultValue; - - if (!isDefaultValueBlacklisted(value)) { - return { - summary: value, - }; - } - } - - return null; -} diff --git a/addons/docs/src/lib/docgen/createPropDef.ts b/addons/docs/src/lib/docgen/createPropDef.ts index c314511d86a6..9d844cf00fb5 100644 --- a/addons/docs/src/lib/docgen/createPropDef.ts +++ b/addons/docs/src/lib/docgen/createPropDef.ts @@ -1,8 +1,11 @@ import { isNil } from 'lodash'; -import { PropDef } from '@storybook/components'; -import { TypeSystem, DocgenInfo, DocgenType } from './types'; +import { PropDef, PropDefaultValue } from '@storybook/components'; +import { TypeSystem, DocgenInfo, DocgenType, DocgenPropDefaultValue } from './types'; import { JsDocParsingResult } from '../jsdocParser'; -import { createDefaultValue } from './createDefaultValue'; +import { createSummaryValue } from '../utils'; +import { createFlowPropDef } from './flow/createPropDef'; +import { isDefaultValueBlacklisted } from './utils/defaultValue'; +import { createTsPropDef } from './typeScript/createPropDef'; export type PropDefFactory = ( propName: string, @@ -10,30 +13,41 @@ export type PropDefFactory = ( 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: { summary: type.name }, + type: createType(type), required, description, defaultValue: createDefaultValue(defaultValue), }; } -function createPropDef( - name: string, - type: DocgenType, - docgenInfo: DocgenInfo, - jsDocParsingResult: JsDocParsingResult -): PropDef { - const propDef = createBasicPropDef(name, type, docgenInfo); - +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; } @@ -41,6 +55,7 @@ function createPropDef( const hasReturns = !isNil(extractedTags.returns) && !isNil(extractedTags.returns.type); if (hasParams || hasReturns) { + // eslint-disable-next-line no-param-reassign propDef.jsDocTags = { params: hasParams && @@ -53,36 +68,28 @@ function createPropDef( return propDef; } -export const javaScriptFactory: PropDefFactory = ( - propName: string, - docgenInfo: DocgenInfo, - jsDocParsingResult?: JsDocParsingResult -) => { - return createPropDef(propName, docgenInfo.type, docgenInfo, jsDocParsingResult); +export const javaScriptFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createBasicPropDef(propName, docgenInfo.type, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); }; -export const tsFactory: PropDefFactory = ( - propName: string, - docgenInfo: DocgenInfo, - jsDocParsingResult?: JsDocParsingResult -) => { - return createPropDef(propName, docgenInfo.tsType, docgenInfo, jsDocParsingResult); +export const tsFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createTsPropDef(propName, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); }; -export const flowFactory: PropDefFactory = ( - propName: string, - docgenInfo: DocgenInfo, - jsDocParsingResult?: JsDocParsingResult -) => { - return createPropDef(propName, docgenInfo.flowType, docgenInfo, jsDocParsingResult); +export const flowFactory: PropDefFactory = (propName, docgenInfo, jsDocParsingResult) => { + const propDef = createFlowPropDef(propName, docgenInfo); + + return applyJsDocResult(propDef, jsDocParsingResult); }; -export const unknownFactory: PropDefFactory = ( - propName: string, - docgenInfo: DocgenInfo, - jsDocParsingResult?: JsDocParsingResult -) => { - return createPropDef(propName, { name: 'unknown' }, docgenInfo, 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 => { diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.test.ts b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts index 64a1a67dd6d9..d2b7c57aa0a5 100644 --- a/addons/docs/src/lib/docgen/extractDocgenProps.test.ts +++ b/addons/docs/src/lib/docgen/extractDocgenProps.test.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ import { Component } from '../../blocks/shared'; -import { extractPropsFromDocgen } from './extractDocgenProps'; +import { extractComponentProps } from './extractDocgenProps'; const DOCGEN_SECTION = 'props'; const PROP_NAME = 'propName'; @@ -14,6 +14,7 @@ interface TypeSystemDef { 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> { @@ -53,103 +54,127 @@ function createComponent(docgenInfo: Record<string, any>): Component { } TypeSystems.forEach(x => { - it('should map defaults docgen info properly', () => { - const component = createComponent({ - ...createStringType(x), - description: 'Hey! Hey!', - defaultValue: { - value: 'Default', - }, + 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'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + it('should remove JSDoc tags from the description', () => { + const component = createComponent({ + ...createStringType(x), + description: 'Hey!\n@param event\nreturns {string}', + }); - 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'); - }); + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; - it('should remove JSDoc tags from the description', () => { - const component = createComponent({ - ...createStringType(x), - description: 'Hey!\n@param event\nreturns {string}', + expect(propDef.description).toBe('Hey!'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + 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', + }); - expect(propDef.description).toBe('Hey!'); - }); + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; - 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', + expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + 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', + }); - expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); - }); + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; - 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', + expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + it('should not remove markdown from description without JSDoc tags', () => { + const component = createComponent({ + ...createStringType(x), + description: 'onClick *emphasis*, **strong**, `formatted` description.', + }); - expect(propDef.description).toBe('onClick description\nis a\nmulti-lines\ndescription'); - }); + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; - it('should not remove markdown from description without JSDoc tags', () => { - const component = createComponent({ - ...createStringType(x), - description: 'onClick *emphasis*, **strong**, `formatted` description.', + expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + it('should not remove markdown from description with JSDoc tags', () => { + const component = createComponent({ + ...createFuncType(x), + description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event', + }); - expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); - }); + const { propDef } = extractComponentProps(component, DOCGEN_SECTION)[0]; - it('should not remove markdown from description with JSDoc tags', () => { - const component = createComponent({ - ...createFuncType(x), - description: 'onClick *emphasis*, **strong**, `formatted` description.\n@param event', + expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + it('should return null when the property is marked with @ignore', () => { + const component = createComponent({ + ...createStringType(x), + description: 'onClick description\n@ignore', + }); - expect(propDef.description).toBe('onClick *emphasis*, **strong**, `formatted` description.'); - }); + expect(extractComponentProps(component, DOCGEN_SECTION).length).toBe(0); + }); - it('should return null when the property is marked with @ignore', () => { - const component = createComponent({ - ...createStringType(x), - description: 'onClick description\n@ignore', + 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(); }); - expect(extractPropsFromDocgen(component, DOCGEN_SECTION).length).toBe(0); - }); + it("should not return 'null' default value", () => { + const component = createComponent({ + ...createStringType(x), + defaultValue: { value: 'null' }, + }); - 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.defaultValue).toBeNull(); }); - const { propDef } = extractPropsFromDocgen(component, DOCGEN_SECTION)[0]; + it("should not return 'undefined' default value", () => { + const component = createComponent({ + ...createStringType(x), + defaultValue: { value: 'undefined' }, + }); + + 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(); + expect(propDef.defaultValue).toBeNull(); + }); }); }); diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.ts b/addons/docs/src/lib/docgen/extractDocgenProps.ts index adcf4d906da6..939b522d6f09 100644 --- a/addons/docs/src/lib/docgen/extractDocgenProps.ts +++ b/addons/docs/src/lib/docgen/extractDocgenProps.ts @@ -3,7 +3,7 @@ import { PropDef } from '@storybook/components'; import { Component } from '../../blocks/shared'; import { ExtractedJsDoc, parseJsDoc } from '../jsdocParser'; import { DocgenInfo, TypeSystem } from './types'; -import { getDocgenSection, isValidDocgenSection } from './utils'; +import { getDocgenSection, isValidDocgenSection, getDocgenDescription } from './utils'; import { getPropDefFactory, PropDefFactory } from './createPropDef'; export interface ExtractedProp { @@ -31,13 +31,16 @@ const getTypeSystem = (docgenInfo: DocgenInfo): TypeSystem => { return TypeSystem.UNKNOWN; }; -export const extractPropsFromDocgen: ExtractProps = (component, section) => { - const docgenSection = getDocgenSection(component, section); +export const extractComponentSectionArray = (docgenSection: any) => { + const typeSystem = getTypeSystem(docgenSection[0]); + const createPropDef = getPropDefFactory(typeSystem); - if (!isValidDocgenSection(docgenSection)) { - return []; - } + 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); @@ -50,7 +53,20 @@ export const extractPropsFromDocgen: ExtractProps = (component, section) => { ? extractProp(propName, docgenInfo, typeSystem, createPropDef) : null; }) - .filter(x => x); + .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( @@ -75,3 +91,7 @@ function extractProp( 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/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 index fd560c9edc76..b59af2b3ec99 100644 --- a/addons/docs/src/lib/docgen/types.ts +++ b/addons/docs/src/lib/docgen/types.ts @@ -3,25 +3,29 @@ import { Component } from '../../blocks/shared'; export type PropsExtractor = (component: Component) => PropsTableProps | null; -export interface DocgenBaseType { +export interface DocgenType { name: string; description?: string; - require?: boolean; + required?: boolean; } -export interface DocgenPropType extends DocgenBaseType { +export interface DocgenPropType extends DocgenType { value?: any; raw?: string; computed?: boolean; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DocgenFlowType extends DocgenBaseType {} +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 DocgenBaseType {} +export interface DocgenTypeScriptType extends DocgenType {} -export type DocgenType = DocgenPropType | DocgenFlowType | DocgenTypeScriptType; +// export type DocgenType = DocgenPropType | DocgenFlowType | DocgenTypeScriptType; export interface DocgenPropDefaultValue { value: string; 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.ts b/addons/docs/src/lib/docgen/utils/docgenInfo.ts similarity index 52% rename from addons/docs/src/lib/docgen/utils.ts rename to addons/docs/src/lib/docgen/utils/docgenInfo.ts index 4bdc8c7138a5..623047c7e380 100644 --- a/addons/docs/src/lib/docgen/utils.ts +++ b/addons/docs/src/lib/docgen/utils/docgenInfo.ts @@ -1,17 +1,8 @@ /* eslint-disable no-underscore-dangle */ import { isNil } from 'lodash'; -import { Component } from '../../blocks/shared'; - -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)}`); -}; +import { Component } from '../../../blocks/shared'; +import { str } from './string'; export function hasDocgen(component: Component): boolean { return !!component.__docgenInfo; @@ -25,5 +16,6 @@ export function getDocgenSection(component: Component, section: string): any { return hasDocgen(component) ? component.__docgenInfo[section] : null; } -export const extractComponentDescription = (component?: Component) => - component && hasDocgen(component) && str(component.__docgenInfo.description); +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/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/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/__testfixtures__/component-id.output.snapshot b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot index c3cf1c46ffad..28cd4ed4165d 100644 --- a/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/component-id.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin component-id.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -39,14 +39,16 @@ componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button> const componentMeta = { title: 'Button', id: 'button-id', includeStories: ['componentNotes'] }; -const mdxStoryNameToId = { 'component notes': 'button--component-notes' }; +const mdxStoryNameToId = { 'component notes': 'button-id--component-notes' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot index b398e22a6b7c..bded3c333d21 100644 --- a/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/decorators.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin decorators.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -77,10 +77,12 @@ const mdxStoryNameToId = { one: 'button--one' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index 2b9c0bf30d90..59f28b9c4b4b 100644 --- a/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/docs-only.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin docs-only.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta } from '@storybook/addon-docs/blocks'; @@ -46,10 +46,12 @@ const mdxStoryNameToId = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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..64bb0d0c7608 --- /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 mdxStoryNameToId = {}; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <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 index ff3a88ccf2c1..39a4e7fab4ae 100644 --- a/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/non-story-exports.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin non-story-exports.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -55,10 +55,12 @@ const mdxStoryNameToId = { one: 'button--one', 'hello story': 'button--hello-sto componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot index d0825f6c34e4..600811ad42ad 100644 --- a/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/parameters.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin parameters.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -78,10 +78,12 @@ const mdxStoryNameToId = { componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot index 2ba1c7bf9af1..cc9e55f146d8 100644 --- a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin previews.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Preview, Story, Meta } from '@storybook/addon-docs/blocks'; @@ -74,10 +74,12 @@ const mdxStoryNameToId = { 'hello button': 'button--hello-button', two: 'button- componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot index 5b1418ec9af9..ba937af8a3ee 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-current.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-current.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story } from '@storybook/addon-docs/blocks'; @@ -35,10 +35,12 @@ const mdxStoryNameToId = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index 310639df0a51..a37003d15677 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-def-text-only.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-def-text-only.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -32,7 +32,7 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; -export const text = makeStoryFn('Plain text'); +export const text = () => 'Plain text'; text.story = {}; text.story.name = 'text'; text.story.parameters = { mdxSource: \\"'Plain text'\\" }; @@ -43,10 +43,12 @@ const mdxStoryNameToId = { text: 'text--text' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index 3ed503cdc384..a0b17d0ba010 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-definitions.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-definitions.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; @@ -76,10 +76,12 @@ const mdxStoryNameToId = { componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index 27da826cdc29..6f5d19b494d8 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-function-var.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-function-var.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Meta, Story } from '@storybook/addon-docs/blocks'; export const basicFn = () => <Button mdxType=\\"Button\\" />; @@ -36,7 +36,7 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; -export const basic = makeStoryFn(basicFn); +export const basic = assertIsFn(basicFn); basic.story = {}; basic.story.name = 'basic'; basic.story.parameters = { mdxSource: 'basicFn' }; @@ -47,10 +47,12 @@ const mdxStoryNameToId = { basic: 'story-function-var--basic' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index 54623560ee5e..495335bfdcd8 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-function.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-function.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; const makeShortcode = name => function MDXDefaultShortcode(props) { @@ -33,12 +33,12 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; -export const functionStory = makeStoryFn(() => { +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 = { @@ -52,10 +52,12 @@ const mdxStoryNameToId = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot index 881825b05980..bcd7e17e4b99 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-object.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-object.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story, Meta } from '@storybook/addon-docs/blocks'; import { Welcome, Button } from '@storybook/angular/demo'; @@ -42,7 +42,7 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; -export const toStorybook = makeStoryFn({ +export const toStorybook = () => ({ template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`, props: { showApp: linkTo('Button'), @@ -64,10 +64,12 @@ const mdxStoryNameToId = { 'to storybook': 'mdx-welcome--to-storybook' }; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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 index a96403ad036e..a00522b285b8 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-references.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin story-references.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Story } from '@storybook/addon-docs/blocks'; @@ -35,10 +35,12 @@ const mdxStoryNameToId = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; 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..b6f807d4daf8 --- /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 mdxStoryNameToId = {}; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <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 index e4a19e2ee010..854a773b70cb 100644 --- a/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/vanilla.output.snapshot @@ -2,7 +2,7 @@ exports[`docs-mdx-compiler-plugin vanilla.mdx 1`] = ` "/* @jsx mdx */ -import { DocsContainer, makeStoryFn } from '@storybook/addon-docs/blocks'; +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; @@ -36,10 +36,12 @@ const mdxStoryNameToId = {}; componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { - container: ({ context, children }) => ( - <DocsContainer context={{ ...context, mdxStoryNameToId }}>{children}</DocsContainer> + ...(componentMeta.parameters.docs || {}), + page: () => ( + <AddContext mdxStoryNameToId={mdxStoryNameToId}> + <MDXContent /> + </AddContext> ), - page: MDXContent, }; export default componentMeta; diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.js b/addons/docs/src/mdx/mdx-compiler-plugin.js index 1d489ce1ff0f..5c2f788143cc 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.js @@ -3,7 +3,7 @@ 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'); +const { toId, storyNameFromExport } = require('@storybook/csf'); // Generate the MDX as is, but append named exports for every // story in the contents @@ -55,7 +55,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 +64,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,12 +136,28 @@ 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, {}); @@ -151,7 +175,7 @@ function genMeta(ast) { }; } -function getExports(node, counter) { +function getExports(node, counter, options) { const { value, type } = node; if (type === 'jsx') { if (STORY_REGEX.exec(value)) { @@ -168,7 +192,7 @@ 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; @@ -179,8 +203,8 @@ function getExports(node, counter) { 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 mdxStoryNameToId={mdxStoryNameToId}><MDXContent /></AddContext>, }; `.trim(); @@ -270,7 +294,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) { @@ -298,11 +322,11 @@ function extractExports(node, options) { } metaExport.includeStories = JSON.stringify(includeStories); - const { title } = metaExport; + const { title, id: componentId } = metaExport; const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce( (acc, [storyName, storyKey]) => { if (title) { - acc[storyName] = toId(title, storyNameFromExport(storyKey)); + acc[storyName] = toId(componentId || title, storyNameFromExport(storyKey)); } return acc; }, @@ -310,7 +334,7 @@ function extractExports(node, options) { ); const fullJsx = [ - 'import { DocsContainer, makeStoryFn } from "@storybook/addon-docs/blocks";', + 'import { assertIsFn, AddContext } from "@storybook/addon-docs/blocks";', defaultJsx, ...storyExports, `const componentMeta = ${stringifyMeta(metaExport)};`, 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/typings.d.ts b/addons/docs/src/typings.d.ts index 179e926ed6ae..dc9928da3997 100644 --- a/addons/docs/src/typings.d.ts +++ b/addons/docs/src/typings.d.ts @@ -4,3 +4,5 @@ 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 8c8348995e43..0a322acaeb23 100644 --- a/addons/docs/vue/README.md +++ b/addons/docs/vue/README.md @@ -22,10 +22,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` presets: ```js -module.exports = ['@storybook/addon-docs/preset']; +module.exports = { + presets: ['@storybook/addon-docs/preset'], +}; ``` ## DocsPage @@ -66,16 +68,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 +104,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/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..1a09f22a8dbe --- /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 = { + presets: ['@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 = { + presets: [{ + 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..ffe5ceaf0984 --- /dev/null +++ b/addons/essentials/package.json @@ -0,0 +1,49 @@ +{ + "name": "@storybook/addon-essentials", + "version": "5.3.0-rc.0", + "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": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/node-logger": "5.3.0-rc.0", + "ts-dedent": "^1.1.0" + }, + "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": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" +} diff --git a/addons/essentials/src/index.ts b/addons/essentials/src/index.ts new file mode 100644 index 000000000000..fc9dbd0f1255 --- /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 addons(entry: any[] = [], options: PresetOptions = {}) { + const registerAddons = ['backgrounds', 'viewport'] + .filter(key => (options as any)[key] !== false) + .map(key => makeAddon(key)) + .filter(addon => !isInstalled(addon)) + .map(addon => `${addon}/register`); + return [...entry, ...registerAddons]; +} 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..eac4a67bed71 --- /dev/null +++ b/addons/essentials/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env", "jest"] + }, + "include": ["src/**/*"], + "exclude": ["src/**.test.ts"] +} diff --git a/addons/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 b828413a533a..307a205b75fe 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -31,11 +31,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "format-json": "^1.0.3", "lodash": "^4.17.15", @@ -50,5 +50,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 d90f5108f72c..3880c31ad0e2 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.45", + "version": "5.3.0-rc.0", "description": "Storybook addon for google analytics", "keywords": [ "addon", @@ -20,13 +20,14 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react-ga": "^2.5.7" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 e2e4f169ccd4..4b6a679d1826 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "addon", @@ -29,8 +29,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "graphiql": "^0.16.0", @@ -42,5 +42,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/info/README.md b/addons/info/README.md index 4fd0017833af..7a19479ec3f8 100644 --- a/addons/info/README.md +++ b/addons/info/README.md @@ -23,7 +23,7 @@ It is possible to add `info` by default to all or a subsection of stories by usi It is important to declare this decorator as **the first decorator**, otherwise it won't work well. ```js -// Globally in your .storybook/config.js. +// Globally in your .storybook/preview.js. import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; @@ -33,9 +33,10 @@ addDecorator(withInfo); or ```js -storiesOf('Component', module) - .addDecorator(withInfo) // At your stories directly. - .add(...); +export default { + title: 'Component', + decorators: [withInfo], +}; ``` Then, you can use the `info` parameter to either pass certain options or specific documentation text to your stories. @@ -43,88 +44,39 @@ A complete list of possible configurations can be found [in a later section](#se 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 />); +export default { + title: 'Component', + parameters: { + info: {}, + }, +}; ``` ...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'; +export default { + title: '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! - }); +export const defaultView = () => <Component />; +defaultView = { + parameters: { + info: { inline: true }, + }, +}; ``` It is also possible to disable the `info` addon entirely. Depending on the scope at which you want to disable the addon, pass the following parameters object either to an individual story or to an `addParameters` call. ``` -{ - info: { - disable: true - } +info: { + disable: true, } ``` @@ -134,30 +86,27 @@ 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> - ~~~ - `, - }, -}); +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`. +To configure default options for all usage of the info addon, pass a option object along with the decorator in `.storybook/preview.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. + header: false, }) ); ``` @@ -298,6 +247,14 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import Button from './button'; +export default { + title: 'Button', + component: Button, + parameters: { + info: TableComponent, + }, +}; + const Red = props => <span style={{ color: 'red' }} {...props} />; const TableComponent = ({ propDefinitions }) => { @@ -332,11 +289,7 @@ const TableComponent = ({ propDefinitions }) => { ); }; -storiesOf('Button', module).add('with text', () => <Button>Hello Button</Button>, { - info: { - TableComponent, - }, -}); +export const defaultView = () => <Button />; ``` ### React Docgen Integration diff --git a/addons/info/package.json b/addons/info/package.json index c60f8879537c..e4cae57ca153 100644 --- a/addons/info/package.json +++ b/addons/info/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-info", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "A Storybook addon to show additional information for your stories.", "keywords": [ "addon", @@ -28,10 +28,10 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "marksy": "^7.0.0", @@ -52,5 +52,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/info/src/__snapshots__/index.test.js.snap b/addons/info/src/__snapshots__/index.test.js.snap index 8108e71d3a94..7dc48103b4f2 100644 --- a/addons/info/src/__snapshots__/index.test.js.snap +++ b/addons/info/src/__snapshots__/index.test.js.snap @@ -118,7 +118,7 @@ exports[`addon Info should render <Info /> and external markdown 1`] = ` "cursor": "pointer", "display": "block", "fontFamily": "sans-serif", - "fontSize": "12px", + "fontSize": 12, "padding": "5px 15px", "position": "fixed", "right": 0, @@ -253,7 +253,7 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)" "cursor": "pointer", "display": "block", "fontFamily": "sans-serif", - "fontSize": "12px", + "fontSize": 12, "padding": "5px 15px", "position": "fixed", "right": 0, @@ -386,7 +386,7 @@ exports[`addon Info should render <Info /> for memoized component 1`] = ` "cursor": "pointer", "display": "block", "fontFamily": "sans-serif", - "fontSize": "12px", + "fontSize": 12, "padding": "5px 15px", "position": "fixed", "right": 0, @@ -1095,6 +1095,7 @@ exports[`addon Info should render component description if story kind matches co "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 */", @@ -1365,12 +1366,12 @@ exports[`addon Info should render component description if story kind matches co "insert": [Function], "inserted": Object { "11xgcgt": true, + "1fhpnuv": true, "1imo1gr": true, "1maezg8": true, "1si67pu": true, "4zr3vl": true, "esgpkx": true, - "scc5fi": true, }, "key": "css", "nonce": undefined, @@ -2626,6 +2627,11 @@ exports[`addon Info should render component description if story kind matches co > <l className="emotion-2" + scrollableNodeProps={ + Object { + "tabIndex": 0, + } + } > <div className="emotion-2" @@ -2649,6 +2655,7 @@ exports[`addon Info should render component description if story kind matches co > <div className="simplebar-content-wrapper" + tabIndex={0} > <div className="simplebar-content" @@ -2879,6 +2886,7 @@ exports[`addon Info should render component description if story kind matches co "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 */", @@ -3149,12 +3157,12 @@ exports[`addon Info should render component description if story kind matches co "insert": [Function], "inserted": Object { "11xgcgt": true, + "1fhpnuv": true, "1imo1gr": true, "1maezg8": true, "1si67pu": true, "4zr3vl": true, "esgpkx": true, - "scc5fi": true, }, "key": "css", "nonce": undefined, @@ -4410,6 +4418,11 @@ exports[`addon Info should render component description if story kind matches co > <l className="emotion-2" + scrollableNodeProps={ + Object { + "tabIndex": 0, + } + } > <div className="emotion-2" @@ -4433,6 +4446,7 @@ exports[`addon Info should render component description if story kind matches co > <div className="simplebar-content-wrapper" + tabIndex={0} > <div className="simplebar-content" @@ -5654,6 +5668,7 @@ exports[`addon Info should render component description if story name matches co "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 */", @@ -5924,12 +5939,12 @@ exports[`addon Info should render component description if story name matches co "insert": [Function], "inserted": Object { "11xgcgt": true, + "1fhpnuv": true, "1imo1gr": true, "1maezg8": true, "1si67pu": true, "4zr3vl": true, "esgpkx": true, - "scc5fi": true, }, "key": "css", "nonce": undefined, @@ -7413,6 +7428,11 @@ exports[`addon Info should render component description if story name matches co > <l className="emotion-2" + scrollableNodeProps={ + Object { + "tabIndex": 0, + } + } > <div className="emotion-2" @@ -7436,6 +7456,7 @@ exports[`addon Info should render component description if story name matches co > <div className="simplebar-content-wrapper" + tabIndex={0} > <div className="simplebar-content" @@ -7666,6 +7687,7 @@ exports[`addon Info should render component description if story name matches co "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 */", @@ -7936,12 +7958,12 @@ exports[`addon Info should render component description if story name matches co "insert": [Function], "inserted": Object { "11xgcgt": true, + "1fhpnuv": true, "1imo1gr": true, "1maezg8": true, "1si67pu": true, "4zr3vl": true, "esgpkx": true, - "scc5fi": true, }, "key": "css", "nonce": undefined, @@ -9425,6 +9447,11 @@ exports[`addon Info should render component description if story name matches co > <l className="emotion-2" + scrollableNodeProps={ + Object { + "tabIndex": 0, + } + } > <div className="emotion-2" @@ -9448,6 +9475,7 @@ exports[`addon Info should render component description if story name matches co > <div className="simplebar-content-wrapper" + tabIndex={0} > <div className="simplebar-content" diff --git a/addons/info/src/components/Story.js b/addons/info/src/components/Story.js index 051d603cae8b..42f5026f0808 100644 --- a/addons/info/src/components/Story.js +++ b/addons/info/src/components/Story.js @@ -18,7 +18,7 @@ const stylesheetBase = { button: { base: { fontFamily: 'sans-serif', - fontSize: '12px', + fontSize: 12, display: 'block', position: 'fixed', border: 'none', diff --git a/addons/info/src/components/markdown/pre/copyButton.js b/addons/info/src/components/markdown/pre/copyButton.js index e92c0da3166b..cf44fa8c3b53 100644 --- a/addons/info/src/components/markdown/pre/copyButton.js +++ b/addons/info/src/components/markdown/pre/copyButton.js @@ -16,11 +16,11 @@ function CopyButton({ onClick, toggled }) { alignSelf: 'flex-start', flexShrink: '0', overflow: 'hidden', - borderWidth: '1px', + borderWidth: 1, borderStyle: 'solid', borderColor: 'rgb(238, 238, 238)', borderImage: 'initial', - borderRadius: '3px', + borderRadius: 3, padding: '3px 10px', }} > 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 5407c59d1937..7fba6ed7a0fa 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -35,11 +35,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -52,5 +52,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 b00b25b4f366..4f8caa1a6ce3 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 withKnobs = () => ({ 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 'htmlbars-inline-precompile'; + +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); ``` @@ -348,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'; @@ -454,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 4e152e3506a8..07d665af93d8 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-knobs", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook Addon Prop Editor Component", "keywords": [ "addon", @@ -29,12 +29,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "@types/react-color": "^3.0.1", "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", @@ -58,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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/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/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..0bce78742e0d --- /dev/null +++ b/addons/knobs/src/preset/index.ts @@ -0,0 +1,15 @@ +type KnobsOptions = { + addDecorator?: boolean; +}; + +export function addons(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 9cd646e71d6e..335b74c26e13 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Story Links addon for storybook", "keywords": [ "addon", @@ -29,10 +29,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/router": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/csf": "0.0.1", + "@storybook/router": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "prop-types": "^15.7.2", @@ -44,5 +45,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 index 0211aab0fcbf..15f1ed96edd5 100644 --- a/addons/notes/README.md +++ b/addons/notes/README.md @@ -14,81 +14,34 @@ Storybook Addon Notes allows you to write notes (text or HTML) for your stories yarn add -D @storybook/addon-notes ``` -Then create a file called `addons.js` in your Storybook config. - -Add following content to it: +within `.storybook/main.js`: ```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'; +module.exports = { + addons: ['@storybook/addon-notes/register'] +} ``` -Now, you can use the `notes` parameter to add a note to each story. - -### With React +Alternatively register the notes addon into a panel. Choose only one, not both. ```js -import { storiesOf } from '@storybook/react'; - -import Component from './Component'; - -storiesOf('Component', module).add('with some emoji', () => <Component />, { - notes: 'An example of addon notes', -}); +module.exports = { + addons: ['@storybook/addon-notes/register-panel'] +} ``` -### 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', - } -); -``` +Now, you can use the `notes` parameter to add a note to each story. -### 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', - } -); -``` - -### Upgrading to CSF Format - -Add `notes` to the `parameters` object: +import Component from './Component'; -```js export default { + title: 'Component', parameters: { - notes: 'My notes', - } -} + notes: 'some documentation here', + }, +}; ``` ## Using Markdown @@ -96,13 +49,19 @@ export default { 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'; +import markdown from './someMarkdownText.md'; -storiesOf('Component', module).add('With Markdown', () => <Component />, { - notes: { markdown: markdownNotes }, -}); +export default { + title: 'Component', +}; + +export const withMarkdown = () => <Component />; +withmarkdown.story = { + parameters: { + notes: { markdown }, + } +}; ``` ## Giphy @@ -122,10 +81,14 @@ If you need to display different notes for different consumers of your storybook ```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 }, -}); +export default { + title: 'Component', + parameters: { + notes: { Introduction: intro, 'Design Notes': design }, + }, +}; ``` diff --git a/addons/notes/package.json b/addons/notes/package.json index a18892bce89d..6744aa425139 100644 --- a/addons/notes/package.json +++ b/addons/notes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-notes", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Write notes for your Storybook stories.", "keywords": [ "addon", @@ -30,13 +30,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/router": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/router": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "markdown-to-jsx": "^6.10.3", @@ -54,5 +54,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/ondevice-actions/package.json b/addons/ondevice-actions/package.json index b860d298b6a6..3028b4a5bc9f 100644 --- a/addons/ondevice-actions/package.json +++ b/addons/ondevice-actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-ondevice-actions", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Action Logger addon for react-native storybook", "keywords": [ "storybook" @@ -26,13 +26,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "fast-deep-equal": "^2.0.1" }, "devDependencies": { - "@storybook/addon-actions": "5.3.0-alpha.45" + "@storybook/addon-actions": "5.3.0-rc.0" }, "peerDependencies": { "@storybook/addon-actions": "*", @@ -41,5 +41,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/ondevice-backgrounds/package.json b/addons/ondevice-backgrounds/package.json index 28848d14a77d..6281630a3c41 100644 --- a/addons/ondevice-backgrounds/package.json +++ b/addons/ondevice-backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-ondevice-backgrounds", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "A react-native storybook addon to show different backgrounds for your preview", "keywords": [ "addon", @@ -31,9 +31,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", "core-js": "^3.0.1", "prop-types": "^15.7.2" }, @@ -43,5 +43,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/ondevice-knobs/package.json b/addons/ondevice-knobs/package.json index e9454a204ca7..c5f2cc2b8dd6 100644 --- a/addons/ondevice-knobs/package.json +++ b/addons/ondevice-knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-ondevice-knobs", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Display storybook story knobs on your deviced.", "keywords": [ "addon", @@ -28,8 +28,8 @@ }, "dependencies": { "@emotion/native": "^10.0.14", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "deep-equal": "^1.0.1", "prop-types": "^15.7.2", @@ -39,11 +39,12 @@ "react-native-switch": "^1.5.0" }, "peerDependencies": { - "@storybook/addon-knobs": "5.2.0-alpha.34", + "@storybook/addon-knobs": "5.3.0-rc.0", "react": "*", "react-native": "*" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/ondevice-notes/package.json b/addons/ondevice-notes/package.json index 31c23cc65291..5b4095649818 100644 --- a/addons/ondevice-notes/package.json +++ b/addons/ondevice-notes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-ondevice-notes", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Write notes for your react-native Storybook stories.", "keywords": [ "addon", @@ -28,11 +28,11 @@ }, "dependencies": { "@emotion/core": "^10.0.20", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "prop-types": "^15.7.2", "react-native-simple-markdown": "^1.1.0" @@ -43,5 +43,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/addons/options/README.md b/addons/options/README.md index 628ad9ee9c5e..854cc8b45c54 100644 --- a/addons/options/README.md +++ b/addons/options/README.md @@ -1,136 +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, - /** - * display the top-level grouping as a "root" in the sidebar - * @type {Boolean} - */ - showRoots: 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 e7f376a5ed1e..bc3c266eb093 100644 --- a/addons/options/package.json +++ b/addons/options/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-options", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Options addon for storybook", "keywords": [ "addon", @@ -29,7 +29,7 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", "core-js": "^3.0.1", "util-deprecate": "^1.0.2" }, @@ -38,5 +38,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 e5ffee222e6c..3a54936d194f 100644 --- a/addons/queryparams/package.json +++ b/addons/queryparams/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-queryparams", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -30,12 +30,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "qs": "^6.6.0", @@ -44,5 +44,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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/.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..e2696305ef77 100644 --- a/addons/storyshots/storyshots-core/README.md +++ b/addons/storyshots/storyshots-core/README.md @@ -385,7 +385,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 +425,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 +653,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 6de591c8cb30..a0c34385a0e2 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.45", + "version": "5.3.0-rc.0", "description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.", "keywords": [ "addon", @@ -33,11 +33,13 @@ }, "dependencies": { "@jest/transform": "^24.9.0", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@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 +49,8 @@ "ts-dedent": "^1.1.0" }, "devDependencies": { - "@storybook/addon-docs": "5.3.0-alpha.45", - "@storybook/react": "5.3.0-alpha.45", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/react": "5.3.0-rc.0", "babel-loader": "^8.0.6", "enzyme-to-json": "^3.4.1", "jest-emotion": "^10.0.17", @@ -56,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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/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/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..f334e98658b3 100644 --- a/addons/storyshots/storyshots-core/src/typings.d.ts +++ b/addons/storyshots/storyshots-core/src/typings.d.ts @@ -2,3 +2,5 @@ declare module 'global'; declare module 'jest-preset-angular/*'; declare module 'preact-render-to-json'; declare module 'react-test-renderer*'; +declare module '@storybook/core/server'; +declare module 'babel-plugin-require-context-hook/register'; 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..718c0c012435 100644 --- a/addons/storyshots/storyshots-puppeteer/README.md +++ b/addons/storyshots/storyshots-puppeteer/README.md @@ -1,33 +1,50 @@ +# 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 +## Configure Storyshots for Puppeteeer tests /\*\ **React-native** is **not supported** by this test function. -Internally, it uses [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot). - -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 +52,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 +60,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'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'file:///path/to/my/storybook-static' }), -}); -``` - -### 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. - -```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 +95,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 +144,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 +154,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 + +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. -You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served. +### 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 +180,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 01c949e67817..71a0687c3116 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.45", + "version": "5.3.0-rc.0", "description": "Image snapshots addition to StoryShots based on puppeteer", "keywords": [ "addon", @@ -29,19 +29,28 @@ "prepare": "node ../../../scripts/prepare.js" }, "dependencies": { - "@storybook/node-logger": "5.3.0-alpha.45", - "@storybook/router": "5.3.0-alpha.45", + "@hypnosphi/jest-puppeteer-axe": "^1.4.0", + "@storybook/csf": "0.0.1", + "@storybook/node-logger": "5.3.0-rc.0", "@types/jest-image-snapshot": "^2.8.0", - "@types/puppeteer-core": "^1.9.0", "core-js": "^3.0.1", "jest-image-snapshot": "^2.8.2", - "puppeteer-core": "^2.0.0", "regenerator-runtime": "^0.13.3" }, + "devDependencies": { + "@types/puppeteer": "^2.0.0" + }, "peerDependencies": { - "@storybook/addon-storyshots": "5.3.0-alpha.13" + "@storybook/addon-storyshots": "5.3.0-rc.0", + "puppeteer": "^1.12.2 || ^2.0.0" }, "publishConfig": { "access": "public" + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a", + "peerDependenciesMeta": { + "puppeteer": { + "optional": true + } } } 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/axeTest.ts b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts new file mode 100644 index 000000000000..920af43c09e9 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts @@ -0,0 +1,23 @@ +import '@hypnosphi/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..49083c46ea66 --- /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 } = 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, kind, name); + 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..6c5a1eaef648 100644 --- a/addons/storyshots/storyshots-puppeteer/src/url.ts +++ b/addons/storyshots/storyshots-puppeteer/src/url.ts @@ -1,4 +1,4 @@ -import { toId } from '@storybook/router/utils'; +import { toId } from '@storybook/csf'; import { URL } from 'url'; diff --git a/addons/storysource/package.json b/addons/storysource/package.json index 260730253996..8def523c07b5 100644 --- a/addons/storysource/package.json +++ b/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Stories addon for storybook", "keywords": [ "addon", @@ -28,11 +28,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/router": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/router": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "estraverse": "^4.2.0", "loader-utils": "^1.2.3", @@ -48,5 +48,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 9305958f3471..c784002d1a0b 100644 --- a/addons/viewport/package.json +++ b/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook addon to change the viewport size to mobile", "keywords": [ "addon", @@ -23,17 +23,18 @@ "*.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.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "memoizerific": "^1.11.3", @@ -48,5 +49,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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..98d38e5addd7 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: 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 ad279b257764..f558168729d2 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,9 +33,9 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", - "@storybook/node-logger": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", + "@storybook/node-logger": "5.3.0-rc.0", "core-js": "^3.0.1", "fork-ts-checker-webpack-plugin": "^3.0.1", "global": "^4.3.2", @@ -71,5 +71,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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..fb7ec1935646 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,23 @@ 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); }); }); 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..fd98d622cb84 100644 --- a/app/angular/src/server/angular-cli_config.ts +++ b/app/angular/src/server/angular-cli_config.ts @@ -57,6 +57,10 @@ 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) { @@ -70,19 +74,18 @@ export function getLeadingAngularCliProject(ngCliConfig: any) { 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); diff --git a/app/ember/package.json b/app/ember/package.json index 0825b39d4f88..ec0ae48cc166 100644 --- a/app/ember/package.json +++ b/app/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/master/app/ember", "bugs": { @@ -31,7 +31,7 @@ }, "dependencies": { "@ember/test-helpers": "^1.5.0", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/core": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -48,5 +48,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/html/package.json b/app/html/package.json index e35703d88daf..0f31b1b6c968 100644 --- a/app/html/package.json +++ b/app/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,8 +33,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@types/webpack-env": "^1.13.9", "core-js": "^3.0.1", "global": "^4.3.2", @@ -50,5 +50,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/marko/package.json b/app/marko/package.json index ed6750cb7aed..282a5f047aec 100644 --- a/app/marko/package.json +++ b/app/marko/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/marko", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "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.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -49,5 +49,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/mithril/package.json b/app/mithril/package.json index e8fae4651d5d..208449fe5c91 100644 --- a/app/mithril/package.json +++ b/app/mithril/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/mithril", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Mithril: Develop Mithril Component in isolation.", "keywords": [ "storybook" @@ -35,8 +35,8 @@ "dependencies": { "@babel/core": "^7.6.2", "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@types/mithril": "^2.0.0", "core-js": "^3.0.1", "global": "^4.3.2", @@ -55,5 +55,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/polymer/package.json b/app/polymer/package.json index c9f1d25dd08f..0b8d95d2b478 100644 --- a/app/polymer/package.json +++ b/app/polymer/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/polymer", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Polymer: Develop Polymer components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -32,7 +32,7 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "5.3.0-alpha.45", + "@storybook/core": "5.3.0-rc.0", "@webcomponents/webcomponentsjs": "^1.2.0", "core-js": "^3.0.1", "global": "^4.3.2", @@ -54,5 +54,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/preact/package.json b/app/preact/package.json index 3f37f2a9942a..fe3ce9e4202c 100644 --- a/app/preact/package.json +++ b/app/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" @@ -34,8 +34,8 @@ }, "dependencies": { "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@types/webpack-env": "^1.13.9", "core-js": "^3.0.1", "global": "^4.3.2", @@ -55,5 +55,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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/rax/package.json b/app/rax/package.json index d9ec06bb73f4..387eaca19a37 100644 --- a/app/rax/package.json +++ b/app/rax/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/rax", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "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.45", + "@storybook/core": "5.3.0-rc.0", "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": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/react-native-server/package.json b/app/react-native-server/package.json index 79b7642a4d5f..e58feefd74d4 100644 --- a/app/react-native-server/package.json +++ b/app/react-native-server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-native-server", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "A better way to develop React Native Components for your app", "keywords": [ "react", @@ -31,13 +31,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/channel-websocket": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/ui": "5.3.0-alpha.45", - "commander": "^3.0.2", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/channel-websocket": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/ui": "5.3.0-rc.0", + "commander": "^4.0.1", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.6.0", @@ -54,5 +54,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/react-native/package.json b/app/react-native/package.json index de1d1563042c..17636fe745d7 100644 --- a/app/react-native/package.json +++ b/app/react-native/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-native", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "A better way to develop React Native Components for your app", "keywords": [ "react", @@ -31,11 +31,11 @@ "dependencies": { "@emotion/core": "^10.0.20", "@emotion/native": "^10.0.14", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/channel-websocket": "5.3.0-alpha.45", - "@storybook/channels": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/channel-websocket": "5.3.0-rc.0", + "@storybook/channels": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", "core-js": "^3.0.1", "emotion-theming": "^10.0.19", "react-native-swipe-gestures": "^1.0.4" @@ -53,5 +53,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/react/README.md b/app/react/README.md index 63426d44391b..5576862408b1 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 diff --git a/app/react/package.json b/app/react/package.json index eb6672d04bc7..6963b134ee96 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -36,9 +36,9 @@ "@babel/plugin-transform-react-constant-elements": "^7.2.0", "@babel/preset-flow": "^7.0.0", "@babel/preset-react": "^7.0.0", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", - "@storybook/node-logger": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", + "@storybook/node-logger": "5.3.0-rc.0", "@svgr/webpack": "^4.0.3", "@types/webpack-env": "^1.13.7", "babel-plugin-add-react-displayname": "^0.0.5", @@ -57,8 +57,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" }, "peerDependencies": { "@babel/core": "^7.0.1", @@ -71,5 +71,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/react/src/server/cra-config.test.ts b/app/react/src/server/cra-config.test.ts index 20feeab66037..4c4425dd8482 100644 --- a/app/react/src/server/cra-config.test.ts +++ b/app/react/src/server/cra-config.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-mocks-import */ import fs from 'fs'; import path from 'path'; import { diff --git a/app/react/src/server/cra-config.ts b/app/react/src/server/cra-config.ts index b38418da6bc0..a614d4a7f16e 100644 --- a/app/react/src/server/cra-config.ts +++ b/app/react/src/server/cra-config.ts @@ -71,29 +71,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,23 +98,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 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[] - ); + return [ + ...accRules, + { + ...rule, + include: [rule.include, path.resolve(configDir)], + }, + ]; + }, [] as RuleSetRule[]); }; export const getModulePath = () => { @@ -197,6 +191,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()); } diff --git a/app/react/src/server/framework-preset-cra.ts b/app/react/src/server/framework-preset-cra.ts index 6c6f56c4d321..6461659522b2 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,10 +20,15 @@ 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)) { +export function webpackFinal( + config: Configuration, + { presetsList, configDir }: { presetsList: Preset[]; configDir: string } +) { + if (checkForNewPreset(presetsList)) { return config; } if (!isReactScriptsInstalled()) { @@ -40,8 +40,8 @@ export function webpackFinal(config: Configuration, { configDir }: { configDir: 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 +53,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/riot/package.json b/app/riot/package.json index 7fc15742474d..11f6e8f383b4 100644 --- a/app/riot/package.json +++ b/app/riot/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/riot", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "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.45", + "@storybook/core": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "raw-loader": "^3.1.0", @@ -58,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/svelte/package.json b/app/svelte/package.json index a65598c606f9..437c9e5aab2e 100644 --- a/app/svelte/package.json +++ b/app/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,8 +33,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -54,5 +54,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 d5417a3b29a4..f509c861627a 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -33,8 +33,8 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@types/webpack-env": "^1.13.9", "core-js": "^3.0.1", "global": "^4.3.2", @@ -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": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/app/web-components/README.md b/app/web-components/README.md index bb59107daade..6de3e192d2a0 100644 --- a/app/web-components/README.md +++ b/app/web-components/README.md @@ -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 = { + webpack: 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 99483732242f..025e0aa8eb1f 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.45", + "version": "5.3.0-rc.0", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit-html", @@ -37,8 +37,8 @@ "dependencies": { "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", "@types/webpack-env": "^1.13.9", "babel-plugin-bundled-import-meta": "^0.3.1", "core-js": "^3.0.1", @@ -58,5 +58,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } 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 4c6b201fc885..0b89a2b5cd75 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.45", + "version": "5.3.0-rc.0", "description": "decorator addon for storybook", "keywords": [ "addon", @@ -24,12 +24,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.4.0" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/dev-kits/addon-parameter/package.json b/dev-kits/addon-parameter/package.json index 43ec6613a0dc..5e0dcbd343cb 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.45", + "version": "5.3.0-rc.0", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -24,12 +24,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -37,5 +37,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/dev-kits/addon-preview-wrapper/package.json b/dev-kits/addon-preview-wrapper/package.json index 755110138670..6fbfe3e5515c 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.45", + "version": "5.3.0-rc.0", "description": "preview wrapper addon for storybook", "keywords": [ "addon", @@ -24,10 +24,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", "react": "^16.8.3" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/dev-kits/addon-roundtrip/package.json b/dev-kits/addon-roundtrip/package.json index 0b952dc102a3..a64a063bb83a 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.45", + "version": "5.3.0-rc.0", "description": "roundtrip addon for storybook", "keywords": [ "addon", @@ -24,13 +24,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/api": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -38,5 +38,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" } diff --git a/docs/package.json b/docs/package.json index 16e5a5ff030c..7502ac066599 100644 --- a/docs/package.json +++ b/docs/package.json @@ -38,9 +38,9 @@ "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", @@ -49,6 +49,6 @@ "sitemap": "^4.1.1", "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/tooltip/TooltipLinkList.js b/docs/src/new-components/basics/tooltip/TooltipLinkList.js index 7fd87163f0f2..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: ${11.5 * 32 /* 11.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/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..1118517e21ef 100644 --- a/docs/src/pages/addons/using-addons/index.md +++ b/docs/src/pages/addons/using-addons/index.md @@ -15,12 +15,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 +35,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 +73,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..88c4a7f62256 100644 --- a/docs/src/pages/addons/writing-addons/index.md +++ b/docs/src/pages/addons/writing-addons/index.md @@ -42,16 +42,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 +62,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 +95,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 +169,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 +229,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 +246,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 +280,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..69a66c41f18e 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 = { + webpack: 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/writing-stories/index.md b/docs/src/pages/basics/writing-stories/index.md index 8f14ae9bc01d..6edbbcc82dbb 100644 --- a/docs/src/pages/basics/writing-stories/index.md +++ b/docs/src/pages/basics/writing-stories/index.md @@ -93,17 +93,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: @@ -183,17 +192,24 @@ 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 @@ -224,11 +240,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 }); ``` diff --git a/docs/src/pages/configurations/custom-webpack-config/index.md b/docs/src/pages/configurations/custom-webpack-config/index.md index 3add2bbefc54..efc86322827d 100644 --- a/docs/src/pages/configurations/custom-webpack-config/index.md +++ b/docs/src/pages/configurations/custom-webpack-config/index.md @@ -3,7 +3,8 @@ 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**. +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 +22,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 +149,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> 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 2905291e8948..e60dea2d3dcd 100644 --- a/docs/src/pages/configurations/options-parameter/index.md +++ b/docs/src/pages/configurations/options-parameter/index.md @@ -5,72 +5,88 @@ 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', + + /** + * display the top-level grouping as a "root" in the sidebar + * @type {Boolean} + */ + showRoots: false, + + /** + * 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, +}); +``` + +### Sorting stories + +Import and use `addParameters` with the `options` key in your `preview.js` file. ```js import { addParameters, configure } 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', - /** - * display the top-level grouping as a "root" in the sidebar - * @type {Boolean} - */ - showRoots: null, - /** - * 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, - /** - * 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, + storySort: (a, b) => + a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), }, -}); +}; ``` For more information on configuring the `theme`, see [theming](../theming/). @@ -80,16 +96,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/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..c02a8698c8e6 100644 --- a/docs/src/pages/configurations/theming/index.md +++ b/docs/src/pages/configurations/theming/index.md @@ -9,7 +9,7 @@ Storybook is theme-able! 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`: diff --git a/docs/src/pages/configurations/typescript-config/index.md b/docs/src/pages/configurations/typescript-config/index.md index cb16b460ac8b..17e08d3bf8f3 100644 --- a/docs/src/pages/configurations/typescript-config/index.md +++ b/docs/src/pages/configurations/typescript-config/index.md @@ -7,7 +7,7 @@ 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 easiest way to write and configure your stories in TypeScript is by using [Storybook presets](../../presets/introduction). 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. @@ -32,24 +32,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 = { + webpack: 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 +101,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'], + webpack: 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 +128,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 +152,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 +188,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..ff63b3712ef8 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 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 0f3b8f946e21..20454b21081d 100644 --- a/docs/src/pages/guides/guide-ember/index.md +++ b/docs/src/pages/guides/guide-ember/index.md @@ -52,19 +52,16 @@ Then add the following NPM script to your `package.json` in order to start the s 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. 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/index.md b/docs/src/pages/guides/guide-react/index.md index e2f92d26cc3f..cf8d8185a661 100644 --- a/docs/src/pages/guides/guide-react/index.md +++ b/docs/src/pages/guides/guide-react/index.md @@ -59,16 +59,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..66d612774739 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. diff --git a/docs/src/pages/guides/guide-vue/index.md b/docs/src/pages/guides/guide-vue/index.md index 28631e33dbdc..a50d20ea87bd 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/writing-presets/index.md b/docs/src/pages/presets/writing-presets/index.md index 199d2291ba17..16422a4089b1 100644 --- a/docs/src/pages/presets/writing-presets/index.md +++ b/docs/src/pages/presets/writing-presets/index.md @@ -91,10 +91,12 @@ export function addons(entry = []) { } ``` -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 -import '@storybook/addon-storysource/register'; +module.exports = { + addons: ['@storybook/addon-storysource/register'] +} ``` ### Entries @@ -107,31 +109,60 @@ 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 exists 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: async (config, options) => { + // update config here + return config; + }, + managerBabel: async (config, options) => { + // update config here + return config; + }, + webpack: async (config, options) => { + return config; + }, + babel: async (config, options) => { + return config; + }, + addons: [], } -module.exports = { managerWebpack, managerBabel, webpack, babel, addons } ``` -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 = { + presets: [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; + }, + webpack: async (config, options) => { + return config; + }, + babel: async (config, options) => { + return config; + }, + addons: [], +} +``` + +Place your `my-preset.js` file where ever you want, if you want to share if far and wide you'll want to make it it's 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/next.json b/docs/src/versions/next.json index 7944d4f3ecbd..314e04604d70 100644 --- a/docs/src/versions/next.json +++ b/docs/src/versions/next.json @@ -1 +1 @@ -{"version":"5.3.0-alpha.45","info":{"plain":"### Breaking Changes\n\n* CSF: Deprecate displayName parameter ([#8775](https://github.com/storybookjs/storybook/pull/8775))\n\n### Features\n\n* Addon-docs: Rich prop tables ([#8826](https://github.com/storybookjs/storybook/pull/8826))\n* Core: Simplified hierarchy separators ([#8796](https://github.com/storybookjs/storybook/pull/8796))\n* CLI: Upgrade hierarchy separator codemod + examples ([#8818](https://github.com/storybookjs/storybook/pull/8818))\n* CLI: Addon postinstall hooks ([#8700](https://github.com/storybookjs/storybook/pull/8700))\n* CSF/MDX: Add component id for permalinks ([#8808](https://github.com/storybookjs/storybook/pull/8808))\n* Addon-knobs: Add object[] support for select ([#7957](https://github.com/storybookjs/storybook/pull/7957))\n\n### Bug Fixes\n\n* Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779))"}} \ No newline at end of file +{"version":"5.3.0-rc.0","info":{"plain":"### Features\n\n* CSF: Use `__orderedExports` in loader if provided ([#9181](https://github.com/storybookjs/storybook/pull/9181))\n\n### Bug Fixes\n\n* Addon-a11y: Fix selected blindness color filter ([#9179](https://github.com/storybookjs/storybook/pull/9179))\n\n### Maintenance\n\n* Addon-essentials: Remove actions, links, knobs ([#9184](https://github.com/storybookjs/storybook/pull/9184))"}} \ No newline at end of file diff --git a/docs/yarn.lock b/docs/yarn.lock index 995ca7f85a35..067a0ceaed77 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3292,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" @@ -9807,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" @@ -10040,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" @@ -10882,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" @@ -12681,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/package.json b/examples-native/crna-kitchen-sink/package.json index 4926e6c7a3b4..21dd1412106d 100644 --- a/examples-native/crna-kitchen-sink/package.json +++ b/examples-native/crna-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "crna-kitchen-sink", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "private": true, "main": "node_modules/expo/AppEntry.js", "workspaces": { @@ -24,27 +24,27 @@ "dependencies": { "expo": "^33.0.7", "prop-types": "^15.6.2", - "react": "16.10.1", + "react": "16.12.0", "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.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-ondevice-actions": "5.3.0-alpha.45", - "@storybook/addon-ondevice-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-ondevice-knobs": "5.3.0-alpha.45", - "@storybook/addon-ondevice-notes": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/react-native": "5.3.0-alpha.45", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-ondevice-actions": "5.3.0-rc.0", + "@storybook/addon-ondevice-backgrounds": "5.3.0-rc.0", + "@storybook/addon-ondevice-knobs": "5.3.0-rc.0", + "@storybook/addon-ondevice-notes": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/react-native": "5.3.0-rc.0", "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", + "expo-cli": "^3.7.1", "jest-expo": "^33.0.2", "react-test-renderer": "16.10.2", "schedule": "^0.5.0" 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..11163d45b187 --- /dev/null +++ b/examples/angular-cli/.storybook/main.js @@ -0,0 +1,15 @@ +module.exports = { + presets: ['@storybook/addon-docs/preset'], + stories: ['../src/stories/**/*.stories.(ts|mdx)'], + addons: [ + '@storybook/addon-storysource/register', + '@storybook/addon-actions/register', + '@storybook/addon-links/register', + '@storybook/addon-notes/register', + '@storybook/addon-knobs/register', + '@storybook/addon-options/register', + '@storybook/addon-jest/register', + '@storybook/addon-backgrounds/register', + '@storybook/addon-a11y/register', + ], +}; 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/config.ts b/examples/angular-cli/.storybook/preview.ts similarity index 69% rename from examples/angular-cli/.storybook/config.ts rename to examples/angular-cli/.storybook/preview.ts index 0316837b5ebc..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: { - showRoots: true, - }, 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 0b59ecc61212..f958cce474b6 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.45", + "version": "5.3.0-rc.0", "private": true, "license": "MIT", "scripts": { @@ -37,24 +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.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-docs": "5.3.0-alpha.45", - "@storybook/addon-jest": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/angular": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/addon-jest": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/angular": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", "@types/core-js": "^2.5.0", "@types/jest": "^24.0.11", - "@types/node": "^12.7.9", + "@types/node": "^12.12.11", "@types/webpack-env": "^1.14.0", "babel-plugin-require-context-hook": "^1.0.0", "global": "^4.3.2", @@ -63,7 +63,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__/core.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot index 7bde756e9ed5..ebb28412d380 100644 --- a/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot @@ -12,7 +12,7 @@ exports[`Storyshots Core/Parameters passed to story 1`] = ` <button _ngcontent-a-c20="" > - Parameters are {"options":{"showRoots":true},"docs":{"iframeHeight":"60px"},"globalParameter":"globalParameter","framework":"angular","chapterParameter":"chapterParameter","storyParameter":"storyParameter","__id":"core-parameters--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/addon-actions.stories.ts b/examples/angular-cli/src/stories/addon-actions.stories.ts index 40d245c49a21..7d9b6ad77774 100644 --- a/examples/angular-cli/src/stories/addon-actions.stories.ts +++ b/examples/angular-cli/src/stories/addon-actions.stories.ts @@ -5,7 +5,7 @@ export default { 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-docs.stories.mdx b/examples/angular-cli/src/stories/addon-docs.stories.mdx index d6e01af6864b..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 c0f821ddc4cb..b5ca229ef1b7 100644 --- a/examples/angular-cli/src/stories/addon-jest.stories.ts +++ b/examples/angular-cli/src/stories/addon-jest.stories.ts @@ -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 fc4436e6eb84..36501f9165be 100644 --- a/examples/angular-cli/src/stories/addon-knobs.stories.ts +++ b/examples/angular-cli/src/stories/addon-knobs.stories.ts @@ -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 785828427ebd..065d4df98410 100644 --- a/examples/angular-cli/src/stories/addon-links.stories.ts +++ b/examples/angular-cli/src/stories/addon-links.stories.ts @@ -5,7 +5,7 @@ export default { 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 index e4a3b3b5923c..87f7bb7d69c9 100644 --- a/examples/angular-cli/src/stories/addon-notes.stories.ts +++ b/examples/angular-cli/src/stories/addon-notes.stories.ts @@ -4,7 +4,7 @@ export default { title: 'Addon/Notes', }; -export const simpleNote = () => ({ +export const SimpleNote = () => ({ component: Button, props: { text: 'Notes on some Button', @@ -12,12 +12,12 @@ export const simpleNote = () => ({ }, }); -simpleNote.story = { +SimpleNote.story = { name: 'Simple note', parameters: { notes: 'My notes on some button' }, }; -export const noteWithHtml = () => ({ +export const NoteWithHtml = () => ({ component: Button, props: { text: 'Notes with HTML', @@ -25,7 +25,7 @@ export const noteWithHtml = () => ({ }, }); -noteWithHtml.story = { +NoteWithHtml.story = { name: 'Note with HTML', parameters: { notes: ` 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/di.component.stories.ts b/examples/angular-cli/src/stories/component-with-di/di.component.stories.ts index 92033eada970..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 @@ -5,25 +5,25 @@ export default { 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/styled.component.stories.ts b/examples/angular-cli/src/stories/component-with-style/styled.component.stories.ts index e94b3b4f21de..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 @@ -4,10 +4,10 @@ export default { 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 0e8908b13baf..91898719d28f 100644 --- a/examples/angular-cli/src/stories/core.stories.ts +++ b/examples/angular-cli/src/stories/core.stories.ts @@ -14,7 +14,7 @@ export default { }, }; -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-pipes.stories.ts b/examples/angular-cli/src/stories/custom-pipes.stories.ts index 48edeb238d0f..f0c9d379fcea 100644 --- a/examples/angular-cli/src/stories/custom-pipes.stories.ts +++ b/examples/angular-cli/src/stories/custom-pipes.stories.ts @@ -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 fd5a3acf12b1..86b2c2093798 100644 --- a/examples/angular-cli/src/stories/custom-providers.stories.ts +++ b/examples/angular-cli/src/stories/custom-providers.stories.ts @@ -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 c54b858ad674..207e818174ca 100644 --- a/examples/angular-cli/src/stories/custom-styles.stories.ts +++ b/examples/angular-cli/src/stories/custom-styles.stories.ts @@ -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/custom-cva-component.stories.ts b/examples/angular-cli/src/stories/customControlValueAccessor/custom-cva-component.stories.ts index a620d636cea9..bb93d416ef2f 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 @@ -9,7 +9,7 @@ export default { title: 'Custom/ngModel', }; -export const customControlValueAccessor = () => ({ +export const CustomControlValueAccessor = () => ({ component: CustomCvaComponent, props: { ngModel: 'Type anything', @@ -17,7 +17,7 @@ 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/inheritance.stories.ts b/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts index 0ca1dfcd367a..37060f32ca92 100644 --- a/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts +++ b/examples/angular-cli/src/stories/inheritance/inheritance.stories.ts @@ -5,7 +5,7 @@ export default { 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 4233e46a133c..a1af39192691 100644 --- a/examples/angular-cli/src/stories/metadata-combined.stories.ts +++ b/examples/angular-cli/src/stories/metadata-combined.stories.ts @@ -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 35392945881d..d70c0306f690 100644 --- a/examples/angular-cli/src/stories/metadata-individual.stories.ts +++ b/examples/angular-cli/src/stories/metadata-individual.stories.ts @@ -4,7 +4,7 @@ export default { 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 c09bfabb53eb..9748a174f306 100644 --- a/examples/angular-cli/src/stories/metadata-shared.stories.ts +++ b/examples/angular-cli/src/stories/metadata-shared.stories.ts @@ -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/on-push/on-push.stories.ts b/examples/angular-cli/src/stories/on-push/on-push.stories.ts index 5077336acf91..0246c5e87a36 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 @@ -6,7 +6,7 @@ export default { decorators: [withKnobs], }; -export const classSpecifiedComponentWithOnPushAndKnobs = () => ({ +export const ClassSpecifiedComponentWithOnPushAndKnobs = () => ({ component: OnPushBoxComponent, props: { word: text('Word', 'OnPush'), @@ -14,7 +14,7 @@ export const classSpecifiedComponentWithOnPushAndKnobs = () => ({ }, }); -classSpecifiedComponentWithOnPushAndKnobs.story = { +ClassSpecifiedComponentWithOnPushAndKnobs.story = { name: 'Class-specified component with OnPush and Knobs', parameters: { notes: ` 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..12b5d46d3964 --- /dev/null +++ b/examples/cra-kitchen-sink/.storybook/main.js @@ -0,0 +1,22 @@ +module.exports = { + presets: [ + { + name: '@storybook/addon-docs/preset', + options: { + configureJSX: true, + }, + }, + ], + addons: [ + '@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-backgrounds/register', + '@storybook/addon-a11y/register', + '@storybook/addon-jest/register', + ], + 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 91812f68f02e..3d7fb4a718a7 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.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build": "react-scripts build", @@ -18,23 +18,23 @@ "react-lifecycles-compat": "^3.0.4" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-docs": "5.3.0-alpha.45", - "@storybook/addon-events": "5.3.0-alpha.45", - "@storybook/addon-info": "5.3.0-alpha.45", - "@storybook/addon-jest": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-logger": "5.3.0-alpha.45", - "@storybook/react": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/addon-events": "5.3.0-rc.0", + "@storybook/addon-info": "5.3.0-rc.0", + "@storybook/addon-jest": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-logger": "5.3.0-rc.0", + "@storybook/react": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "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..4a6563d040f4 100644 --- a/examples/cra-kitchen-sink/src/stories/button.stories.js +++ b/examples/cra-kitchen-sink/src/stories/button.stories.js @@ -28,30 +28,30 @@ export default { 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 = { +export const Story3 = () => <Button>Check my notes in the notes panel</Button>; +Story3.story = { name: 'with notes', parameters: { notes: 'A very simple button', @@ -59,14 +59,14 @@ story3.story = { }, }; -export const story4 = context => ( +export const Story4 = context => ( <Container> <span> click the <InfoButton /> label in top right for info about "{context.name}" </span> </Container> ); -story4.story = { +Story4.story = { name: 'with new info', parameters: { notes: 'Composition: Info(Notes())', 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/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 e3988b952e57..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, /button\.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..a1c77ad67997 --- /dev/null +++ b/examples/cra-react15/.storybook/main.js @@ -0,0 +1,4 @@ +module.exports = { + stories: ['../src/stories/welcome.stories', '../src/stories/**/button.stories.js'], + addons: ['@storybook/addon-actions/register', '@storybook/addon-links/register'], +}; 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 b3cdffbb41ea..d6752374a8df 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.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build": "react-scripts build", @@ -18,10 +18,11 @@ "react-scripts": "3.0.1" }, "devDependencies": { - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/react": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/react": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "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..1fe1cb3ce2d8 --- /dev/null +++ b/examples/cra-ts-essentials/.storybook/main.js @@ -0,0 +1,12 @@ +module.exports = { + stories: ['../src/**/*.stories.tsx'], + presets: [ + '@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..c2fa27c44ce0 --- /dev/null +++ b/examples/cra-ts-essentials/package.json @@ -0,0 +1,44 @@ +{ + "name": "cra-ts-essentials", + "version": "5.3.0-rc.0", + "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": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/preset-create-react-app": "^1.5.0", + "@storybook/react": "5.3.0-rc.0" + } +} 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/presets.js b/examples/cra-ts-kitchen-sink/.storybook/presets.js index 0fa1bcda8045..5fc756b2eb3a 100644 --- a/examples/cra-ts-kitchen-sink/.storybook/presets.js +++ b/examples/cra-ts-kitchen-sink/.storybook/presets.js @@ -8,6 +8,7 @@ module.exports = [ 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/'); } @@ -17,5 +18,5 @@ module.exports = [ }, }, }, - '@storybook/addon-docs/react/preset', + '@storybook/addon-docs/preset', ]; diff --git a/examples/cra-ts-kitchen-sink/package.json b/examples/cra-ts-kitchen-sink/package.json index bc613b8b438e..48cf81e4efbd 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.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build": "react-scripts build", @@ -23,26 +23,26 @@ ] }, "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.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-info": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/preset-create-react-app": "^1.2.0", - "@storybook/react": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-info": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/preset-create-react-app": "^1.5.0", + "@storybook/react": "5.3.0-rc.0", "@types/enzyme": "^3.9.0", "@types/react": "^16.8.14", "@types/react-dom": "^16.8.2", @@ -51,6 +51,7 @@ "enzyme-to-json": "^3.4.1", "fork-ts-checker-webpack-plugin": "^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 f7da6411c637..d058304c95f1 100644 --- a/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx +++ b/examples/cra-ts-kitchen-sink/src/components/Button.stories.tsx @@ -10,7 +10,7 @@ export default { 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 = () => ( + +
+
+
+ Preview counter: {previewState} +
+ + +
+ + ); +}; + +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 e3ca3a2f8dcc..c2432007b911 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.45", + "version": "5.3.0-rc.0", "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.45", - "@storybook/addon-parameter": "5.3.0-alpha.45", - "@storybook/addon-preview-wrapper": "5.3.0-alpha.45", - "@storybook/addon-roundtrip": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/node-logger": "5.3.0-alpha.45", - "@storybook/react": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addon-decorator": "5.3.0-rc.0", + "@storybook/addon-parameter": "5.3.0-rc.0", + "@storybook/addon-preview-wrapper": "5.3.0-rc.0", + "@storybook/addon-roundtrip": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/api": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/node-logger": "5.3.0-rc.0", + "@storybook/react": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "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/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 2e126f9c84fd..000000000000 --- a/examples/ember-cli/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/ember'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - showRoots: true, - }, -}); - -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..c8d73dff464a --- /dev/null +++ b/examples/ember-cli/.storybook/main.js @@ -0,0 +1,26 @@ +const path = require('path'); + +module.exports = { + addons: [ + '@storybook/addon-a11y/register', + '@storybook/addon-storysource/register', + '@storybook/addon-actions/register', + '@storybook/addon-docs/register', + '@storybook/addon-links/register', + '@storybook/addon-notes/register', + '@storybook/addon-knobs/register', + '@storybook/addon-viewport/register', + '@storybook/addon-options/register', + '@storybook/addon-backgrounds/register', + ], + stories: ['../stories/**/*.stories.js'], + webpack: 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/angular-cli/.storybook/presets.js b/examples/ember-cli/.storybook/preset.js similarity index 100% rename from examples/angular-cli/.storybook/presets.js rename to examples/ember-cli/.storybook/preset.js diff --git a/examples/ember-cli/.storybook/preview.js b/examples/ember-cli/.storybook/preview.js new file mode 100644 index 000000000000..d8969edd461b --- /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 '../dist/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/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 a9b449b816bb..3188a31bd49d 100644 --- a/examples/ember-cli/package.json +++ b/examples/ember-cli/package.json @@ -1,6 +1,6 @@ { "name": "ember-example", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build": "ember build", @@ -15,24 +15,26 @@ }, "devDependencies": { "@babel/core": "^7.3.4", - "@storybook/addon-a11y": "5.3.0-alpha.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addon-viewport": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/ember": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/ember": "5.3.0-rc.0", + "@storybook/ember-cli-storybook": "^0.2.0", + "@storybook/source-loader": "5.3.0-rc.0", "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-ajax": "^5.0.0", + "ember-cli": "~3.14.0", "ember-cli-app-version": "^3.0.0", "ember-cli-babel": "^7.6.0", "ember-cli-htmlbars": "^3.0.1", @@ -40,11 +42,10 @@ "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-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 a8bcb6529205..4958bd0157f0 100644 --- a/examples/ember-cli/stories/addon-a11y.stories.js +++ b/examples/ember-cli/stories/addon-a11y.stories.js @@ -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 aab9fd86f064..60b66a0377dc 100644 --- a/examples/ember-cli/stories/addon-actions.stories.js +++ b/examples/ember-cli/stories/addon-actions.stories.js @@ -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 0f8ab7df34b3..7f17ebeea6c5 100644 --- a/examples/ember-cli/stories/addon-backgrounds.stories.js +++ b/examples/ember-cli/stories/addon-backgrounds.stories.js @@ -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 797e882feda2..2382a0b83b87 100644 --- a/examples/ember-cli/stories/addon-centered.stories.js +++ b/examples/ember-cli/stories/addon-centered.stories.js @@ -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 7cc3806665d6..a379a495814b 100644 --- a/examples/ember-cli/stories/addon-knobs.stories.js +++ b/examples/ember-cli/stories/addon-knobs.stories.js @@ -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 8aba44e70029..539a821fe43b 100644 --- a/examples/ember-cli/stories/addon-links.stories.js +++ b/examples/ember-cli/stories/addon-links.stories.js @@ -5,13 +5,13 @@ export default { 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 index 25ebe7cf591b..a2b963b58dac 100644 --- a/examples/ember-cli/stories/addon-notes.stories.js +++ b/examples/ember-cli/stories/addon-notes.stories.js @@ -4,20 +4,20 @@ export default { title: 'Addon/Notes', }; -export const simpleNote = () => ({ +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 = { +SimpleNote.story = { name: 'Simple note', parameters: { notes: 'My notes on some bold text' }, }; -export const noteWithHtml = () => ({ +export const NoteWithHtml = () => ({ template: hbs`

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

`, }); -noteWithHtml.story = { +NoteWithHtml.story = { name: 'Note with HTML', parameters: { diff --git a/examples/ember-cli/stories/index.stories.js b/examples/ember-cli/stories/index.stories.js index 4c194a8a2dde..8b3f32fce523 100644 --- a/examples/ember-cli/stories/index.stories.js +++ b/examples/ember-cli/stories/index.stories.js @@ -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..248ea7d96eea 100644 --- a/examples/ember-cli/stories/welcome-banner.stories.js +++ b/examples/ember-cli/stories/welcome-banner.stories.js @@ -3,9 +3,10 @@ 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..4982b94cf401 --- /dev/null +++ b/examples/html-kitchen-sink/.storybook/main.js @@ -0,0 +1,18 @@ +module.exports = { + // this dirname is because we run tests from project root + stories: [`${__dirname}/../stories/*.stories.*`], + presets: ['@storybook/addon-docs/preset'], + addons: [ + '@storybook/addon-a11y/register', + '@storybook/addon-actions/register', + '@storybook/addon-backgrounds/register', + '@storybook/addon-events/register', + '@storybook/addon-jest/register', + '@storybook/addon-knobs/register', + '@storybook/addon-links/register', + '@storybook/addon-notes/register', + '@storybook/addon-options/register', + '@storybook/addon-storysource/register', + '@storybook/addon-viewport/register', + ], +}; diff --git a/examples/html-kitchen-sink/.storybook/presets.js b/examples/html-kitchen-sink/.storybook/presets.js deleted file mode 100644 index a04174c7a331..000000000000 --- a/examples/html-kitchen-sink/.storybook/presets.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['@storybook/addon-docs/preset']; diff --git a/examples/html-kitchen-sink/.storybook/config.js b/examples/html-kitchen-sink/.storybook/preview.js similarity index 67% rename from examples/html-kitchen-sink/.storybook/config.js rename to examples/html-kitchen-sink/.storybook/preview.js index 5df4018a47fe..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); @@ -18,5 +18,3 @@ addParameters({ 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 d2c032f09ed9..f8e7cca0bc47 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.45", + "version": "5.3.0-rc.0", "private": true, "description": "", "keywords": [], @@ -13,26 +13,26 @@ "storybook": "start-storybook -p 9006" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-docs": "5.3.0-alpha.45", - "@storybook/addon-events": "5.3.0-alpha.45", - "@storybook/addon-jest": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addon-viewport": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/client-api": "5.3.0-alpha.45", - "@storybook/core": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/html": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/addon-events": "5.3.0-rc.0", + "@storybook/addon-jest": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/client-api": "5.3.0-rc.0", + "@storybook/core": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/html": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", "eventemitter3": "^4.0.0", "format-json": "^1.0.3", "global": "^4.3.2", diff --git a/examples/html-kitchen-sink/stories/addon-a11y.stories.js b/examples/html-kitchen-sink/stories/addon-a11y.stories.js index 89942adb7cf2..a811c0cee73f 100644 --- a/examples/html-kitchen-sink/stories/addon-a11y.stories.js +++ b/examples/html-kitchen-sink/stories/addon-a11y.stories.js @@ -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 bc21446b6027..40ec7cb6eba0 100644 --- a/examples/html-kitchen-sink/stories/addon-actions.stories.js +++ b/examples/html-kitchen-sink/stories/addon-actions.stories.js @@ -8,19 +8,19 @@ export default { 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 6a2d799ac4c3..fdfdb6b9954b 100644 --- a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js +++ b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js @@ -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 858709b9a324..611afd934335 100644 --- a/examples/html-kitchen-sink/stories/addon-centered.stories.js +++ b/examples/html-kitchen-sink/stories/addon-centered.stories.js @@ -5,5 +5,5 @@ export default { 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-jest.stories.js b/examples/html-kitchen-sink/stories/addon-jest.stories.js index f2dee19b5239..eb80009be86b 100644 --- a/examples/html-kitchen-sink/stories/addon-jest.stories.js +++ b/examples/html-kitchen-sink/stories/addon-jest.stories.js @@ -6,5 +6,5 @@ export default { 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 e97a4608cdcf..7b529a6c4101 100644 --- a/examples/html-kitchen-sink/stories/addon-knobs.stories.js +++ b/examples/html-kitchen-sink/stories/addon-knobs.stories.js @@ -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 index 795a21fe7a92..9129457c82d5 100644 --- a/examples/html-kitchen-sink/stories/addon-notes.stories.js +++ b/examples/html-kitchen-sink/stories/addon-notes.stories.js @@ -2,13 +2,13 @@ export default { title: 'Addons/Notes', }; -export const story1 = () => +export const Story1 = () => `

This is a fragment of HTML

`; -story1.story = { +Story1.story = { name: 'Simple note', parameters: { notes: 'My notes on some bold text', 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/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 7957ba4f980c..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: { - showRoots: true, - }, -}); - -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..9d439b634082 --- /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/register', + '@storybook/addon-actions/register', + '@storybook/addon-knobs/register', + '@storybook/addon-options/register', + '@storybook/addon-a11y/register', + ], + webpack: 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 007852c09b46..09101cbdb717 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.45", + "version": "5.3.0-rc.0", "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.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/marko": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/marko": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", "prettier": "^1.16.4", "webpack": "^4.33.0" } diff --git a/examples/marko-cli/src/stories/hello.stories.js b/examples/marko-cli/src/stories/hello.stories.js index 4c9e74866c69..a6a717560ad8 100644 --- a/examples/marko-cli/src/stories/hello.stories.js +++ b/examples/marko-cli/src/stories/hello.stories.js @@ -8,5 +8,5 @@ export default { }; 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/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 3db6d5be468c..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: { - showRoots: true, - }, -}); - -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..0ffc858a046b --- /dev/null +++ b/examples/mithril-kitchen-sink/.storybook/main.js @@ -0,0 +1,25 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/stories/**/*.stories.js'], + addons: [ + '@storybook/addon-storysource/register', + '@storybook/addon-actions/register', + '@storybook/addon-links/register', + '@storybook/addon-notes/register', + '@storybook/addon-knobs/register', + '@storybook/addon-viewport/register', + '@storybook/addon-options/register', + '@storybook/addon-backgrounds/register', + '@storybook/addon-a11y/register', + ], + webpack: 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 6bf9972686f4..a0ffc7c4b9c3 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.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build-storybook": "build-storybook", @@ -10,20 +10,20 @@ "mithril": "^1.1.6" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addon-viewport": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/mithril": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/mithril": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", "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 7423669b09aa..48c158691950 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js @@ -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-knobs.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js index 6d8b76d6ace1..4cf79669f6a0 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js @@ -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 72589574ae1a..ea20da6fa529 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-links.stories.js @@ -9,7 +9,7 @@ export default { 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 index 6801d66fcecd..26a9b8a401db 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-notes.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-notes.stories.js @@ -6,7 +6,7 @@ export default { title: 'Addons/Notes', }; -export const story1 = () => ({ +export const Story1 = () => ({ view: () => (

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

🤔😳😯😮 @@ -38,7 +38,7 @@ export const story2 = () => ({ ), }); -story2.story = { +Story2.story = { name: 'Note with HTML', parameters: { notes: ` 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..01953ea10d96 --- /dev/null +++ b/examples/official-storybook/components/ButtonGroup.js @@ -0,0 +1,17 @@ +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: PropTypes.string, + children: PropTypes.arrayOf(PropTypes.element), +}; 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/main.js b/examples/official-storybook/main.js index 84d0925a0505..74dadf2bd338 100644 --- a/examples/official-storybook/main.js +++ b/examples/official-storybook/main.js @@ -1,7 +1,11 @@ module.exports = { presets: ['@storybook/addon-docs/preset'], - addons: existing => [ - ...existing, + stories: [ + '../../lib/ui/src/**/*.stories.(js|tsx|mdx)', + '../../lib/components/src/**/*.stories.(js|tsx|mdx)', + './stories/**/*.stories.(js|tsx|mdx)', + ], + addons: [ '@storybook/addon-storysource/register', '@storybook/addon-design-assets/register', '@storybook/addon-actions/register', diff --git a/examples/official-storybook/package.json b/examples/official-storybook/package.json index 3a3dda7836ae..375f36827215 100644 --- a/examples/official-storybook/package.json +++ b/examples/official-storybook/package.json @@ -1,46 +1,47 @@ { "name": "official-storybook", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "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.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-contexts": "5.3.0-alpha.45", - "@storybook/addon-cssresources": "5.3.0-alpha.45", - "@storybook/addon-design-assets": "5.3.0-alpha.45", - "@storybook/addon-docs": "5.3.0-alpha.45", - "@storybook/addon-events": "5.3.0-alpha.45", - "@storybook/addon-graphql": "5.3.0-alpha.45", - "@storybook/addon-info": "5.3.0-alpha.45", - "@storybook/addon-jest": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-queryparams": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addon-storyshots-puppeteer": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addon-viewport": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/components": "5.3.0-alpha.45", - "@storybook/core-events": "5.3.0-alpha.45", - "@storybook/node-logger": "5.3.0-alpha.45", - "@storybook/react": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", - "@storybook/theming": "5.3.0-alpha.45", + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-contexts": "5.3.0-rc.0", + "@storybook/addon-cssresources": "5.3.0-rc.0", + "@storybook/addon-design-assets": "5.3.0-rc.0", + "@storybook/addon-docs": "5.3.0-rc.0", + "@storybook/addon-events": "5.3.0-rc.0", + "@storybook/addon-graphql": "5.3.0-rc.0", + "@storybook/addon-info": "5.3.0-rc.0", + "@storybook/addon-jest": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-queryparams": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addon-storyshots-puppeteer": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/components": "5.3.0-rc.0", + "@storybook/core-events": "5.3.0-rc.0", + "@storybook/node-logger": "5.3.0-rc.0", + "@storybook/react": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", + "@storybook/theming": "5.3.0-rc.0", "cors": "^2.8.5", "cross-env": "^6.0.3", "enzyme-to-json": "^3.4.1", @@ -54,6 +55,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 44ec0f039ade..c8ef59f5683d 100644 --- a/examples/official-storybook/preview.js +++ b/examples/official-storybook/preview.js @@ -1,5 +1,5 @@ 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'; @@ -48,7 +48,7 @@ addParameters({ 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 }, @@ -56,21 +56,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 cfdac38ea8be..64f1aded15ca 100644 --- a/examples/official-storybook/stories/addon-a11y/base-button.stories.js +++ b/examples/official-storybook/stories/addon-a11y/base-button.stories.js @@ -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 6f030c88bec3..a59bd7a48603 100644 --- a/examples/official-storybook/stories/addon-a11y/button.stories.js +++ b/examples/official-storybook/stories/addon-a11y/button.stories.js @@ -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 b0f928195c99..6d858132d683 100644 --- a/examples/official-storybook/stories/addon-design-assets.stories.js +++ b/examples/official-storybook/stories/addon-design-assets.stories.js @@ -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..1f614050bb0a --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/addon-docs-blocks.stories.js @@ -0,0 +1,176 @@ +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 } from '../../components/ButtonGroup'; + +export default { + title: 'Addons/Docs/stories docs bocks', + 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: { + '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 d4ecae2a59f6..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 fa1874207391..970bdea292a1 100644 --- a/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/addon-docs.stories.mdx @@ -88,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/docs-only.stories.mdx b/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx index d748141b63f8..aef2c1db786e 100644 --- a/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/docs-only.stories.mdx @@ -7,3 +7,10 @@ import { Meta } from '@storybook/addon-docs/blocks'; 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/markdown.stories.mdx b/examples/official-storybook/stories/addon-docs/markdown.stories.mdx index a711700e13ce..077715496817 100644 --- a/examples/official-storybook/stories/addon-docs/markdown.stories.mdx +++ b/examples/official-storybook/stories/addon-docs/markdown.stories.mdx @@ -133,6 +133,10 @@ Right aligned columns [link with title](https://hichroma.com 'Insert title!') +[link to in page anchor](#h1-heading) + +[link to another story](/?path=/docs/addons-docs-docs-only--page#bottom) + ## Images ![Minion](https://octodex.github.com/images/minion.png) diff --git a/examples/official-storybook/stories/addon-docs/mdx.stories.js b/examples/official-storybook/stories/addon-docs/mdx.stories.js index c4cab584e45a..e2563d4825fe 100644 --- a/examples/official-storybook/stories/addon-docs/mdx.stories.js +++ b/examples/official-storybook/stories/addon-docs/mdx.stories.js @@ -8,7 +8,7 @@ export default { }; // 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..65c4a01b198a --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/meta-string-template.stories.mdx @@ -0,0 +1,7 @@ +import { Meta, Story } from '@storybook/addon-docs/blocks'; +import { titleFunction } from '@storybook/addon-docs/dist/mdx/title-generators'; + + + +# Meta title from a string template + 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/subcomponents.stories.js b/examples/official-storybook/stories/addon-docs/subcomponents.stories.js new file mode 100644 index 000000000000..dedfa5534759 --- /dev/null +++ b/examples/official-storybook/stories/addon-docs/subcomponents.stories.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { DocgenButton } from '../../components/DocgenButton'; +import { ButtonGroup } from '../../components/ButtonGroup'; + +export default { + title: 'Addons/Docs/ButtonGroup', + component: ButtonGroup, + subcomponents: { DocgenButton }, +}; + +export const basic = () => ( + + + + +); diff --git a/examples/official-storybook/stories/addon-graphql.stories.js b/examples/official-storybook/stories/addon-graphql.stories.js index f1e8044e0d93..ceab150d2f7e 100644 --- a/examples/official-storybook/stories/addon-graphql.stories.js +++ b/examples/official-storybook/stories/addon-graphql.stories.js @@ -4,9 +4,9 @@ export default { title: 'Addons/GraphQL', }; -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/decorators.stories.js b/examples/official-storybook/stories/addon-info/decorators.stories.js index 0d3774ffdcc0..780fb3a0b6db 100644 --- a/examples/official-storybook/stories/addon-info/decorators.stories.js +++ b/examples/official-storybook/stories/addon-info/decorators.stories.js @@ -7,5 +7,5 @@ export default { 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' }; +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 index 21fdcd5ac6d6..1aa8da24c022 100644 --- a/examples/official-storybook/stories/addon-info/forward-ref.stories.js +++ b/examples/official-storybook/stories/addon-info/forward-ref.stories.js @@ -8,10 +8,10 @@ export default { decorators: [withInfo], }; -export const displaysCorrectly = () => ; -displaysCorrectly.story = { name: 'Displays forwarded ref components correctly' }; +export const DisplaysCorrectly = () => ; +DisplaysCorrectly.story = { name: 'Displays forwarded ref components correctly' }; -export const displayName = () => ( +export const DisplayName = () => ( ); -displayName.story = { name: 'Uses forwardRef displayName if available' }; +DisplayName.story = { name: 'Uses forwardRef displayName if available' }; diff --git a/examples/official-storybook/stories/addon-info/jsx.stories.js b/examples/official-storybook/stories/addon-info/jsx.stories.js index 5052bf5f241c..7db0a4ee09b5 100644 --- a/examples/official-storybook/stories/addon-info/jsx.stories.js +++ b/examples/official-storybook/stories/addon-info/jsx.stories.js @@ -25,11 +25,11 @@ export default { decorators: [withInfo], }; -export const displaysJsxInDescription = () => ( +export const DisplaysJsxInDescription = () => ( ); -displaysJsxInDescription.story = { +DisplaysJsxInDescription.story = { name: 'Displays JSX in description', parameters: { diff --git a/examples/official-storybook/stories/addon-info/markdown.stories.js b/examples/official-storybook/stories/addon-info/markdown.stories.js index 208b5c84ffee..38038e50703f 100644 --- a/examples/official-storybook/stories/addon-info/markdown.stories.js +++ b/examples/official-storybook/stories/addon-info/markdown.stories.js @@ -47,20 +47,20 @@ html with special formatting Maybe include a [link](http://storybook.js.org) to your project as well. `; -export const displaysMarkdownInDescription = () => ( +export const DisplaysMarkdownInDescription = () => ( ); -displaysMarkdownInDescription.story = { +DisplaysMarkdownInDescription.story = { name: 'Displays Markdown in description', parameters: { info: markdownDescription }, }; -export const fromInternalMarkdownFile = () => ( +export const FromInternalMarkdownFile = () => ( ); -fromInternalMarkdownFile.story = { +FromInternalMarkdownFile.story = { name: 'From internal Markdown file', parameters: { @@ -72,11 +72,11 @@ fromInternalMarkdownFile.story = { }, }; -export const fromExternalMarkdownFile = () => ( +export const FromExternalMarkdownFile = () => ( ); -fromExternalMarkdownFile.story = { +FromExternalMarkdownFile.story = { name: 'From external Markdown file', parameters: { info: externalMdDocs }, }; diff --git a/examples/official-storybook/stories/addon-info/options.stories.js b/examples/official-storybook/stories/addon-info/options.stories.js index 632518c932f3..f75e3b825607 100644 --- a/examples/official-storybook/stories/addon-info/options.stories.js +++ b/examples/official-storybook/stories/addon-info/options.stories.js @@ -12,8 +12,8 @@ export default { decorators: [withInfo], }; -export const inlinesComponentInsideStory = () => ; -inlinesComponentInsideStory.story = { +export const InlinesComponentInsideStory = () => ; +InlinesComponentInsideStory.story = { name: 'Inlines component inside story', parameters: { info: { @@ -23,10 +23,10 @@ inlinesComponentInsideStory.story = { }, }; -export const excludesPropTypesThatAreInTheExcludedPropTypesArray = () => ( +export const ExcludesPropTypesThatAreInTheExcludedPropTypesArray = () => ( ); -excludesPropTypesThatAreInTheExcludedPropTypesArray.story = { +ExcludesPropTypesThatAreInTheExcludedPropTypesArray.story = { name: 'Excludes propTypes that are in the excludedPropTypes array', parameters: { info: { @@ -36,8 +36,8 @@ excludesPropTypesThatAreInTheExcludedPropTypesArray.story = { }, }; -export const showsOrHidesInfoAddonHeader = () => ; -showsOrHidesInfoAddonHeader.story = { +export const ShowsOrHidesInfoAddonHeader = () => ; +ShowsOrHidesInfoAddonHeader.story = { name: 'Shows or hides Info Addon header', parameters: { info: { @@ -47,8 +47,8 @@ showsOrHidesInfoAddonHeader.story = { }, }; -export const showsOrHidesInfoAddonSource = () => ; -showsOrHidesInfoAddonSource.story = { +export const ShowsOrHidesInfoAddonSource = () => ; +ShowsOrHidesInfoAddonSource.story = { name: 'Shows or hides Info Addon source', parameters: { info: { @@ -58,8 +58,8 @@ showsOrHidesInfoAddonSource.story = { }, }; -export const showsAdditionalComponentPropTables = () => ; -showsAdditionalComponentPropTables.story = { +export const ShowsAdditionalComponentPropTables = () => ; +ShowsAdditionalComponentPropTables.story = { name: 'Shows additional component prop tables', parameters: { info: { @@ -69,13 +69,13 @@ showsAdditionalComponentPropTables.story = { }, }; -export const excludeComponentFromPropTables = () => ( +export const ExcludeComponentFromPropTables = () => (
); -excludeComponentFromPropTables.story = { +ExcludeComponentFromPropTables.story = { name: 'Exclude component from prop tables', parameters: { info: { @@ -85,8 +85,8 @@ excludeComponentFromPropTables.story = { }, }; -export const extendInfoStylesWithAnObject = () => ; -extendInfoStylesWithAnObject.story = { +export const ExtendInfoStylesWithAnObject = () => ; +ExtendInfoStylesWithAnObject.story = { name: 'Extend info styles with an object', parameters: { info: { @@ -106,8 +106,8 @@ extendInfoStylesWithAnObject.story = { }, }; -export const fullControlOverStylesUsingAFunction = () => ; -fullControlOverStylesUsingAFunction.story = { +export const FullControlOverStylesUsingAFunction = () => ; +FullControlOverStylesUsingAFunction.story = { name: 'Full control over styles using a function', parameters: { info: { @@ -125,8 +125,8 @@ fullControlOverStylesUsingAFunction.story = { }, }; -export const useACustomComponentForTheTable = () => ; -useACustomComponentForTheTable.story = { +export const UseACustomComponentForTheTable = () => ; +UseACustomComponentForTheTable.story = { name: 'Use a custom component for the table', component: TableComponent, parameters: { @@ -136,22 +136,22 @@ useACustomComponentForTheTable.story = { }, }; -export const useInfoAsStoryDecorator = () => ; +export const UseInfoAsStoryDecorator = () => ; -useInfoAsStoryDecorator.story = { +UseInfoAsStoryDecorator.story = { name: 'Use Info as story decorator', decorators: [withInfo('Info can take options via the global or local decorator as well.')], }; -export const usingParametersAcrossAllStories = () => ; -usingParametersAcrossAllStories.story = { +export const UsingParametersAcrossAllStories = () => ; +UsingParametersAcrossAllStories.story = { name: 'Using parameters across all stories', }; -export const overwritingAndExtendingTheParametersAndOptionsSetStoriesWise = () => ( +export const OverwritingAndExtendingTheParametersAndOptionsSetStoriesWise = () => ( ); -overwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { +OverwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { name: 'Overwriting and extending the parameters and options set stories-wise', parameters: { info: { @@ -161,20 +161,20 @@ overwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { }, }; -export const overwriteTheParametersWithMarkdownVariable = () => ( +export const OverwriteTheParametersWithMarkdownVariable = () => ( ); -overwriteTheParametersWithMarkdownVariable.story = { +OverwriteTheParametersWithMarkdownVariable.story = { name: 'Overwrite the parameters with markdown variable', parameters: { info: markdownDescription }, }; -export const overwriteTheTextParameterWithMarkdownInline = () => ( +export const OverwriteTheTextParameterWithMarkdownInline = () => ( ); -overwriteTheTextParameterWithMarkdownInline.story = { +OverwriteTheTextParameterWithMarkdownInline.story = { name: 'Overwrite the text parameter with markdown inline', parameters: { info: { @@ -189,10 +189,10 @@ overwriteTheTextParameterWithMarkdownInline.story = { }, }; -export const disableTheAddonEntirely = () => ( +export const DisableTheAddonEntirely = () => ( ); -disableTheAddonEntirely.story = { +DisableTheAddonEntirely.story = { name: 'Disable the addon entirely', parameters: { info: { disable: true } }, }; diff --git a/examples/official-storybook/stories/addon-info/parameters.stories.js b/examples/official-storybook/stories/addon-info/parameters.stories.js index 257c165f9937..9e36b966b5f7 100644 --- a/examples/official-storybook/stories/addon-info/parameters.stories.js +++ b/examples/official-storybook/stories/addon-info/parameters.stories.js @@ -27,15 +27,15 @@ export default { }, }; -export const usingParametersAcrossAllStories = () => ; -usingParametersAcrossAllStories.story = { +export const UsingParametersAcrossAllStories = () => ; +UsingParametersAcrossAllStories.story = { name: 'Using parameters across all stories', }; -export const overwritingAndExtendingTheParametersAndOptionsSetStoriesWise = () => ( +export const OverwritingAndExtendingTheParametersAndOptionsSetStoriesWise = () => ( ); -overwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { +OverwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { name: 'Overwriting and extending the parameters and options set stories-wise', parameters: { info: { @@ -45,18 +45,18 @@ overwritingAndExtendingTheParametersAndOptionsSetStoriesWise.story = { }, }; -export const overwriteTheParametersWithMarkdownVariable = () => ( +export const OverwriteTheParametersWithMarkdownVariable = () => ( ); -overwriteTheParametersWithMarkdownVariable.story = { +OverwriteTheParametersWithMarkdownVariable.story = { name: 'Overwrite the parameters with markdown variable', parameters: { info: markdownDescription }, }; -export const overwriteTheTextParameterWithMarkdownInline = () => ( +export const OverwriteTheTextParameterWithMarkdownInline = () => ( ); -overwriteTheTextParameterWithMarkdownInline.story = { +OverwriteTheTextParameterWithMarkdownInline.story = { name: 'Overwrite the text parameter with markdown inline', parameters: { info: { @@ -71,10 +71,10 @@ overwriteTheTextParameterWithMarkdownInline.story = { }, }; -export const disableTheAddonEntirely = () => ( +export const DisableTheAddonEntirely = () => ( ); -disableTheAddonEntirely.story = { +DisableTheAddonEntirely.story = { name: 'Disable the addon entirely', parameters: { info: { disable: true } }, }; diff --git a/examples/official-storybook/stories/addon-info/react-docgen.stories.js b/examples/official-storybook/stories/addon-info/react-docgen.stories.js index bbb6412abeba..deef915f0478 100644 --- a/examples/official-storybook/stories/addon-info/react-docgen.stories.js +++ b/examples/official-storybook/stories/addon-info/react-docgen.stories.js @@ -13,7 +13,7 @@ export default { decorators: [withInfo], }; -export const commentsFromPropTypeDeclarations = () => ( +export const CommentsFromPropTypeDeclarations = () => ( ( /> ); -commentsFromPropTypeDeclarations.story = { +CommentsFromPropTypeDeclarations.story = { name: 'Comments from PropType declarations', parameters: { @@ -42,11 +42,11 @@ commentsFromPropTypeDeclarations.story = { }, }; -export const commentsFromFlowDeclarations = () => ( +export const CommentsFromFlowDeclarations = () => ( ); -commentsFromFlowDeclarations.story = { +CommentsFromFlowDeclarations.story = { name: 'Comments from Flow declarations', parameters: { @@ -55,11 +55,11 @@ commentsFromFlowDeclarations.story = { }, }; -export const commentsFromComponentDeclaration = () => ( +export const CommentsFromComponentDeclaration = () => ( ); -commentsFromComponentDeclaration.story = { +CommentsFromComponentDeclaration.story = { name: 'Comments from component declaration', parameters: { @@ -68,11 +68,11 @@ commentsFromComponentDeclaration.story = { }, }; -export const commentsFromNamedExportComponentDeclaration = () => ( +export const CommentsFromNamedExportComponentDeclaration = () => ( ); -commentsFromNamedExportComponentDeclaration.story = { +CommentsFromNamedExportComponentDeclaration.story = { name: 'Comments from named export component declaration', parameters: { diff --git a/examples/official-storybook/stories/addon-info/story-source.stories.js b/examples/official-storybook/stories/addon-info/story-source.stories.js index fb2a42ece90e..b3ab034e9ecb 100644 --- a/examples/official-storybook/stories/addon-info/story-source.stories.js +++ b/examples/official-storybook/stories/addon-info/story-source.stories.js @@ -10,21 +10,21 @@ export default { decorators: [withInfo], }; -export const oneProp = () => ; -oneProp.story = { name: 'One prop' }; +export const OneProp = () => ; +OneProp.story = { name: 'One prop' }; -export const manyProps = () => ; -manyProps.story = { name: 'Many props' }; +export const ManyProps = () => ; +ManyProps.story = { name: 'Many props' }; -export const children = () => ( +export const Children = () => (

Here is my nice button:

); -children.story = { name: 'Children' }; +Children.story = { name: 'Children' }; -export const arrayProp = () => { +export const ArrayProp = () => { const propDefs = [ { property: 'label', @@ -55,9 +55,9 @@ export const arrayProp = () => { ]; return ; }; -arrayProp.story = { name: 'Array prop' }; +ArrayProp.story = { name: 'Array prop' }; -export const objectProp = () => ( +export const ObjectProp = () => ( ( backgroundColor: 'powderblue', fontSize: '16px', boxShadow: '1px 1px rgba(0, 0, 0, .07)', - borderRadius: '5px', + borderRadius: 5, padding: '4px 8px', }} /> ); -objectProp.story = { name: 'Object prop' }; +ObjectProp.story = { name: 'Object prop' }; diff --git a/examples/official-storybook/stories/addon-jest.stories.js b/examples/official-storybook/stories/addon-jest.stories.js index b0226b4cd8f2..e488a6e6f75a 100644 --- a/examples/official-storybook/stories/addon-jest.stories.js +++ b/examples/official-storybook/stories/addon-jest.stories.js @@ -8,7 +8,7 @@ export default { decorators: [withTestsHOC({ results })], }; -export const withTests = () =>

Hello

; -withTests.story = { +export const WithTests = () =>

Hello

; +WithTests.story = { parameters: { jest: 'addon-jest' }, }; diff --git a/examples/official-storybook/stories/addon-knobs/with-knobs-decorators.stories.js b/examples/official-storybook/stories/addon-knobs/with-knobs-decorators.stories.js index 1a9a5a6d7ba9..478b21d5ef07 100644 --- a/examples/official-storybook/stories/addon-knobs/with-knobs-decorators.stories.js +++ b/examples/official-storybook/stories/addon-knobs/with-knobs-decorators.stories.js @@ -4,10 +4,10 @@ export default { title: 'Addons/Knobs/with decorators', }; -export const withDecoratorCallingStoryFunctionMoreThanOnce = () => { +export const WithDecoratorCallingStoryFunctionMoreThanOnce = () => { return text('Text', 'Hello'); }; -withDecoratorCallingStoryFunctionMoreThanOnce.story = { +WithDecoratorCallingStoryFunctionMoreThanOnce.story = { decorators: [ withKnobs, storyFn => { diff --git a/examples/official-storybook/stories/addon-knobs/with-knobs-options.stories.js b/examples/official-storybook/stories/addon-knobs/with-knobs-options.stories.js index d4ee216a4fd4..df29629444bb 100644 --- a/examples/official-storybook/stories/addon-knobs/with-knobs-options.stories.js +++ b/examples/official-storybook/stories/addon-knobs/with-knobs-options.stories.js @@ -10,7 +10,7 @@ export default { ], }; -export const acceptsOptions = () =>
{text('Rendered string', '

Hello

')}
; -acceptsOptions.story = { +export const AcceptsOptions = () =>
{text('Rendered string', '

Hello

')}
; +AcceptsOptions.story = { name: 'accepts options', }; diff --git a/examples/official-storybook/stories/addon-knobs/with-knobs.stories.js b/examples/official-storybook/stories/addon-knobs/with-knobs.stories.js index c6209f09a214..e458969cc86e 100644 --- a/examples/official-storybook/stories/addon-knobs/with-knobs.stories.js +++ b/examples/official-storybook/stories/addon-knobs/with-knobs.stories.js @@ -45,7 +45,7 @@ export default { decorators: [withKnobs], }; -export const tweaksStaticValues = () => { +export const TweaksStaticValues = () => { const name = text('Name', 'Storyteller'); const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }); const fruits = { @@ -69,7 +69,7 @@ export const tweaksStaticValues = () => { const otherStyles = object('Styles', { border: '2px dashed silver', borderRadius: 10, - padding: '10px', + padding: 10, }); const nice = boolean('Nice', true); const images = files('Happy Picture', 'image/*', [ @@ -119,11 +119,11 @@ export const tweaksStaticValues = () => { ); }; -tweaksStaticValues.story = { +TweaksStaticValues.story = { name: 'tweaks static values', }; -export const tweaksStaticValuesOrganizedInGroups = () => { +export const TweaksStaticValuesOrganizedInGroups = () => { const GROUP_IDS = { DISPLAY: 'Display', GENERAL: 'General', @@ -173,7 +173,7 @@ export const tweaksStaticValuesOrganizedInGroups = () => { { border: '2px dashed silver', borderRadius: 10, - padding: '10px', + padding: 10, }, GROUP_IDS.DISPLAY ); @@ -206,11 +206,11 @@ export const tweaksStaticValuesOrganizedInGroups = () => { ); }; -tweaksStaticValuesOrganizedInGroups.story = { +TweaksStaticValuesOrganizedInGroups.story = { name: 'tweaks static values organized in groups', }; -export const dynamicKnobs = () => { +export const DynamicKnobs = () => { const showOptional = select('Show optional', ['yes', 'no'], 'yes'); return ( @@ -219,11 +219,11 @@ export const dynamicKnobs = () => { ); }; -dynamicKnobs.story = { +DynamicKnobs.story = { name: 'dynamic knobs', }; -export const complexSelect = () => { +export const ComplexSelect = () => { const m = select( 'complex', { @@ -243,11 +243,11 @@ export const complexSelect = () => { ); }; -complexSelect.story = { +ComplexSelect.story = { name: 'complex select', }; -export const optionsKnob = () => { +export const OptionsKnob = () => { const valuesRadio = { Monday: 'Monday', Tuesday: 'Tuesday', @@ -322,7 +322,7 @@ export const optionsKnob = () => { ); }; -export const triggersActionsViaButton = () => { +export const TriggersActionsViaButton = () => { button('Toggle item list state', () => { if (!injectedIsLoading && injectedItems.length === 0) { injectedIsLoading = true; @@ -346,18 +346,18 @@ export const triggersActionsViaButton = () => {
); }; -triggersActionsViaButton.story = { +TriggersActionsViaButton.story = { name: 'triggers actions via button', }; -export const buttonWithReactUseState = () => { +export const ButtonWithReactUseState = () => { const [counter, setCounter] = React.useState(0); button('increment', () => setCounter(counter + 1)); button('decrement', () => setCounter(counter - 1)); return counter; }; -export const xssSafety = () => ( +export const XssSafety = () => (
( }} /> ); -xssSafety.story = { +XssSafety.story = { name: 'XSS safety', }; -export const acceptsStoryParameters = () =>
{text('Rendered string', '

Hello

')}
; -acceptsStoryParameters.story = { +export const AcceptsStoryParameters = () =>
{text('Rendered string', '

Hello

')}
; +AcceptsStoryParameters.story = { name: 'accepts story parameters', parameters: { @@ -378,7 +378,7 @@ acceptsStoryParameters.story = { }, }; -export const withDuplicateDecorator = () => { +export const WithDuplicateDecorator = () => { return text('Text', 'Hello'); }; -withDuplicateDecorator.story = { decorators: [withKnobs] }; +WithDuplicateDecorator.story = { decorators: [withKnobs] }; diff --git a/examples/official-storybook/stories/addon-links/button.stories.js b/examples/official-storybook/stories/addon-links/button.stories.tsx similarity index 68% rename from examples/official-storybook/stories/addon-links/button.stories.js rename to examples/official-storybook/stories/addon-links/button.stories.tsx index 16ef54740b81..73b9352134cf 100644 --- a/examples/official-storybook/stories/addon-links/button.stories.js +++ b/examples/official-storybook/stories/addon-links/button.stories.tsx @@ -6,13 +6,13 @@ export default { }; export const First = () => ( - ); export const Second = () => ( - ); diff --git a/examples/official-storybook/stories/addon-links/href.stories.js b/examples/official-storybook/stories/addon-links/href.stories.js index 98dc6fb4a891..fc2f79bae16b 100644 --- a/examples/official-storybook/stories/addon-links/href.stories.js +++ b/examples/official-storybook/stories/addon-links/href.stories.js @@ -6,12 +6,12 @@ export default { title: 'Addons/Links/Href', }; -export const log = () => { +export const Log = () => { hrefTo('Addons|Links.Href', 'log').then(href => action('URL of this story')(href)); return See action logger; }; -log.story = { +Log.story = { parameters: { options: { panel: 'storybook/actions/panel', diff --git a/examples/official-storybook/stories/addon-notes.stories.js b/examples/official-storybook/stories/addon-notes.stories.js index f6053091f722..b9d05dd35035 100644 --- a/examples/official-storybook/stories/addon-notes.stories.js +++ b/examples/official-storybook/stories/addon-notes.stories.js @@ -77,11 +77,11 @@ export default { title: 'Addons/Notes', }; -export const addonNotes = () => ( +export const AddonNotes = () => ( ); -addonNotes.story = { +AddonNotes.story = { name: 'addon notes', parameters: { notes: @@ -89,44 +89,44 @@ addonNotes.story = { }, }; -export const addonNotesRenderingImportedMarkdown = () => ( +export const AddonNotesRenderingImportedMarkdown = () => ( ); -addonNotesRenderingImportedMarkdown.story = { +AddonNotesRenderingImportedMarkdown.story = { name: 'addon notes rendering imported markdown', parameters: { notes: { markdown: markdownNotes }, }, }; -export const addonNotesRenderingInlineGithubFlavoredMarkdown = () => ( +export const AddonNotesRenderingInlineGithubFlavoredMarkdown = () => ( ); -addonNotesRenderingInlineGithubFlavoredMarkdown.story = { +AddonNotesRenderingInlineGithubFlavoredMarkdown.story = { name: 'addon notes rendering inline, github-flavored markdown', parameters: { notes: { markdown: markdownString }, }, }; -export const withAMarkdownTable = () => ( +export const WithAMarkdownTable = () => ( ); -withAMarkdownTable.story = { +WithAMarkdownTable.story = { name: 'with a markdown table', parameters: { notes: { markdown: markdownTable }, }, }; -export const withAMarkdownGiphy = () => ( +export const WithAMarkdownGiphy = () => ( ); -withAMarkdownGiphy.story = { +WithAMarkdownGiphy.story = { name: 'with a markdown giphy', parameters: { notes: { markdown: giphyMarkdown }, diff --git a/examples/official-storybook/stories/addon-options.stories.js b/examples/official-storybook/stories/addon-options.stories.js index 358abb41b2f6..b8cafe157faf 100644 --- a/examples/official-storybook/stories/addon-options.stories.js +++ b/examples/official-storybook/stories/addon-options.stories.js @@ -4,11 +4,11 @@ export default { title: 'Addons/Options', }; -export const settingName = () => ( +export const SettingName = () => (
This story should have changed the name of the storybook
); -settingName.story = { +SettingName.story = { name: 'setting name', parameters: { @@ -18,11 +18,11 @@ settingName.story = { }, }; -export const hidingAddonPanel = () => ( +export const HidingAddonPanel = () => (
This story should have changed hidden the addons panel
); -hidingAddonPanel.story = { +HidingAddonPanel.story = { name: 'hiding addon panel', parameters: { diff --git a/examples/official-storybook/stories/addon-queryparams.stories.js b/examples/official-storybook/stories/addon-queryparams.stories.js index 8fe633c230f0..3d5c515fb2ae 100644 --- a/examples/official-storybook/stories/addon-queryparams.stories.js +++ b/examples/official-storybook/stories/addon-queryparams.stories.js @@ -13,19 +13,19 @@ export default { }, }; -export const mockIsTrue = () => ( +export const MockIsTrue = () => (
This story should have an extra url query param: {document.location.search}
); -mockIsTrue.story = { +MockIsTrue.story = { name: 'mock is true', }; -export const mockIs4 = () => ( +export const MockIs4 = () => (
This story should have an extra url query param: {document.location.search}
); -mockIs4.story = { +MockIs4.story = { name: 'mock is 4', parameters: { query: { mock: 4 } }, }; diff --git a/examples/official-storybook/stories/addon-storyshots.stories.js b/examples/official-storybook/stories/addon-storyshots.stories.js index 18eeb9eda95d..904ec48f807e 100644 --- a/examples/official-storybook/stories/addon-storyshots.stories.js +++ b/examples/official-storybook/stories/addon-storyshots.stories.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { styled } from '@storybook/theming'; const Block = styled.div({ @@ -12,4 +12,24 @@ export default { title: 'Addons/Storyshots', }; -export const block = () => ; +export const block = () => { + const [hover, setHover] = useState(false); + + return ( + setHover(true)} onMouseLeave={() => setHover(false)}> + {hover && 'I am hovered'} + + ); +}; +block.story = { + parameters: { + async puppeteerTest(page) { + const element = await page.$('[data-test-block]'); + await element.hover(); + const textContent = await element.getProperty('textContent'); + const text = await textContent.jsonValue(); + // eslint-disable-next-line jest/no-standalone-expect + expect(text).toBe('I am hovered'); + }, + }, +}; diff --git a/examples/official-storybook/stories/addon-viewport/custom-default.stories.js b/examples/official-storybook/stories/addon-viewport/custom-default.stories.js index aa98e44191f1..b75528e380bc 100644 --- a/examples/official-storybook/stories/addon-viewport/custom-default.stories.js +++ b/examples/official-storybook/stories/addon-viewport/custom-default.stories.js @@ -28,12 +28,12 @@ export const Inherited = () => ( ); -export const overriddenViaWithViewportParameterizedDecorator = () => ( +export const OverriddenViaWithViewportParameterizedDecorator = () => ( I respect my parents but I should be looking good on iPad. ); -overriddenViaWithViewportParameterizedDecorator.story = { +OverriddenViaWithViewportParameterizedDecorator.story = { name: 'Overridden via "withViewport" parameterized decorator', parameters: { viewport: { defaultViewport: 'ipad' } }, }; diff --git a/examples/official-storybook/stories/addon-viewport/default.stories.js b/examples/official-storybook/stories/addon-viewport/default.stories.js index 945473987ad6..31c43a42c866 100644 --- a/examples/official-storybook/stories/addon-viewport/default.stories.js +++ b/examples/official-storybook/stories/addon-viewport/default.stories.js @@ -12,7 +12,7 @@ export default { }, }, }; -export const defaultFn = () => ( +export const DefaultFn = () => ( I don't have problems being rendered using the default viewport. ); -defaultFn.story = { name: 'default' }; +DefaultFn.story = { name: 'default' }; diff --git a/examples/official-storybook/stories/core/decorators.stories.js b/examples/official-storybook/stories/core/decorators.stories.js index be0c8c2071e7..15af07f7bde9 100644 --- a/examples/official-storybook/stories/core/decorators.stories.js +++ b/examples/official-storybook/stories/core/decorators.stories.js @@ -26,8 +26,8 @@ export default { ], }; -export const all = () =>

Story

; -all.story = { +export const All = () =>

Story

; +All.story = { decorators: [ s => ( <> @@ -38,8 +38,8 @@ all.story = { ], }; -export const deprecated = () =>

Story

; -deprecated.story = { +export const Deprecated = () =>

Story

; +Deprecated.story = { parameters: { decorators: [ s => ( diff --git a/examples/official-storybook/stories/core/errors.stories.js b/examples/official-storybook/stories/core/errors.stories.js index 54950d178087..00c6c19bf082 100644 --- a/examples/official-storybook/stories/core/errors.stories.js +++ b/examples/official-storybook/stories/core/errors.stories.js @@ -7,10 +7,10 @@ export default { title: 'Core/Errors', }; -export const exception = () => { +export const Exception = () => { throw new Error('storyFn threw an error! WHOOPS'); }; -exception.story = { +Exception.story = { name: 'story throws exception', parameters: { storyshots: { disable: true }, @@ -33,8 +33,8 @@ badComponent.story = { }, }; -export const badStory = () => badOutput; -badStory.story = { +export const BadStory = () => badOutput; +BadStory.story = { name: 'story errors - story un-renderable type', parameters: { notes: 'Story does not return something react can render', diff --git a/examples/official-storybook/stories/core/events.stories.js b/examples/official-storybook/stories/core/events.stories.js index 4da37afffc0d..7f193d263b39 100644 --- a/examples/official-storybook/stories/core/events.stories.js +++ b/examples/official-storybook/stories/core/events.stories.js @@ -13,5 +13,5 @@ export default { title: 'Core/Events', }; -export const force = () => ; -force.story = { name: 'Force re-render' }; +export const Force = () => ; +Force.story = { name: 'Force re-render' }; diff --git a/examples/official-storybook/stories/core/parameters.stories.js b/examples/official-storybook/stories/core/parameters.stories.js index 25c93dadf61b..2afcde8a13ad 100644 --- a/examples/official-storybook/stories/core/parameters.stories.js +++ b/examples/official-storybook/stories/core/parameters.stories.js @@ -19,10 +19,10 @@ export default { // I'm not sure what we should recommend regarding propTypes? are they a good idea for examples? // Given we sort of control the props, should we export a prop type? -export const passed = ({ parameters: { options, fileName, ...parameters }, ...rest }) => ( +export const Passed = ({ parameters: { options, fileName, ...parameters }, ...rest }) => (
Parameters: {JSON.stringify(parameters, null, 2)}
); -passed.story = { +Passed.story = { name: 'passed to story', parameters: { storyParameter: 'storyParameter' }, }; diff --git a/examples/official-storybook/stories/core/scroll.stories.js b/examples/official-storybook/stories/core/scroll.stories.js index 89a1d283694b..73b7b4bca47c 100644 --- a/examples/official-storybook/stories/core/scroll.stories.js +++ b/examples/official-storybook/stories/core/scroll.stories.js @@ -1,29 +1,56 @@ import React from 'react'; +import { styled } from '@storybook/theming'; + +import { Spaced } from '@storybook/components'; export default { title: 'Core/Scroll', }; -export const story1 = () => ( -
+const Horizontal = styled(props => )({ + display: 'grid', + gridTemplateColumns: '100px calc(100vw + 100px) 100px', +}); +const Vertical = styled(props => )({}); + +export const Story1 = () => ( +
START, when switching stories, you should be able to read this at the top of the page
-
+
middle
       END, this text should be below the scroll "fold" and therefore only be readable after
       scrolling
     
-
+
); -story1.story = { name: 'story with 100vh padding 1' }; +Story1.story = { name: 'story with 100vh padding 1' }; -export const story2 = () => ( -
+export const Story2 = () => ( +
START, when switching stories, you should be able to read this at the top of the page
-
+
middle
       END, this text should be below the scroll "fold" and therefore only be readable after
       scrolling
     
-
+
+); +Story2.story = { name: 'story with 100vh padding 2' }; + +export const Story3 = () => ( + +
START
+
middle
+
END
+
+); +Story3.story = { name: 'story with 100vw+' }; + +export const Story4 = () => ( + +
START
+
middle
+
END
+
); -story2.story = { name: 'story with 100vh padding 2' }; +Story4.story = { name: 'story with 100vw+ 2' }; diff --git a/examples/official-storybook/stories/demo/button.stories.js b/examples/official-storybook/stories/demo/button.stories.js index ed9e74c27d4c..d641ccae0054 100644 --- a/examples/official-storybook/stories/demo/button.stories.js +++ b/examples/official-storybook/stories/demo/button.stories.js @@ -13,29 +13,29 @@ export default { }, }; -export const withText = () => ; -withText.story = { +export const WithText = () => ; +WithText.story = { name: 'with text', }; -export const withSomeEmoji = () => ( +export const WithSomeEmoji = () => ( ); -withSomeEmoji.story = { +WithSomeEmoji.story = { name: 'with some emoji', }; -export const withCounter = () => { +export const WithCounter = () => { const [counter, setCounter] = useState(0); const label = `Testing: ${counter}`; return ; }; -withCounter.story = { +WithCounter.story = { name: 'with counter', parameters: { docs: { diff --git a/examples/official-storybook/stories/demo/csf-embedding.test.js b/examples/official-storybook/stories/demo/csf-embedding.test.js index b5f566e3257e..ba26d54e53d3 100644 --- a/examples/official-storybook/stories/demo/csf-embedding.test.js +++ b/examples/official-storybook/stories/demo/csf-embedding.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { withText as WithText, withCounter as WithCounter } from './button.stories'; +import { WithText, WithCounter } from './button.stories'; const mockAction = jest.fn(); jest.mock('@storybook/addon-actions', () => ({ diff --git a/examples/official-storybook/stories/demo/welcome.stories.js b/examples/official-storybook/stories/demo/welcome.stories.js index b0b56a55bc04..775b761ee1cc 100644 --- a/examples/official-storybook/stories/demo/welcome.stories.js +++ b/examples/official-storybook/stories/demo/welcome.stories.js @@ -7,7 +7,10 @@ export default { component: Welcome, }; -export const toStorybook = () => ; -toStorybook.story = { +// Some other valid values: +// - 'other-demo-buttonmdx--with-text' +// - 'Other/Demo/ButtonMdx' +export const ToStorybook = () => ; +ToStorybook.story = { name: 'to Storybook', }; diff --git a/examples/official-storybook/stories/deprecated/addon-actions.stories.js b/examples/official-storybook/stories/deprecated/addon-actions.stories.js index 0da9183e3d70..eb04f408d1de 100644 --- a/examples/official-storybook/stories/deprecated/addon-actions.stories.js +++ b/examples/official-storybook/stories/deprecated/addon-actions.stories.js @@ -18,10 +18,10 @@ export default { title: 'Addons/Actions/deprecated', }; -export const decoratedAction = () => ( +export const DecoratedAction = () => ( ); -decoratedAction.story = { +DecoratedAction.story = { name: 'Decorated Action', }; diff --git a/examples/official-storybook/stories/deprecated/addon-info.stories.js b/examples/official-storybook/stories/deprecated/addon-info.stories.js index 4180d2ed88d0..f63fadce4b12 100644 --- a/examples/official-storybook/stories/deprecated/addon-info.stories.js +++ b/examples/official-storybook/stories/deprecated/addon-info.stories.js @@ -9,8 +9,8 @@ export default { title: 'Addons/Info/deprecated', }; -export const displaysMarkdown = withInfo(markdownDescription)(() => ( +export const DisplaysMarkdown = withInfo(markdownDescription)(() => ( )); -displaysMarkdown.story = { name: 'Displays Markdown in description' }; +DisplaysMarkdown.story = { name: 'Displays Markdown in description' }; diff --git a/examples/official-storybook/stories/hooks.stories.js b/examples/official-storybook/stories/hooks.stories.js index 3f349858e254..6f705a024982 100644 --- a/examples/official-storybook/stories/hooks.stories.js +++ b/examples/official-storybook/stories/hooks.stories.js @@ -20,7 +20,7 @@ export const Input = () => { return setText(e.target.value)} />; }; -export const effect = () => { +export const Effect = () => { const ref = useRef(); useEffect(() => { if (ref.current != null) { @@ -35,7 +35,7 @@ export const effect = () => { ); }; -export const reactHookCheckbox = () => { +export const ReactHookCheckbox = () => { const [on, setOn] = React.useState(false); return (
`; diff --git a/examples/riot-kitchen-sink/src/stories/addon-actions.stories.js b/examples/riot-kitchen-sink/src/stories/addon-actions.stories.js index 2a2966ca1c7f..cf8196fda117 100644 --- a/examples/riot-kitchen-sink/src/stories/addon-actions.stories.js +++ b/examples/riot-kitchen-sink/src/stories/addon-actions.stories.js @@ -8,22 +8,22 @@ export default { title: 'Addon/Actions', }; -export const actionOnly = () => +export const ActionOnly = () => mount('my-button', { handleClick: action('button-click'), content: 'Click me to log the action', }); -actionOnly.story = { +ActionOnly.story = { name: 'Action only', }; -export const multipleActions = () => +export const MultipleActions = () => mount('my-button', { handleDblClick: action('button-double-click'), content: 'Double Click me to log the action', }); -multipleActions.story = { +MultipleActions.story = { name: 'Multiple actions', }; diff --git a/examples/riot-kitchen-sink/src/stories/addon-backgrounds.stories.js b/examples/riot-kitchen-sink/src/stories/addon-backgrounds.stories.js index 9c5ba032ab47..6b18ecddfb36 100644 --- a/examples/riot-kitchen-sink/src/stories/addon-backgrounds.stories.js +++ b/examples/riot-kitchen-sink/src/stories/addon-backgrounds.stories.js @@ -10,7 +10,7 @@ export default { }, }; -export const story1 = () => { +export const Story1 = () => { const content = 'You should be able to switch backgrounds for this story'; return { @@ -19,11 +19,11 @@ export const story1 = () => { }; }; -story1.story = { +Story1.story = { name: 'story 1', }; -export const story2 = () => { +export const Story2 = () => { const content = 'This one too!'; return { @@ -32,6 +32,6 @@ export const story2 = () => { }; }; -story2.story = { +Story2.story = { name: 'story 2', }; diff --git a/examples/riot-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/riot-kitchen-sink/src/stories/addon-knobs.stories.js index de49663bb517..6d754a7af993 100644 --- a/examples/riot-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/riot-kitchen-sink/src/stories/addon-knobs.stories.js @@ -26,7 +26,7 @@ export const Simple = () => { }; }; -export const allKnobs = () => { +export const AllKnobs = () => { const name = text('Name', 'Jane'); const stock = number('Stock', 20, { range: true, @@ -73,11 +73,11 @@ export const allKnobs = () => { }; }; -allKnobs.story = { +AllKnobs.story = { name: 'All knobs', }; -export const xssSafety = () => ({ +export const XssSafety = () => ({ tags: [ `
@@ -87,6 +87,6 @@ export const xssSafety = () => ({ ], }); -xssSafety.story = { +XssSafety.story = { name: 'XSS safety', }; diff --git a/examples/riot-kitchen-sink/src/stories/addon-links.stories.js b/examples/riot-kitchen-sink/src/stories/addon-links.stories.js index a372d6a5b468..1f2cd6303566 100644 --- a/examples/riot-kitchen-sink/src/stories/addon-links.stories.js +++ b/examples/riot-kitchen-sink/src/stories/addon-links.stories.js @@ -8,7 +8,7 @@ export default { title: 'Addon/Links', }; -export const goToWelcome = () => +export const GoToWelcome = () => mount('my-button', { rounded: true, content: 'This button links to Welcome', @@ -16,6 +16,6 @@ export const goToWelcome = () => handleClick: linkTo('Welcome', 'Welcome'), }); -goToWelcome.story = { +GoToWelcome.story = { name: 'Go to welcome', }; diff --git a/examples/riot-kitchen-sink/src/stories/addon-notes.stories.js b/examples/riot-kitchen-sink/src/stories/addon-notes.stories.js index 1b20fdc48302..cd8f2ad23633 100644 --- a/examples/riot-kitchen-sink/src/stories/addon-notes.stories.js +++ b/examples/riot-kitchen-sink/src/stories/addon-notes.stories.js @@ -2,22 +2,22 @@ export default { title: 'Addon/Notes', }; -export const simpleNote = () => ({ +export const SimpleNote = () => ({ tags: [ '

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 = { +SimpleNote.story = { name: 'Simple note', parameters: { notes: 'My notes on some bold text' }, }; -export const noteWithHtml = () => ({ +export const NoteWithHtml = () => ({ tags: ['

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

'], }); -noteWithHtml.story = { +NoteWithHtml.story = { name: 'Note with HTML', parameters: { notes: ` diff --git a/examples/riot-kitchen-sink/src/stories/core.stories.js b/examples/riot-kitchen-sink/src/stories/core.stories.js index f34fbb367797..769fe3c29830 100644 --- a/examples/riot-kitchen-sink/src/stories/core.stories.js +++ b/examples/riot-kitchen-sink/src/stories/core.stories.js @@ -15,9 +15,9 @@ export default { }, }; -export const passedToStory = ({ parameters: { fileName, ...parameters } }) => +export const PassedToStory = ({ parameters: { fileName, ...parameters } }) => mount('parameters', { ...parameters, storyParameter }); -passedToStory.story = { +PassedToStory.story = { name: 'passed to story', }; diff --git a/examples/riot-kitchen-sink/src/stories/nested-tags.stories.js b/examples/riot-kitchen-sink/src/stories/nested-tags.stories.js index f6180cb8689e..ab021ebbb888 100644 --- a/examples/riot-kitchen-sink/src/stories/nested-tags.stories.js +++ b/examples/riot-kitchen-sink/src/stories/nested-tags.stories.js @@ -8,7 +8,7 @@ export default { title: 'Story/Nest tags', }; -export const threeTags = () => ({ +export const ThreeTags = () => ({ tags: [ 'Simple titleSimple Content', '

', @@ -16,7 +16,7 @@ export const threeTags = () => ({ ], }); -threeTags.story = { +ThreeTags.story = { name: 'Three tags', }; diff --git a/examples/riot-kitchen-sink/src/stories/story-code.stories.js b/examples/riot-kitchen-sink/src/stories/story-code.stories.js index 49a3ab6e535c..7a06796e7cd9 100644 --- a/examples/riot-kitchen-sink/src/stories/story-code.stories.js +++ b/examples/riot-kitchen-sink/src/stories/story-code.stories.js @@ -8,33 +8,33 @@ export default { title: 'Story/How to create a story', }; -export const builtWithTag = () => +export const BuiltWithTag = () => tag('test', '
simple test ({ opts.value })
', '', '', () => {}) && mount('test', { value: 'with a parameter' }); -builtWithTag.story = { +BuiltWithTag.story = { name: 'built with tag', }; -export const builtAsString = () => ({ tags: ['
simple test
'] }); +export const BuiltAsString = () => ({ tags: ['
simple test
'] }); -builtAsString.story = { +BuiltAsString.story = { name: 'built as string', }; -export const builtFromRawImport = () => simpleTestCompiled; +export const BuiltFromRawImport = () => simpleTestCompiled; -builtFromRawImport.story = { +BuiltFromRawImport.story = { name: 'built from raw import', }; -export const builtFromTagsAndTemplate = () => ({ +export const BuiltFromTagsAndTemplate = () => ({ tags: [{ content: SimpleTestRaw, boundAs: 'mustBeUniquePlease' }], template: '', }); -builtFromTagsAndTemplate.story = { +BuiltFromTagsAndTemplate.story = { name: 'built from tags and template', parameters: { notes: @@ -42,7 +42,7 @@ builtFromTagsAndTemplate.story = { }, }; -export const tagsTemplateAndTagConstructorAtOnce = () => ({ +export const TagsTemplateAndTagConstructorAtOnce = () => ({ tags: [ { content: @@ -57,27 +57,27 @@ export const tagsTemplateAndTagConstructorAtOnce = () => ({ }, }); -tagsTemplateAndTagConstructorAtOnce.story = { +TagsTemplateAndTagConstructorAtOnce.story = { name: 'tags, template and tagConstructor at once', }; -export const builtFromThePrecompilation = () => mount('anothertest', {}); +export const BuiltFromThePrecompilation = () => mount('anothertest', {}); -builtFromThePrecompilation.story = { +BuiltFromThePrecompilation.story = { name: 'built from the precompilation', parameters: { notes: 'WARN, only works in lower case, never upper case with precompiled templates', }, }; -export const theMountInstructionIsNotNecessary = () => ({ tagName: 'anothertest', opts: {} }); +export const TheMountInstructionIsNotNecessary = () => ({ tagName: 'anothertest', opts: {} }); -theMountInstructionIsNotNecessary.story = { +TheMountInstructionIsNotNecessary.story = { name: 'the mount instruction is not necessary', }; -export const theOptsValueIsNotNecessary = () => ({ tagName: 'anothertest' }); +export const TheOptsValueIsNotNecessary = () => ({ tagName: 'anothertest' }); -theOptsValueIsNotNecessary.story = { +TheOptsValueIsNotNecessary.story = { name: 'the opts value is not necessary', }; diff --git a/examples/standalone-preview/package.json b/examples/standalone-preview/package.json index 154968e489cf..20c7ca37acbe 100644 --- a/examples/standalone-preview/package.json +++ b/examples/standalone-preview/package.json @@ -1,12 +1,12 @@ { "name": "standalone-preview", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "storybook": "parcel ./storybook.html --port 1337" }, "devDependencies": { - "@storybook/react": "5.3.0-alpha.45", + "@storybook/react": "5.3.0-rc.0", "parcel": "^1.12.3", "react": "^16.8.4", "react-dom": "^16.8.4" diff --git a/examples/svelte-kitchen-sink/.storybook/addons.js b/examples/svelte-kitchen-sink/.storybook/addons.js deleted file mode 100644 index d948e3fd71be..000000000000 --- a/examples/svelte-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-backgrounds/register'; -import '@storybook/addon-viewport/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-a11y/register'; diff --git a/examples/svelte-kitchen-sink/.storybook/config.js b/examples/svelte-kitchen-sink/.storybook/config.js deleted file mode 100644 index fa4f691f63b7..000000000000 --- a/examples/svelte-kitchen-sink/.storybook/config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { configure, addParameters, addDecorator } from '@storybook/svelte'; -import { withA11y } from '@storybook/addon-a11y'; - -addDecorator(withA11y); -addParameters({ - options: { - showRoots: true, - }, -}); - -configure(require.context('../src/stories', true, /\.stories\.js$/), module); diff --git a/examples/svelte-kitchen-sink/.storybook/main.js b/examples/svelte-kitchen-sink/.storybook/main.js new file mode 100644 index 000000000000..28172cac2889 --- /dev/null +++ b/examples/svelte-kitchen-sink/.storybook/main.js @@ -0,0 +1,25 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/stories/**/*.stories.*'], + addons: [ + '@storybook/addon-storysource/register', + '@storybook/addon-actions/register', + '@storybook/addon-links/register', + '@storybook/addon-notes/register', + '@storybook/addon-knobs/register', + '@storybook/addon-backgrounds/register', + '@storybook/addon-viewport/register', + '@storybook/addon-options/register', + '@storybook/addon-a11y/register', + ], + webpack: async config => { + config.module.rules.push({ + test: [/\.stories\.js$/, /index\.js$/], + loaders: [require.resolve('@storybook/source-loader')], + include: [path.resolve(__dirname, '../src')], + enforce: 'pre', + }); + return config; + }, +}; diff --git a/examples/svelte-kitchen-sink/.storybook/manager.js b/examples/svelte-kitchen-sink/.storybook/manager.js new file mode 100644 index 000000000000..ee8398728448 --- /dev/null +++ b/examples/svelte-kitchen-sink/.storybook/manager.js @@ -0,0 +1,5 @@ +import { addons } from '@storybook/addons'; + +addons.setConfig({ + showRoots: true, +}); diff --git a/examples/svelte-kitchen-sink/.storybook/preview.js b/examples/svelte-kitchen-sink/.storybook/preview.js new file mode 100644 index 000000000000..7f5d4bed2e58 --- /dev/null +++ b/examples/svelte-kitchen-sink/.storybook/preview.js @@ -0,0 +1,4 @@ +import { addDecorator } from '@storybook/svelte'; +import { withA11y } from '@storybook/addon-a11y'; + +addDecorator(withA11y); diff --git a/examples/svelte-kitchen-sink/.storybook/webpack.config.js b/examples/svelte-kitchen-sink/.storybook/webpack.config.js deleted file mode 100644 index 243b6489507a..000000000000 --- a/examples/svelte-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$/, /index\.js$/], - loaders: [require.resolve('@storybook/source-loader')], - include: [path.resolve(__dirname, '../src')], - enforce: 'pre', - }); - return config; -}; diff --git a/examples/svelte-kitchen-sink/package.json b/examples/svelte-kitchen-sink/package.json index 9a0424be91f2..94ad34b092d3 100644 --- a/examples/svelte-kitchen-sink/package.json +++ b/examples/svelte-kitchen-sink/package.json @@ -1,6 +1,6 @@ { "name": "svelte-example", - "version": "5.3.0-alpha.45", + "version": "5.3.0-rc.0", "private": true, "scripts": { "build-storybook": "build-storybook -s public", @@ -10,19 +10,19 @@ "global": "^4.3.2" }, "devDependencies": { - "@storybook/addon-a11y": "5.3.0-alpha.45", - "@storybook/addon-actions": "5.3.0-alpha.45", - "@storybook/addon-backgrounds": "5.3.0-alpha.45", - "@storybook/addon-centered": "5.3.0-alpha.45", - "@storybook/addon-knobs": "5.3.0-alpha.45", - "@storybook/addon-links": "5.3.0-alpha.45", - "@storybook/addon-notes": "5.3.0-alpha.45", - "@storybook/addon-options": "5.3.0-alpha.45", - "@storybook/addon-storyshots": "5.3.0-alpha.45", - "@storybook/addon-storysource": "5.3.0-alpha.45", - "@storybook/addon-viewport": "5.3.0-alpha.45", - "@storybook/addons": "5.3.0-alpha.45", - "@storybook/source-loader": "5.3.0-alpha.45", - "@storybook/svelte": "5.3.0-alpha.45" + "@storybook/addon-a11y": "5.3.0-rc.0", + "@storybook/addon-actions": "5.3.0-rc.0", + "@storybook/addon-backgrounds": "5.3.0-rc.0", + "@storybook/addon-centered": "5.3.0-rc.0", + "@storybook/addon-knobs": "5.3.0-rc.0", + "@storybook/addon-links": "5.3.0-rc.0", + "@storybook/addon-notes": "5.3.0-rc.0", + "@storybook/addon-options": "5.3.0-rc.0", + "@storybook/addon-storyshots": "5.3.0-rc.0", + "@storybook/addon-storysource": "5.3.0-rc.0", + "@storybook/addon-viewport": "5.3.0-rc.0", + "@storybook/addons": "5.3.0-rc.0", + "@storybook/source-loader": "5.3.0-rc.0", + "@storybook/svelte": "5.3.0-rc.0" } } diff --git a/examples/svelte-kitchen-sink/src/components/Button.svelte b/examples/svelte-kitchen-sink/src/components/Button.svelte index 43a8cf854d15..6e69c974368a 100644 --- a/examples/svelte-kitchen-sink/src/components/Button.svelte +++ b/examples/svelte-kitchen-sink/src/components/Button.svelte @@ -22,7 +22,6 @@