diff --git a/.circleci/config.yml b/.circleci/config.yml index 3658e56fe2f..8e7a099ee28 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,14 +1,21 @@ version: 2.1 -aliases: - - &defaults +executors: + sb_node: + parameters: + class: + description: The Resource class + type: enum + enum: ["small", "medium", "large", "xlarge"] + default: "medium" working_directory: /tmp/storybook docker: - image: circleci/node:10-browsers + resource_class: <> jobs: install: - <<: *defaults + executor: sb_node steps: - checkout - restore_cache: @@ -37,7 +44,7 @@ jobs: - app - lib build: - <<: *defaults + executor: sb_node steps: - checkout - attach_workspace: @@ -54,7 +61,7 @@ jobs: - app - lib chromatic: - <<: *defaults + executor: sb_node parallelism: 11 steps: - checkout @@ -65,7 +72,7 @@ jobs: command: | yarn run-chromatics packtracker: - <<: *defaults + executor: sb_node steps: - checkout - attach_workspace: @@ -76,7 +83,7 @@ jobs: cd examples/official-storybook yarn packtracker examples: - <<: *defaults + executor: sb_node parallelism: 11 steps: - checkout @@ -91,7 +98,7 @@ jobs: paths: - built-storybooks publish: - <<: *defaults + executor: sb_node steps: - checkout - attach_workspace: @@ -159,7 +166,7 @@ jobs: command: yarn info @storybook/core - run: name: run e2e tests - command: yarn test:e2e-framework yarn2Cra + command: yarn test:e2e-framework --use-yarn-2 sfcVue cra - store_artifacts: path: /tmp/storybook/cypress destination: cypress @@ -183,9 +190,12 @@ jobs: - run: name: cypress run command: yarn test:e2e + - store_artifacts: + path: /tmp/storybook/cypress + destination: cypress smoke-tests: - <<: *defaults + executor: sb_node steps: - checkout - attach_workspace: @@ -251,7 +261,7 @@ jobs: cd examples/cra-react15 yarn storybook --smoke-test --quiet frontpage: - <<: *defaults + executor: sb_node steps: - checkout - restore_cache: @@ -264,22 +274,10 @@ jobs: - run: name: Trigger build command: ./scripts/build-frontpage.js - docs: - <<: *defaults - steps: - - checkout - - run: - name: Install dependencies - command: | - cd docs - yarn install - - run: - name: Build docs - command: | - cd docs - yarn build lint: - <<: *defaults + executor: + class: small + name: sb_node steps: - checkout - attach_workspace: @@ -288,7 +286,7 @@ jobs: name: Lint command: yarn lint test: - <<: *defaults + executor: sb_node steps: - checkout - attach_workspace: @@ -301,7 +299,9 @@ jobs: paths: - coverage coverage: - <<: *defaults + executor: + class: small + name: sb_node steps: - checkout - attach_workspace: @@ -352,5 +352,4 @@ workflows: - publish deploy: jobs: - - docs - frontpage diff --git a/.eslintrc.js b/.eslintrc.js index 99d83c78c2f..9bcec6a682d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,5 +49,11 @@ module.exports = { 'spaced-comment': 'off', }, }, + { + files: ['**/mithril/**/*'], + rules: { + 'react/no-unknown-property': 'off', // Need to deactivate otherwise eslint replaces some unknown properties with React ones + }, + }, ], }; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8a63d2211db..25634de47a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,6 @@ --- name: Bug report about: Create a report to help us improve - --- **Describe the bug** @@ -9,6 +8,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,7 +24,7 @@ If applicable, add screenshots to help explain your problem. If applicable, add code samples to help explain your problem. **System:** -Please paste the results of `npx -p @storybook/cli@next sb info` here. +Please paste the results of `npx sb@next info` here. **Additional context** Add any other context about the problem here. diff --git a/.github/automention.yml b/.github/automention.yml index 4ca3b6a6256..fd2029bf062 100644 --- a/.github/automention.yml +++ b/.github/automention.yml @@ -1,5 +1,5 @@ -'app: angular': ['kroeder', 'igor-dv', 'MaximSagan'] -'app: ember': ['gabrielcsapo'] +'app: angular': ['kroeder', 'igor-dv', 'MaximSagan', 'Marklb'] +'app: ember': ['gabrielcsapo', 'gossi', 'meirish'] 'app: html': ['Hypnosphi'] 'app: marko': ['nm123github'] 'app: preact': ['BartWaardenburg'] @@ -16,8 +16,9 @@ typescript: ['kroeder', 'gaetanmaisse', 'ndelangen', 'emilio-martinez'] theming: ['ndelangen', 'domyen'] cra: ['mrmckeb'] -cli: ['Keraito'] +cli: ['yannbf', 'tooppaaa'] dependencies: ['ndelangen'] 'babel / webpack': ['ndelangen', 'igor-dv', 'shilman'] 'yarn / npm': ['ndelangen', 'tmeasday'] 'source-loader': ['libetl'] +documentation: ['jonniebigodes'] diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml index 3b5a154a4c6..e8348e0c053 100644 --- a/.github/workflows/tests-cli.yml +++ b/.github/workflows/tests-cli.yml @@ -17,6 +17,9 @@ jobs: - uses: actions/setup-node@v1 with: node-version: '10.x' + - name: increase filewatcher limit + run: | + echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v1 @@ -32,40 +35,3 @@ jobs: - name: cli run: | yarn test --cli - cli-yarn-2: - name: CLI Fixtures with Yarn 2 - runs-on: ubuntu-latest - steps: - - uses: actions/setup-node@v1 - with: - node-version: '10.x' - - uses: actions/checkout@v2 - - name: Cache node modules - uses: actions/cache@v1 - with: - path: node_modules - key: build-v2-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - build-v2-${{ env.cache-name }}- - build-v2- - - name: install, bootstrap - run: | - yarn bootstrap --core - - name: cli with Yarn 2 - run: | - cd lib/cli - yarn test-yarn-2 - latest-cra: - name: Latest CRA - runs-on: ubuntu-latest - steps: - - uses: actions/setup-node@v1 - with: - node-version: '10.x' - - uses: actions/checkout@v2 - - name: install, bootstrap - run: | - yarn bootstrap --core - - name: latest-cra - run: | - yarn test-latest-cra diff --git a/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index cdad86428e7..d795c7a9b6b 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -1,24 +1,24 @@ ## Addon / Framework Support Table -| | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Mithril](app/mithril) | [HTML](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) | -| ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- | -| [a11y](addons/a11y) | + | | + | + | + | + | + | + | + | + | + | + | -| [actions](addons/actions) | + | +\* | + | + | + | + | + | + | + | + | + | + | -| [backgrounds](addons/backgrounds) | + | \* | + | + | + | + | + | + | + | + | + | + | -| [cssresources](addons/cssresources) | + | | + | + | + | + | + | + | + | + | + | + | -| [design assets](addons/design-assets) | + | | + | + | + | + | + | + | + | + | + | + | -| [docs](addons/docs) | + | | + | + | + | + | + | + | + | + | + | + | -| [events](addons/events) | + | | + | + | + | + | + | | | + | + | + | -| [google-analytics](addons/google-analytics) | + | + | + | + | + | + | + | + | + | + | + | + | -| [graphql](addons/graphql) | + | | | | | | | | | | | | -| [jest](addons/jest) | + | + | + | + | + | + | + | + | + | + | + | + | -| [knobs](addons/knobs) | + | +\* | + | + | + | + | + | + | + | + | + | + | -| [links](addons/links) | + | + | + | + | + | + | | + | + | + | + | + | -| [options](addons/options) | + | + | + | + | + | + | | + | + | + | + | + | -| [query params](addons/queryparams) | + | | + | + | + | + | + | + | + | + | + | + | -| [storyshots](addons/storyshots) | + | + | + | + | | + | | + | + | | + | + | -| [storysource](addons/storysource) | + | | + | + | + | + | + | + | + | + | + | + | -| [viewport](addons/viewport) | + | | + | + | + | + | + | + | + | + | + | + | +| | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Mithril](app/mithril) | [HTML](app/html) | [Web Components](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) | +| ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------: | :------------------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- | +| [a11y](addons/a11y) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [actions](addons/actions) | + | +\* | + | + | + | + | + | + | + | + | + | + | + | +| [backgrounds](addons/backgrounds) | + | \* | + | + | + | + | + | + | + | + | + | + | + | +| [cssresources](addons/cssresources) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [design assets](addons/design-assets) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [docs](addons/docs) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [events](addons/events) | + | | + | + | + | + | + | + | | | + | + | + | +| [google-analytics](addons/google-analytics) | + | + | + | + | + | + | + | + | + | + | + | + | + | +| [graphql](addons/graphql) | + | | | | | | | | | | | | | +| [jest](addons/jest) | + | + | + | + | + | + | + | + | + | + | + | + | + | +| [knobs](addons/knobs) | + | +\* | + | + | + | + | + | + | + | + | + | + | + | +| [links](addons/links) | + | + | + | + | + | + | + | | + | + | + | + | + | +| [options](addons/options) | + | + | + | + | + | + | + | | + | + | + | + | + | +| [query params](addons/queryparams) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [storyshots](addons/storyshots) | + | + | + | + | | + | + | | + | + | | + | + | +| [storysource](addons/storysource) | + | | + | + | + | + | + | + | + | + | + | + | + | +| [viewport](addons/viewport) | + | | + | + | + | + | + | + | + | + | + | + | + | `*` - React Native on device addon (addons/onDevice-\) @@ -26,7 +26,7 @@ | | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Mithril](app/mithril) | [HTML](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) | | ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- | -| [info](https://github.com/storybookjs/storybook/tree/master/addons/info) | + | | | | | | | | | | | | -| [notes](https://github.com/storybookjs/storybook/tree/master/addons/notes) | + | +\* | + | + | + | + | | + | + | + | + | + | +| [info](https://github.com/storybookjs/deprecated-addons/tree/master/addons/info) | + | | | | | | | | | | | | +| [notes](https://github.com/storybookjs/deprecated-addons/tree/master/addons/notes) | + | +\* | + | + | + | + | | + | + | + | + | + | `*` - React Native on device addon (addons/onDevice-\) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6201a35c3..b86fa0e98e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,1047 @@ +## 6.1.0-alpha.14 (September 22, 2020) + +### Features + +- Web Components: Add script tag support ([#12509](https://github.com/storybookjs/storybook/pull/12509)) + +### Bug Fixes + +- React: Fix fast refresh ([#12535](https://github.com/storybookjs/storybook/pull/12535)) + +### Maintenance + +- CLI: Change suggested upgrade command to sb@latest ([#12533](https://github.com/storybookjs/storybook/pull/12533)) + +## 6.1.0-alpha.13 (September 22, 2020) + +Failed NPM publish + +## 6.1.0-alpha.12 (September 21, 2020) + +### Features + +- React: Add react-refresh ([#12470](https://github.com/storybookjs/storybook/pull/12470)) +- Server: Add support for script tags ([#12522](https://github.com/storybookjs/storybook/pull/12522)) + +### Dependency Upgrades + +- Core: Upgrade babel ([#12499](https://github.com/storybookjs/storybook/pull/12499)) + +## 6.1.0-alpha.11 (September 19, 2020) + +### Bug Fixes + +- Preact: Keep the story state between rerenders ([#12221](https://github.com/storybookjs/storybook/pull/12221)) + +### Maintenance + +- Addon-controls: Update style of Boolean control ([#12515](https://github.com/storybookjs/storybook/pull/12515)) + +## 6.1.0-alpha.10 (September 16, 2020) + +### Features + +- Ember: Add `emberOptions` to `main.js` config ✨ ([#12440](https://github.com/storybookjs/storybook/pull/12440)) + +### Bug Fixes + +- React: Fix reactDocgen option when false ([#12492](https://github.com/storybookjs/storybook/pull/12492)) + +## 6.1.0-alpha.9 (September 13, 2020) + +### Features + +- Storyshots: Allow taking a screenshot of just a specific element ([#12460](https://github.com/storybookjs/storybook/pull/12460)) + +### Bug Fixes + +- CLI: Fix storiesof-to-csf codemod for TypeScript ([#12453](https://github.com/storybookjs/storybook/pull/12453)) + +### Maintenance + +- Addon-docs: Resolve vue-docgen-loader from @storybook/vue ([#12461](https://github.com/storybookjs/storybook/pull/12461)) +- Build: Disable problematic story in Chromatic ([#12457](https://github.com/storybookjs/storybook/pull/12457)) + +## 6.1.0-alpha.8 (September 12, 2020) + +### Features + +- HTML: Add script tag support ([#12089](https://github.com/storybookjs/storybook/pull/12089)) +- Addon-docs: Fix fixed-position inline stories ([#11350](https://github.com/storybookjs/storybook/pull/11350)) + +### Bug Fixes + +- Core: require.resolve loaders and add missing dependencies ([#12383](https://github.com/storybookjs/storybook/pull/12383)) +- Addon-docs: Fix DocsPage scroll behavior ([#12047](https://github.com/storybookjs/storybook/pull/12047)) + +### Maintenance + +- Core: Fix monorepo compatibility ([#11753](https://github.com/storybookjs/storybook/pull/11753)) + +## 6.1.0-alpha.7 (September 10, 2020) + +### Features + +- Components: Add graphql support to SyntaxHighlighter ([#12385](https://github.com/storybookjs/storybook/pull/12385)) + +### Bug Fixes + +- UI: Fix the p > div nesting issue ([#12298](https://github.com/storybookjs/storybook/pull/12298)) +- Addon-docs: Apply transformSource to any SourceType ([#12375](https://github.com/storybookjs/storybook/pull/12375)) +- CLI: Workaround for react native `sb init` ([#12405](https://github.com/storybookjs/storybook/pull/12405)) + +### Dependency Upgrades + +- Core: Change react deps to normal deps ([#11628](https://github.com/storybookjs/storybook/pull/11628)) + +## 6.1.0-alpha.6 (September 10, 2020) + +### Features + +- Core: Add static dir path mappings ([#12222](https://github.com/storybookjs/storybook/pull/12222)) +- Addon-controls: Default to radio control for small enums ([#12436](https://github.com/storybookjs/storybook/pull/12436)) + +### Bug Fixes + +- Source-loader: Export extract-source in its own entry point ([#12429](https://github.com/storybookjs/storybook/pull/12429)) +- Addon-docs: Prefer flow's union elements over raw values ([#12376](https://github.com/storybookjs/storybook/pull/12376)) + +## 6.1.0-alpha.5 (September 10, 2020) + +Failed npm publish + +## 6.1.0-alpha.4 (September 8, 2020) + +### Features + +- Addon-docs: Add Methods to web components ArgsTable ([#12413](https://github.com/storybookjs/storybook/pull/12413)) + +### Bug Fixes + +- Addon-docs: Introduce undefined filtering to jsxDecorator ([#12365](https://github.com/storybookjs/storybook/pull/12365)) +- Addon-docs: Fix missing line-height on TypeSet block ([#12134](https://github.com/storybookjs/storybook/pull/12134)) + +### Maintenance + +- Addon-docs: Reuse extractSource from source-loader ([#12225](https://github.com/storybookjs/storybook/pull/12225)) + +## 6.1.0-alpha.3 (September 3, 2020) + +### Features + +- Addon-docs: Add Controls argument autodetection for svelte ([#12347](https://github.com/storybookjs/storybook/pull/12347)) + +### Bug Fixes + +- Core: Use denormed params of the first story for initial options ([#11938](https://github.com/storybookjs/storybook/pull/11938)) + +### Maintenance + +- Addon-docs: Light refactor of Source block ([#12268](https://github.com/storybookjs/storybook/pull/12268)) +- Addon-docs: Change 2nd argument of transformSource to the storyContext ([#12265](https://github.com/storybookjs/storybook/pull/12265)) + +### Dependency Upgrades + +- Bump css from 2.2.4 to 3.0.0 ([#12338](https://github.com/storybookjs/storybook/pull/12338)) + +## 6.1.0-alpha.2 (September 3, 2020) + +Failed NPM publish + +## 6.1.0-alpha.1 (August 31, 2020) + +### Features + +- Components: Add additionalActions prop to Preview block ([#12274](https://github.com/storybookjs/storybook/pull/12274)) + +### Maintenance + +- Addon-docs: Add transformSource for jsxDecorator, deprecated onBeforeRender ([#12178](https://github.com/storybookjs/storybook/pull/12178)) + +### Dependency Upgrades + +- Update the axe version in addon-a11y to 4.0 ([#12150](https://github.com/storybookjs/storybook/pull/12150)) +- Upgrade react-popper-tooltip to 3.1.0 ([#11827](https://github.com/storybookjs/storybook/pull/11827)) + +## 6.1.0-alpha.0 (August 31, 2020) + +### Features + +- SyntaxHighlighter: Put formatted code to clipboard ([#11276](https://github.com/storybookjs/storybook/pull/11276)) +- Addon-docs: Add inline rendering for svelte ([#12313](https://github.com/storybookjs/storybook/pull/12313)) + +### Bug Fixes + +- UI: Remove scrolling attribute from iFrame ([#12223](https://github.com/storybookjs/storybook/pull/12223)) + +### Maintenance + +- CLI: Add HTML components and stories ([#12286](https://github.com/storybookjs/storybook/pull/12286)) +- Vue: Add basic CSF types ([#12037](https://github.com/storybookjs/storybook/pull/12037)) +- CLI: Add Aurelia detection ([#12181](https://github.com/storybookjs/storybook/pull/12181)) +- Storyshots: Remove needless iteration testStorySnapshots ([#12321](https://github.com/storybookjs/storybook/pull/12321)) + +### Dependency Upgrades + +- Bump react-syntax-highlighter to 13.2.1 ([#11838](https://github.com/storybookjs/storybook/pull/11838)) + +## 6.0.21 (August 31, 2020) + +### Bug Fixes + +- Addon-controls: Fix uncontrolled to controlled warning for booleans ([#12322](https://github.com/storybookjs/storybook/pull/12322)) + +### Maintenance + +- Build: Add CRA benchmark ([#12209](https://github.com/storybookjs/storybook/pull/12209)) + +## 6.0.20 (August 28, 2020) + +### Bug Fixes + +- ArgsTable: Fix union type splitting ([#11868](https://github.com/storybookjs/storybook/pull/11868)) +- CLI: Fix import of Button in react mdx template ([#12252](https://github.com/storybookjs/storybook/pull/12252)) + +## 5.3.21 (August 28, 2020) + +### Bug Fixes + +- Core: Add skip dispose option to ClientApi ([#9868](https://github.com/storybookjs/storybook/pull/9868)) + +## 6.0.19 (August 27, 2020) + +### Bug Fixes + +- UI: Fix eject and copy URLs for composition ([#12233](https://github.com/storybookjs/storybook/pull/12233)) + +## 5.3.20 (August 27, 2020) + +### Bug Fixes + +- React-native server: Fix addon tabs ([#10468](https://github.com/storybookjs/storybook/pull/10468)) +- Addon-docs: Fix babel JSX handling in MDX ([#11448](https://github.com/storybookjs/storybook/pull/11448)) +- Revert "Fix: Search stories" ([#10916](https://github.com/storybookjs/storybook/pull/10916)) + +## 6.0.18 (August 26, 2020) + +### Bug Fixes + +- UI: Fix `disable` parameter to hide addon panel ([#12171](https://github.com/storybookjs/storybook/pull/12171)) +- Addon-controls: Fix controls from args ([#12230](https://github.com/storybookjs/storybook/pull/12230)) + +### Dependency Upgrades + +- Mithril: Add Mithril v2.0.0 to peer dependencies ([#12229](https://github.com/storybookjs/storybook/pull/12229)) + +## 6.0.17 (August 25, 2020) + +### Bug Fixes + +- Addon-essentials: Log info on config override ([#12211](https://github.com/storybookjs/storybook/pull/12211)) + +### Maintenance + +- Build: Remove outdated CLI tests ([#12207](https://github.com/storybookjs/storybook/pull/12207)) + +### Dependency Upgrades + +- Source-loader: Pin prettier to 2.0.x version ([#12226](https://github.com/storybookjs/storybook/pull/12226)) + +## 6.0.16 (August 20, 2020) + +### Bug Fixes + +- Addon-docs: Fix Vue ArgsTable sanitizing of item.type.elements to item.type.value ([#12165](https://github.com/storybookjs/storybook/pull/12165)) + +## 6.0.15 (August 20, 2020) + +### Bug Fixes + +- Addon-docs: Fix ArgsTable union type handling in Vue/TS ([#12158](https://github.com/storybookjs/storybook/pull/12158)) +- Addon-docs: Fix inline rendering for DOM nodes in HTML ([#12164](https://github.com/storybookjs/storybook/pull/12164)) + +### Maintenance + +- React: Simplify component type for CSF typing ([#12110](https://github.com/storybookjs/storybook/pull/12110)) + +## 6.0.14 (August 20, 2020) + +### Bug Fixes + +- Addon-docs: Fix MDX IDs from CSF imports ([#12154](https://github.com/storybookjs/storybook/pull/12154)) +- Addon-viewport: Add preset to fix windows import ([#12148](https://github.com/storybookjs/storybook/pull/12148)) +- Composition: Verify refs in node ([#12085](https://github.com/storybookjs/storybook/pull/12085)) + +### Maintenance + +- Build: Update and optimize circleCI Config ([#12118](https://github.com/storybookjs/storybook/pull/12118)) + +## 6.0.13 (August 19, 2020) + +### Bug Fixes + +- Source-loader: Fix default exports of type TSAsExpression ([#12099](https://github.com/storybookjs/storybook/pull/12099)) +- Addon-docs: Fix source code for Template.bind({}) in MDX ([#12107](https://github.com/storybookjs/storybook/pull/12107)) +- Addon-A11y: Fix manual run & timeline ([#12003](https://github.com/storybookjs/storybook/pull/12003)) +- Core: Add frameworkPath to options to support custom frameworks ([#12087](https://github.com/storybookjs/storybook/pull/12087)) + +## 6.0.12 (August 17, 2020) + +### Bug Fixes + +- Angular: Make CLI templates compatible with TS strict mode ([#12081](https://github.com/storybookjs/storybook/pull/12081)) +- React: Fix CSF component typing ([#12072](https://github.com/storybookjs/storybook/pull/12072)) +- ArgsTable: Fix styles to allow long text to wrap ([#11818](https://github.com/storybookjs/storybook/pull/11818)) +- Addon-docs: Fix main check for absolute config dirs ([#12057](https://github.com/storybookjs/storybook/pull/12057)) + +## 6.0.11 (August 17, 2020) + +NPM publish failed + +## 6.0.10 (August 15, 2020) + +### Bug Fixes + +- Addon-controls: Fix argType inference priority ([#12048](https://github.com/storybookjs/storybook/pull/12048)) + +## 6.0.9 (August 15, 2020) + +### Bug Fixes + +- Addon-docs: Fix CSF names importing in MDX ([#12044](https://github.com/storybookjs/storybook/pull/12044)) + +### Maintenance + +- ArgsTable: Error when subcomponents is an array ([#12033](https://github.com/storybookjs/storybook/pull/12033)) + +## 6.0.8 (August 15, 2020) + +Unpublished + +## 6.0.7 (August 14, 2020) + +### Bug Fixes + +- Addon-docs: Fix extractArgTypes for unknown component ([#12012](https://github.com/storybookjs/storybook/pull/12012)) + +### Maintenance + +- UI:pdate upgrade command in about section ([#11934](https://github.com/storybookjs/storybook/pull/11934)) +- Build: Remove documentation scripts and fix README ([#12015](https://github.com/storybookjs/storybook/pull/12015)) + +### Dependency Upgrades + +- Bump jest-specific-snapshot to v4 ([#11939](https://github.com/storybookjs/storybook/pull/11939)) + +## 6.0.6 (August 14, 2020) + +### Bug Fixes + +- CLI: Fix upgrade to warn when no packages found ([#11993](https://github.com/storybookjs/storybook/pull/11993)) +- Addon-docs: Fix blocks type export ([#11987](https://github.com/storybookjs/storybook/pull/11987)) +- CLI: Fix RN link ([#11973](https://github.com/storybookjs/storybook/pull/11973)) + +## 6.0.5 (August 13, 2020) + +### Bug Fixes + +- CLI: Fix welcome links on Introduction MDX ([#11949](https://github.com/storybookjs/storybook/pull/11949)) + +## 6.0.4 (August 12, 2020) + +### Bug Fixes + +- Source-loader: Fix `.add` detection ([#11920](https://github.com/storybookjs/storybook/pull/11920)) + +## 6.0.3 (August 12, 2020) + +### Bug Fixes + +- Essentials: Fix missing toolbars addon ([#11910](https://github.com/storybookjs/storybook/pull/11910)) + +## 6.0.2 (August 11, 2020) + +### Bug Fixes + +- CLI: Fix csf-hoist-story-annotations codemod for variable default exports ([#11895](https://github.com/storybookjs/storybook/pull/11895)) + +## 6.0.1 (August 11, 2020) + +### Bug Fixes + +- Core: Fix support for main.ts/preview.ts files ([#11885](https://github.com/storybookjs/storybook/pull/11885)) +- Addon-docs: Fix ArgsTable regression ([#11889](https://github.com/storybookjs/storybook/pull/11889)) + +## 6.0.0 (August 10, 2020) + +Storybook 6.0 is here! + +- 💎 [Essentials: Zero-configuration setup](https://medium.com/storybookjs/zero-config-storybook-66e7c4798e5d) +- 🧬 [Args: Next-generation, dynamic story format](https://medium.com/storybookjs/introducing-storybook-args-2dadcdb777cc) +- 🎛 [Controls: Live edit component examples](https://medium.com/storybookjs/storybook-controls-ce82af93e430) +- 🌐 [Composition: Combine multiple Storybooks](https://medium.com/storybookjs/storybook-composition-af0da9084fba) +- 📚 [Documentation: Complete project overhaul](https://storybook.js.org/docs/react/get-started/introduction) + + 6.0 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `6.0.0-alpha.*`, `6.0.0-beta.*`, and `6.0.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.3` or earlier. + +## 6.0.0-rc.30 (August 10, 2020) + +### Bug Fixes + +- Storyshots: Don't ship typescript files in dist ([#11792](https://github.com/storybookjs/storybook/pull/11792)) + +### Maintenance + +- 6.0 documentation overhaul ([#11861](https://github.com/storybookjs/storybook/pull/11861)) + +## 6.0.0-rc.29 (August 7, 2020) + +### Features + +- CLI: Add CSF types for Angular ([#11825](https://github.com/storybookjs/storybook/pull/11825)) + +### Bug Fixes + +- Core: Fix serialization of `undefined` ([#11829](https://github.com/storybookjs/storybook/pull/11829)) + +## 6.0.0-rc.28 (August 6, 2020) + +### Bug Fixes + +- CLI: Add CSF typings for react stories ([#11811](https://github.com/storybookjs/storybook/pull/11811)) + +### Dependency Upgrades + +- Upgrade telejson to 5.0.1 ([#11824](https://github.com/storybookjs/storybook/pull/11824)) + +## 6.0.0-rc.27 (August 6, 2020) + +### Features + +- Addon-docs: Prettier, collapsible values in ArgsTable ([#11768](https://github.com/storybookjs/storybook/pull/11768)) +- Addon-docs: Add inline rendering support for HTML ([#11814](https://github.com/storybookjs/storybook/pull/11814)) + +### Bug Fixes + +- Addon-controls: Fix ArgsTable bugs and styling ([#11805](https://github.com/storybookjs/storybook/pull/11805)) +- Addon-docs: Don't zoom docs content ([#11795](https://github.com/storybookjs/storybook/pull/11795)) + +## 6.0.0-rc.26 (August 5, 2020) + +### Dependency Upgrades + +- Perf: Upgrade telejson ([#11797](https://github.com/storybookjs/storybook/pull/11797)) + +## 6.0.0-rc.25 (August 4, 2020) + +### Bug Fixes + +- CSF: Fix mixed `.x` and deprecated `.story.x` parameters ([#11781](https://github.com/storybookjs/storybook/pull/11781)) + +## 6.0.0-rc.24 (August 3, 2020) + +### Bug Fixes + +- Addon-docs: Fix link font size to inherit ([#11770](https://github.com/storybookjs/storybook/pull/11770)) +- Addon-knobs: Fix search params with URI encoding ([#11642](https://github.com/storybookjs/storybook/pull/11642)) +- Core: Add `STORY_SPECIFIED` event for initial selection/URL ([#11766](https://github.com/storybookjs/storybook/pull/11766)) +- Core: Fix handling of initial hashes ([#11767](https://github.com/storybookjs/storybook/pull/11767)) + +### Documentation + +- Core: Fix link to deprecated configure ([#11771](https://github.com/storybookjs/storybook/pull/11771)) + +## 6.0.0-rc.23 (August 3, 2020) + +### Bug Fixes + +- Source-loader: Fix storiesOf missing `__STORY__` variable ([#11765](https://github.com/storybookjs/storybook/pull/11765)) + +## 6.0.0-rc.22 (August 2, 2020) + +### Features + +- Addon-docs: Add `docs.description` parameter ([#11761](https://github.com/storybookjs/storybook/pull/11761)) + +### Bug Fixes + +- Composition: Fix missing version property in autoref ([#11745](https://github.com/storybookjs/storybook/pull/11745)) +- Addon-a11y: Fix inherited parameters ([#11730](https://github.com/storybookjs/storybook/pull/11730)) +- Addon-docs: Fix Ember args ([#11760](https://github.com/storybookjs/storybook/pull/11760)) +- Addon-Docs: Fix Ember extractArgTypes default value ([#10512](https://github.com/storybookjs/storybook/pull/10512)) +- Addon-Docs: Fix Ember extractArgTypes ([#10525](https://github.com/storybookjs/storybook/pull/10525)) + +### Dependency Upgrades + +- Addon-docs: Make vue-docgen optional peer deps ([#11759](https://github.com/storybookjs/storybook/pull/11759)) + +## 6.0.0-rc.21 (August 1, 2020) + +### Features + +- Addon-docs: Add Story.story for CSF stories with MDX docs ([#11752](https://github.com/storybookjs/storybook/pull/11752)) + +### Maintenance + +- Addon-docs: Rename Preview/Props to Canvas/ArgsTable ([#11744](https://github.com/storybookjs/storybook/pull/11744)) + +## 6.0.0-rc.20 (July 31, 2020) + +### Breaking Changes + +- Core: Pass normalized parameters to the story sort function ([#11743](https://github.com/storybookjs/storybook/pull/11743)) + +### Bug Fixes + +- Core: Dedupe argTypes serialization ([#11740](https://github.com/storybookjs/storybook/pull/11740)) + +## 6.0.0-rc.19 (July 31, 2020) + +### Bug Fixes + +- Core: Speed up combineParameters ([#11736](https://github.com/storybookjs/storybook/pull/11736)) +- Addon-docs: Support absolute anchors when deployed at non-root ([#11403](https://github.com/storybookjs/storybook/pull/11403)) + +### Maintenance + +- Args: Add optional scalar test cases for typescript ([#11149](https://github.com/storybookjs/storybook/pull/11149)) + +## 6.0.0-rc.18 (July 30, 2020) + +### Bug Fixes + +- Addon-controls: Fix update logic for argTypes with custom names ([#11704](https://github.com/storybookjs/storybook/pull/11704)) +- Core: Fix HMR ([#11709](https://github.com/storybookjs/storybook/pull/11709)) +- Server: Serialize Object controls as JSON over the wire ([#11703](https://github.com/storybookjs/storybook/pull/11703)) +- Revert #11502: Remove z-index on ActionBar ([#11708](https://github.com/storybookjs/storybook/pull/11708)) +- Revert #11066: Add skip to content on panel and story iframe ([#11718](https://github.com/storybookjs/storybook/pull/11718)) +- UI: Improve treestate performance ([#11725](https://github.com/storybookjs/storybook/pull/11725)) + +## 6.0.0-rc.17 (July 30, 2020) + +Failed NPM publish + +## 6.0.0-rc.16 (July 28, 2020) + +### Features + +- Source-loader: Inject source snippets as story parameters ([#11707](https://github.com/storybookjs/storybook/pull/11707)) +- Source-loader: Handle bind expression stories ([#11710](https://github.com/storybookjs/storybook/pull/11710)) +- UI: Add skip to content on panel and story iframe ([#11066](https://github.com/storybookjs/storybook/pull/11066)) +- Addon-storyshots: Add web-component support ([#11064](https://github.com/storybookjs/storybook/pull/11064)) + +### Bug Fixes + +- Addon-docs: Fix docs render layout to always be 'fullscreen' ([#11699](https://github.com/storybookjs/storybook/pull/11699)) +- UI: Fix release notes on prduction builds ([#11700](https://github.com/storybookjs/storybook/pull/11700)) +- Addon-docs: Apply list styles over reset ([#11281](https://github.com/storybookjs/storybook/pull/11281)) + +### Maintenance + +- CLI: Update rax/mithril/web-components templates, rework Yarn2 E2E ([#11354](https://github.com/storybookjs/storybook/pull/11354)) + +## 6.0.0-rc.15 (July 27, 2020) + +### Features + +- Server: Update to 6.0 APIs and support Controls ([#11680](https://github.com/storybookjs/storybook/pull/11680)) + +### Bug Fixes + +- Addon-Storysource: Fix broken source when there's no story ([#11679](https://github.com/storybookjs/storybook/pull/11679)) +- Angular: Use system path when checking if asset is a directory ([#11472](https://github.com/storybookjs/storybook/pull/11472)) +- Composition: Fix composition of older storybooks ([#11673](https://github.com/storybookjs/storybook/pull/11673)) +- Core: Fix existing behavior with story prefixes ([#11660](https://github.com/storybookjs/storybook/pull/11660)) +- Core: Fix webpack recursion in mainjs glob processing logic ([#11647](https://github.com/storybookjs/storybook/pull/11647)) + +### Maintenance + +- Addon-knobs: Move `@types/react-color` to devDeps ([#11690](https://github.com/storybookjs/storybook/pull/11690)) + +### Dependency Upgrades + +- Bump react-docgen-typescript-plugin to 0.5.2 ([#11658](https://github.com/storybookjs/storybook/pull/11658)) + +## 6.0.0-rc.14 (July 22, 2020) + +### Bug Fixes + +- Addon-docs: Re-enable source-loader by default ([#11650](https://github.com/storybookjs/storybook/pull/11650)) +- Core: Remove duplicate decorators and warn ([#11643](https://github.com/storybookjs/storybook/pull/11643)) +- Storyshots: Fix metadata (parameters/decorators) handling ([#11518](https://github.com/storybookjs/storybook/pull/11518)) +- Addon-docs: Skip dynamic source rendering when not needed ([#11640](https://github.com/storybookjs/storybook/pull/11640)) +- Core: Fix prefix redirect ([#11637](https://github.com/storybookjs/storybook/pull/11637)) + +### Maintenance + +- Core: Log message length on channel message ([#11646](https://github.com/storybookjs/storybook/pull/11646)) + +## 6.0.0-rc.13 (July 21, 2020) + +### Bug Fixes + +- Core: Improve translation of globs for main.js stories ([#11531](https://github.com/storybookjs/storybook/pull/11531)) +- Core: Optimize `storiesHash` by removing unused parameters ([#11624](https://github.com/storybookjs/storybook/pull/11624)) +- Composition: Fix docs-only stories in composed refs ([#11584](https://github.com/storybookjs/storybook/pull/11584)) +- CLI: Generate `docs:json` command dynamically for Angular project ([#11622](https://github.com/storybookjs/storybook/pull/11622)) + +## 6.0.0-rc.12 (July 20, 2020) + +### Bug Fixes + +- Addon-controls: Fix undefined args handling ([#11619](https://github.com/storybookjs/storybook/pull/11619)) +- UI: Fix the color of the menu separator ([#11564](https://github.com/storybookjs/storybook/pull/11564)) +- Storyshots: Don't show `configure` deprecation warning ([#11611](https://github.com/storybookjs/storybook/pull/11611)) +- Addon-docs: Fix Props `components` input ([#11612](https://github.com/storybookjs/storybook/pull/11612)) + +### Maintenance + +- Examples: Remove deprecated hierarchy separators ([#11615](https://github.com/storybookjs/storybook/pull/11615)) +- Hoist CSF `.story` annotations ([#11617](https://github.com/storybookjs/storybook/pull/11617)) + +## 6.0.0-rc.11 (July 19, 2020) + +### Features + +- Addon-controls: Expose `presetColors` for the color control ([#11606](https://github.com/storybookjs/storybook/pull/11606)) + +### Bug Fixes + +- Addon-docs: Fix Vue defaultValue in props table ([#11603](https://github.com/storybookjs/storybook/pull/11603)) + +## 6.0.0-rc.10 (July 18, 2020) + +### Features + +- Addon-docs: Automatic source selection based on story type ([#11601](https://github.com/storybookjs/storybook/pull/11601)) + +## 6.0.0-rc.9 (July 17, 2020) + +### Bug Fixes + +- Addon-controls: Fix no-args warning if argTypes are used ([#11598](https://github.com/storybookjs/storybook/pull/11598)) +- Core: Pass denormalized stories to the sort function ([#11572](https://github.com/storybookjs/storybook/pull/11572)) +- Addon-docs: Fix Vue inline rendering with Args and decorators ([#11594](https://github.com/storybookjs/storybook/pull/11594)) +- Composition: Fix missing refId on getData calls ([#11541](https://github.com/storybookjs/storybook/pull/11541)) +- UI: Fix scrollbars in flexbar ([#11579](https://github.com/storybookjs/storybook/pull/11579)) + +## 6.0.0-rc.8 (July 16, 2020) + +### Features + +- CLI: Component-driven React / Vue / Angular / Preact / Svelte templates ([#11505](https://github.com/storybookjs/storybook/pull/11505)) +- Addon-controls: Add reset UI to ArgsTable ([#11550](https://github.com/storybookjs/storybook/pull/11550)) + +### Bug Fixes + +- Addon-docs: Fix Vue argTypes default values ([#11534](https://github.com/storybookjs/storybook/pull/11534)) + +### Maintenance + +- Core: Move basic argType inference out of addon-docs and into core ([#11561](https://github.com/storybookjs/storybook/pull/11561)) + +## 6.0.0-rc.7 (July 16, 2020) + +NPM publish failed + +## 6.0.0-rc.6 (July 16, 2020) + +NPM publish failed + +## 6.0.0-rc.5 (July 15, 2020) + +### Features + +- Core: Add args reset API ([#11519](https://github.com/storybookjs/storybook/pull/11519)) + +### Bug Fixes + +- Addon-docs: Make Meta block subcomponents optional ([#11556](https://github.com/storybookjs/storybook/pull/11556)) +- UI: Remove z-index on ActionBar ([#11502](https://github.com/storybookjs/storybook/pull/11502)) +- Composition: Fix docs-only story handling for composed storybooks ([#11537](https://github.com/storybookjs/storybook/pull/11537)) +- Addon-Docs: Fix ArgsTable controls on Docs tab ([#11552](https://github.com/storybookjs/storybook/pull/11552)) + +### Maintenance + +- Core: Add `argTypes` to `StoryContext` ([#11558](https://github.com/storybookjs/storybook/pull/11558)) +- CLI: Improve Storybook packages version management ([#11342](https://github.com/storybookjs/storybook/pull/11342)) + +## 6.0.0-rc.4 (July 15, 2020) + +NPM publish failed + +## 6.0.0-rc.3 (July 11, 2020) + +### Bug Fixes + +- Composition: Don't show versions dropdown if there are no versions ([#11497](https://github.com/storybookjs/storybook/pull/11497)) +- Addon-docs: Remove undefined for optional values in Typescript Props ([#11503](https://github.com/storybookjs/storybook/pull/11503)) + +## 6.0.0-rc.2 (July 10, 2020) + +### Bug Fixes + +- UI: Fix menu alignment regression ([#11469](https://github.com/storybookjs/storybook/pull/11469)) +- Composition: Fix syntax on `no-cors` ([#11491](https://github.com/storybookjs/storybook/pull/11491)) +- Addon-docs: Fix MDX handling to ignore babel.config.js ([#11495](https://github.com/storybookjs/storybook/pull/11495)) +- UI: Increase max-height of menu tooltip so scrollbars don't appear ([#11471](https://github.com/storybookjs/storybook/pull/11471)) + +### Maintenance + +- CLI: Add common welcome MDX and cleanup ([#11422](https://github.com/storybookjs/storybook/pull/11422)) +- CSF: Deprecate duplicate titles rather than forbid them ([#11476](https://github.com/storybookjs/storybook/pull/11476)) + +## 6.0.0-rc.1 (July 9, 2020) + +### Bug Fixes + +- Addon-toolbars: Show name if there is no icon ([#11475](https://github.com/storybookjs/storybook/pull/11475)) + +### Maintenance + +- CI: Fix iframe test flake ([#11473](https://github.com/storybookjs/storybook/pull/11473)) +- CI: Deploy the `next` branch of frontpage too ([#11462](https://github.com/storybookjs/storybook/pull/11462)) + +### Dependency Upgrades + +- Bump vue-property-decorator from 8.4.2 to 9.0.0 ([#11241](https://github.com/storybookjs/storybook/pull/11241)) +- Bump @types/react-dom from 16.9.7 to 16.9.8 ([#11191](https://github.com/storybookjs/storybook/pull/11191)) +- Bump jest-image-snapshot from 3.1.0 to 4.0.2 ([#11267](https://github.com/storybookjs/storybook/pull/11267)) +- Bump autoprefixer from 9.8.0 to 9.8.4 ([#11288](https://github.com/storybookjs/storybook/pull/11288)) +- [Security] Bump npm-registry-fetch from 4.0.4 to 4.0.5 ([#11453](https://github.com/storybookjs/storybook/pull/11453)) + +## 6.0.0-rc.0 (July 8, 2020) + +[Storybook 6.0](https://github.com/storybookjs/storybook/issues/9311) is stabilizing! It brings loads of component dev and documentation improvements to your favorite workshop: + +- [Args](https://docs.google.com/document/d/1Mhp1UFRCKCsN8pjlfPdz8ZdisgjNXeMXpXvGoALjxYM/edit#heading=h.6mdg0tp8crgj) - next-gen dynamic component examples + - ([controls](https://github.com/storybookjs/storybook/pull/10834) / [runtime](https://github.com/storybookjs/storybook/pull/10014) / [actions](https://github.com/storybookjs/storybook/pull/10029) / [toolbars](https://github.com/storybookjs/storybook/pull/10028) / [docs controls](https://github.com/storybookjs/storybook/pull/10354)) +- [Composition](https://github.com/storybookjs/storybook/pull/9210) - compose multiple storybooks into one +- [Zero-config Typescript](https://github.com/storybookjs/storybook/pull/10813) - pre-configured for all frameworks, easy customization +- [Yarn 2 support](https://github.com/storybookjs/storybook/issues/9527) - next generation package management + +## 6.0.0-beta.46 (July 8, 2020) + +### Breaking Changes + +- Addon-backgrounds: Zero config defaults ([#11460](https://github.com/storybookjs/storybook/pull/11460)) + +### Features + +- Composition: Add version query to composed ref ([#11447](https://github.com/storybookjs/storybook/pull/11447)) +- UI: Add version release notes ([#11360](https://github.com/storybookjs/storybook/pull/11360)) + +### Maintenance + +- Addon-controls: Add addon-docs check on startup ([#11458](https://github.com/storybookjs/storybook/pull/11458)) +- Addon-docs: Remove deprecated addParameters calls ([#11455](https://github.com/storybookjs/storybook/pull/11455)) +- Composition: Change event source to ref ([#11392](https://github.com/storybookjs/storybook/pull/11392)) +- Fix Yarn 2 compatibility in CLI and Essentials ([#11444](https://github.com/storybookjs/storybook/pull/11444)) + +## 6.0.0-beta.45 (July 7, 2020) + +### Bug Fixes + +- Addon-docs: Fix babel JSX handling in MDX ([#11448](https://github.com/storybookjs/storybook/pull/11448)) + +## 6.0.0-beta.44 (July 6, 2020) + +### Breaking Changes + +- Core: Consistent file paths for locally-defined addons ([#11368](https://github.com/storybookjs/storybook/pull/11368)) + +### Features + +- Composition: Ensure args get sent to the right ref ([#11401](https://github.com/storybookjs/storybook/pull/11401)) +- Core: Deprecate configure and clearDecorators ([#11431](https://github.com/storybookjs/storybook/pull/11431)) + +### Bug Fixes + +- Controls: Fix object control for story switching ([#11432](https://github.com/storybookjs/storybook/pull/11432)) +- Controls: Fix interaction lag & CJK input ([#11430](https://github.com/storybookjs/storybook/pull/11430)) + +## 6.0.0-beta.43 (July 5, 2020) + +### Features + +- Composition: Ignore globals from non-local refs ([#11407](https://github.com/storybookjs/storybook/pull/11407)) + +### Maintenance + +- Core: Remove manager redirects on boot ([#11390](https://github.com/storybookjs/storybook/pull/11390)) + +## 6.0.0-beta.42 (July 5, 2020) + +### Bug Fixes + +- Addon-docs: Fix Props subcomponents regression ([#11420](https://github.com/storybookjs/storybook/pull/11420)) + +### Maintenance + +- Core: Deprecate `addParameters` and `addDecorator` ([#11417](https://github.com/storybookjs/storybook/pull/11417)) + +## 6.0.0-beta.41 (July 4, 2020) + +### Bug Fixes + +- Addon-docs: Fix subcomponents display logic ([#11415](https://github.com/storybookjs/storybook/pull/11415)) +- Addon-docs: Fix Source rendering corner case ([#11414](https://github.com/storybookjs/storybook/pull/11414)) + +## 6.0.0-beta.40 (July 4, 2020) + +### Features + +- CLI: Add upgrade utility with version consistency check ([#11396](https://github.com/storybookjs/storybook/pull/11396)) + +### Maintenance + +- Build: Fix CI breaks ([#11410](https://github.com/storybookjs/storybook/pull/11410)) +- Core: Deprecate immutable options as parameters ([#11387](https://github.com/storybookjs/storybook/pull/11387)) + +## 6.0.0-beta.39 (July 2, 2020) + +### Breaking Changes + +- CSF: Forbid duplicate kinds ([#11369](https://github.com/storybookjs/storybook/pull/11369)) +- Args: Rename `globalArgs` to `globals` ([#11385](https://github.com/storybookjs/storybook/pull/11385)) + +### Features + +- Args: Disable rows and controls in argTypes ([#11388](https://github.com/storybookjs/storybook/pull/11388)) +- Composition: Add auto disable ([#11364](https://github.com/storybookjs/storybook/pull/11364)) +- CLI: use addon-essentials & args ([#11282](https://github.com/storybookjs/storybook/pull/11282)) + +### Maintenance + +- Build: Add artifacts for e2e CI task ([#11365](https://github.com/storybookjs/storybook/pull/11365)) +- Build: Remove cli fixtures in favor of e2e ([#11357](https://github.com/storybookjs/storybook/pull/11357)) + +## 6.0.0-beta.38 (June 30, 2020) + +### Breaking Changes + +- Configuration: Remove hierarchy separators ([#11344](https://github.com/storybookjs/storybook/pull/11344)) + +### Features + +- Addon-docs: Dynamic Source rendering for React ([#11332](https://github.com/storybookjs/storybook/pull/11332)) +- Args: Store global args in session storage ([#11345](https://github.com/storybookjs/storybook/pull/11345)) +- Addon-docs: Add opt-in Markdown transclusion in MDX ([#11334](https://github.com/storybookjs/storybook/pull/11334)) + +### Bug Fixes + +- Core: Fix preset options handling ([#11333](https://github.com/storybookjs/storybook/pull/11333)) +- UI: Revert theming greys flip ([#11297](https://github.com/storybookjs/storybook/pull/11297)) +- Core: Composition QA ([#11224](https://github.com/storybookjs/storybook/pull/11224)) + +### Maintenance + +- Examples: Dual theme rendering ([#11295](https://github.com/storybookjs/storybook/pull/11295)) +- Examples: Recreate stories for sidebaritem ([#11298](https://github.com/storybookjs/storybook/pull/11298)) + +## 6.0.0-beta.37 (June 26, 2020) + +### Breaking Changes + +- Core: Deprecate `setAddon` from `storiesOf` API ([#11322](https://github.com/storybookjs/storybook/pull/11322)) + +### Bug Fixes + +- Revert "Core: Fix source-map strategy for production" ([#11320](https://github.com/storybookjs/storybook/pull/11320)) +- Core: Set viewMode to story when navating from non story pages ([#11317](https://github.com/storybookjs/storybook/pull/11317)) + +### Dependency Upgrades + +- Bump react-textarea-autosize to 8.1.1 ([#11319](https://github.com/storybookjs/storybook/pull/11319)) + +## 6.0.0-beta.36 (June 25, 2020) + +### Features + +- Composition: Allow refs versions in config ([#11294](https://github.com/storybookjs/storybook/pull/11294)) + +### Bug Fixes + +- CLI: Fix docs & essentials version on `sb@next init` ([#11303](https://github.com/storybookjs/storybook/pull/11303)) +- Composition: Fix list of versions missing current version ([#11259](https://github.com/storybookjs/storybook/pull/11259)) +- Composition: Fix undefined/undefined in url on init ([#11293](https://github.com/storybookjs/storybook/pull/11293)) + +### Maintenance + +- Essentials example: Fix typescript error ([#11305](https://github.com/storybookjs/storybook/pull/11305)) + +## 6.0.0-beta.35 (June 24, 2020) + +### Features + +- Essentials: Add addon-controls ([#11285](https://github.com/storybookjs/storybook/pull/11285)) + +### Bug Fixes + +- Addon-docs markdown tables right align support ([#11280](https://github.com/storybookjs/storybook/pull/11280)) + +### Maintenance + +- Addon-docs: Simplify argType inference ([#11284](https://github.com/storybookjs/storybook/pull/11284)) + +## 6.0.0-beta.34 (June 23, 2020) + +### Features + +- Addon-backgrounds: Allow gradients in story preview ([#11265](https://github.com/storybookjs/storybook/pull/11265)) + +### Bug Fixes + +- Core: Fix invalid glob warning for absolute paths ([#11247](https://github.com/storybookjs/storybook/pull/11247)) + +### Maintenance + +- UI: Replace document.execCommand with navigator.clipboard ([#11251](https://github.com/storybookjs/storybook/pull/11251)) +- ArgsTable: Updated Boolean control ([#11263](https://github.com/storybookjs/storybook/pull/11263)) +- Core; Preserve watch output when running dev mode ([#11150](https://github.com/storybookjs/storybook/pull/11150)) + +### Dependency Upgrades + +- Bump lint-staged from 10.2.6 to 10.2.10 ([#11187](https://github.com/storybookjs/storybook/pull/11187)) + +## 6.0.0-beta.33 (June 22, 2020) + +### Features + +- ArgsTable: Add subsections and design cleanup ([#11216](https://github.com/storybookjs/storybook/pull/11216)) +- Core: Improve startup events ([#11080](https://github.com/storybookjs/storybook/pull/11080)) + +### Bug Fixes + +- Preact: Fix Preact 8 compatibility ([#11225](https://github.com/storybookjs/storybook/pull/11225)) + +### Maintenance + +- Core :Add deprecation message for selectedName/Kind urls ([#11111](https://github.com/storybookjs/storybook/pull/11111)) + +## 6.0.0-beta.32 (June 19, 2020) + +### Bug Fixes + +- Addon-knobs: Update select types for undefined, null and boolean ([#11202](https://github.com/storybookjs/storybook/pull/11202)) + +### Maintenance + +- Composition: rename auth url & add tests for modules/refs ([#11215](https://github.com/storybookjs/storybook/pull/11215)) +- Examples: Add design-system to official example ([#11081](https://github.com/storybookjs/storybook/pull/11081)) + +## 6.0.0-beta.31 (June 17, 2020) + +### Bug Fixes + +- React: Fix react-docgen for JS files ([#11217](https://github.com/storybookjs/storybook/pull/11217)) +- React: Load root tsconfig.json into docgen-typescript if none provided ([#11184](https://github.com/storybookjs/storybook/pull/11184)) +- Composition: Remove manual redirects ([#11196](https://github.com/storybookjs/storybook/pull/11196)) + +### Dependency Upgrades + +- Bump react-draggable from 4.4.2 to 4.4.3 ([#11192](https://github.com/storybookjs/storybook/pull/11192)) + +## 6.0.0-beta.30 (June 16, 2020) + +### Features + +- MDX: Support function.bind({}) syntax ([#11198](https://github.com/storybookjs/storybook/pull/11198)) + +### Bug Fixes + +- Addon-docs: Remove render preprocessing for react components w/o docgen ([#11195](https://github.com/storybookjs/storybook/pull/11195)) +- Core: Fix addon load order ([#11178](https://github.com/storybookjs/storybook/pull/11178)) +- Core: Add global box-sizing setting. Fixes #10207 ([#11055](https://github.com/storybookjs/storybook/pull/11055)) + +### Maintenance + +- Addon-controls: Add examples to angular, ember, html, svelte, vue, web-components ([#11197](https://github.com/storybookjs/storybook/pull/11197)) + +## 6.0.0-beta.29 (June 16, 2020) + +### Features + +- Addon-docs: Add syntax highlighting to Code and Description blocks ([#11183](https://github.com/storybookjs/storybook/pull/11183)) + +### Bug Fixes + +- MDX: Don't use root babelrc by default ([#11185](https://github.com/storybookjs/storybook/pull/11185)) +- Addon-docs: Fix 'show source' for stories with dynamic title ([#10959](https://github.com/storybookjs/storybook/pull/10959)) + +## 6.0.0-beta.28 (June 15, 2020) + +### Features + +- Addon-docs: Add argTypes type/control shorthand ([#11174](https://github.com/storybookjs/storybook/pull/11174)) + +### Bug Fixes + +- Core: Remove boxSizing to fix weird CSS layouts ([#11175](https://github.com/storybookjs/storybook/pull/11175)) +- Addon-docs: Fix forwardRef & invalid hook call ([#11154](https://github.com/storybookjs/storybook/pull/11154)) +- Client-API: Add @types/qs typings ([#11162](https://github.com/storybookjs/storybook/pull/11162)) + +### Maintenance + +- CLI: Colocate stories and components, centralize main.js ([#11136](https://github.com/storybookjs/storybook/pull/11136)) +- Build: Exclude stories from collecting coverage ([#11164](https://github.com/storybookjs/storybook/pull/11164)) +- Core: Extend router/utils test set ([#11156](https://github.com/storybookjs/storybook/pull/11156)) + +## 6.0.0-beta.27 (June 14, 2020) + +### Maintenance + +- UI: Remove unused modules ([#11159](https://github.com/storybookjs/storybook/pull/11159)) +- UI: Remove unused & duplicated code ([#11155](https://github.com/storybookjs/storybook/pull/11155)) +- REMOVE unused dependencies && FIX versions ([#11143](https://github.com/storybookjs/storybook/pull/11143)) + +## 6.0.0-beta.26 (June 12, 2020) + +### Bug Fixes + +- Addon-docs: Fix Vue args rendering in Docs mode ([#11138](https://github.com/storybookjs/storybook/pull/11138)) +- Typescript: Fix mandatory typescript dependency ([#11140](https://github.com/storybookjs/storybook/pull/11140)) + +## 6.0.0-beta.25 (June 11, 2020) + +### Bug Fixes + +- Composition: Fix auto refs when there are no specified refs ([#11057](https://github.com/storybookjs/storybook/pull/11057)) + +## 6.0.0-beta.24 (June 11, 2020) + +### Breaking Changes + +- Components: Remove PropsTable, clean ArgsTable stories ([#11105](https://github.com/storybookjs/storybook/pull/11105)) + +### Features + +- React: Switch react-docgen-typescript-loader to react-docgen-typescript-plugin ([#11106](https://github.com/storybookjs/storybook/pull/11106)) +- Vue: Add first-class args support ([#11115](https://github.com/storybookjs/storybook/pull/11115)) +- Core: Add babel plugin for typescript decorators ([#11063](https://github.com/storybookjs/storybook/pull/11063)) +- CLI: Pass --quiet to disable HMR logging in browser console ([#11087](https://github.com/storybookjs/storybook/pull/11087)) +- Addon-knobs: Add number of knobs to tab title ([#11075](https://github.com/storybookjs/storybook/pull/11075)) + +### Bug Fixes + +- Core: Fix package duplication issues by aliasing all storybook packages ([#11092](https://github.com/storybookjs/storybook/pull/11092)) +- hidden) canvas ([#10599](https://github.com/storybookjs/storybook/pull/10599)) +- Core: Fix loglevel filtering ([#11096](https://github.com/storybookjs/storybook/pull/11096)) +- Core: Remove @babel/plugin-transform-react-constant-elements ([#11086](https://github.com/storybookjs/storybook/pull/11086)) +- UI: Fix search in production mode ([#10917](https://github.com/storybookjs/storybook/pull/10917)) + +### Maintenance + +- CLI: Refactor to simplify works with multiple package managers ([#11074](https://github.com/storybookjs/storybook/pull/11074)) + +### Dependency Upgrades + +- chore(deps-dev): bump protractor from 5.4.4 to 7.0.0 ([#10832](https://github.com/storybookjs/storybook/pull/10832)) +- build(deps): [security] bump websocket-extensions from 0.1.3 to 0.1.4 ([#11056](https://github.com/storybookjs/storybook/pull/11056)) +- build(deps): bump @babel/plugin-transform-shorthand-properties from 7.8.3 to 7.10.1 ([#11088](https://github.com/storybookjs/storybook/pull/11088)) +- build(deps-dev): bump tslib from 1.13.0 to 2.0.0 ([#11089](https://github.com/storybookjs/storybook/pull/11089)) +- build(deps-dev): bump @packtracker/webpack-plugin from 2.2.0 to 2.3.0 ([#11091](https://github.com/storybookjs/storybook/pull/11091)) + ## 6.0.0-beta.23 (June 8, 2020) ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b85ab64f15..29e292f506d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -362,6 +362,12 @@ Save and go to `http://localhost:9011` (or wherever storybook is running) If you don't see the changes rerun `yarn storybook` again in your sandbox app +### Documentation + +The documentation for Storybook is served by the [frontpage](https://github.com/storybookjs/frontpage), but the docs files are in this repository. + +To see changes in a development version of the docs, use the "linking" method documented [here](https://github.com/storybookjs/frontpage#docs-content). + ## Release Guide This section is for Storybook maintainers who will be creating releases. It assumes: diff --git a/MIGRATION.md b/MIGRATION.md index 45c40c0b05c..96ae5db1d8f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,21 +1,32 @@

Migration

+- [From version 6.0.x to 6.1.0](#from-version-60x-to-610) + - [6.1 deprecations](#61-deprecations) + - [Deprecated onBeforeRender](#deprecated-onbeforerender) - [From version 5.3.x to 6.0.x](#from-version-53x-to-60x) - [Hoisted CSF annotations](#hoisted-csf-annotations) - [Zero config typescript](#zero-config-typescript) - [Correct globs in main.js](#correct-globs-in-mainjs) - - [Backgrounds addon has a new api](#backgrounds-addon-has-a-new-api) - [CRA preset removed](#cra-preset-removed) + - [Core-JS dependency errors](#core-js-dependency-errors) - [Args passed as first argument to story](#args-passed-as-first-argument-to-story) - [6.0 Docs breaking changes](#60-docs-breaking-changes) - [Remove framework-specific docs presets](#remove-framework-specific-docs-presets) + - [Preview/Props renamed](#previewprops-renamed) - [Docs theme separated](#docs-theme-separated) - [DocsPage slots removed](#docspage-slots-removed) - [React prop tables with Typescript](#react-prop-tables-with-typescript) + - [ConfigureJSX true by default in React](#configurejsx-true-by-default-in-react) + - [User babelrc disabled by default in MDX](#user-babelrc-disabled-by-default-in-mdx) + - [Docs description parameter](#docs-description-parameter) + - [6.0 Inline stories](#60-inline-stories) - [New addon presets](#new-addon-presets) - [Removed babel-preset-vue from Vue preset](#removed-babel-preset-vue-from-vue-preset) - [Removed Deprecated APIs](#removed-deprecated-apis) - [New setStories event](#new-setstories-event) + - [Removed renderCurrentStory event](#removed-rendercurrentstory-event) + - [Removed hierarchy separators](#removed-hierarchy-separators) + - [No longer pass denormalized parameters to storySort](#no-longer-pass-denormalized-parameters-to-storysort) - [Client API changes](#client-api-changes) - [Removed Legacy Story APIs](#removed-legacy-story-apis) - [Can no longer add decorators/parameters after stories](#can-no-longer-add-decoratorsparameters-after-stories) @@ -24,15 +35,24 @@ - [Story Store immutable outside of configuration](#story-store-immutable-outside-of-configuration) - [Improved story source handling](#improved-story-source-handling) - [6.0 Addon API changes](#60-addon-api-changes) + - [Consistent local addon paths in main.js](#consistent-local-addon-paths-in-mainjs) + - [Deprecated setAddon](#deprecated-setaddon) + - [Deprecated disabled parameter](#deprecated-disabled-parameter) - [Actions addon uses parameters](#actions-addon-uses-parameters) - [Removed action decorator APIs](#removed-action-decorator-apis) - [Removed withA11y decorator](#removed-witha11y-decorator) - [Essentials addon disables differently](#essentials-addon-disables-differently) + - [Backgrounds addon has a new api](#backgrounds-addon-has-a-new-api) - [6.0 Deprecations](#60-deprecations) - [Deprecated addon-info, addon-notes](#deprecated-addon-info-addon-notes) - [Deprecated addon-contexts](#deprecated-addon-contexts) - [Removed addon-centered](#removed-addon-centered) - [Deprecated polymer](#deprecated-polymer) + - [Deprecated immutable options parameters](#deprecated-immutable-options-parameters) + - [Deprecated addParameters and addDecorator](#deprecated-addparameters-and-adddecorator) + - [Deprecated clearDecorators](#deprecated-cleardecorators) + - [Deprecated configure](#deprecated-configure) + - [Deprecated support for duplicate kinds](#deprecated-support-for-duplicate-kinds) - [From version 5.2.x to 5.3.x](#from-version-52x-to-53x) - [To main.js configuration](#to-mainjs-configuration) - [Using main.js](#using-mainjs) @@ -112,6 +132,16 @@ - [Packages renaming](#packages-renaming) - [Deprecated embedded addons](#deprecated-embedded-addons) +## From version 6.0.x to 6.1.0 + +### 6.1 deprecations + +#### Deprecated onBeforeRender + +The `@storybook/addon-docs` previously accepted a `jsx` option called `onBeforeRender`, which was unfortunately named as it was called after the render. + +We've renamed it `transformSource` and also allowed it to receive the `StoryContext` in case source rendering requires additional information. + ## From version 5.3.x to 6.0.x ### Hoisted CSF annotations @@ -142,7 +172,7 @@ Basic.decorators = [ ... ]; 2. Similar to React's `displayName`, `propTypes`, `defaultProps` annotations 3. We're introducing a new feature, [Storybook Args](https://docs.google.com/document/d/1Mhp1UFRCKCsN8pjlfPdz8ZdisgjNXeMXpXvGoALjxYM/edit?usp=sharing), where the new syntax will be significantly more ergonomic -To help you upgrade your stories, we've crated a codemod: +To help you upgrade your stories, we've created a codemod: ``` npx @storybook/cli@next migrate csf-hoist-story-annotations --glob="**/*.stories.js" @@ -156,7 +186,7 @@ Storybook has built-in Typescript support in 6.0. That means you should remove y To migrate from an old setup, we recommend deleting any typescript-specific webpack/babel configurations in your project. You should also remove `@storybook/preset-typescript`, which is superceded by the built-in configuration. -If you want to override the defaults, see the [typescript configuration docs](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/configurations/typescript-config/index.md). +If you want to override the defaults, see the [typescript configuration docs](https://storybook.js.org/docs/react/configure/typescript). ### Correct globs in main.js @@ -178,52 +208,31 @@ Example of a **valid** glob: stories: ['./**/*.stories.@(ts|js)'] ``` -### Backgrounds addon has a new api - -Starting in 6.0, the backgrounds addon now receives an object instead of an array as parameter, with a property to define the default background. - -Consider the following example of its usage in `Button.stories.js`: - -```jsx -// Button.stories.js -export default { - title: 'Button', - parameters: { - backgrounds: [ - { name: 'twitter', value: '#00aced', default: true }, - { name: 'facebook', value: '#3b5998' }, - ], - }, -}; -``` - -Here's an updated version of the example, using the new api: - -```jsx -// Button.stories.js -export default { - title: 'Button', - parameters: { - backgrounds: { - default: 'twitter', - values: [ - { name: 'twitter', value: '#00aced' }, - { name: 'facebook', value: '#3b5998' }, - ], - }, - }, -}; -``` - ### CRA preset removed The built-in create-react-app preset, which was [previously deprecated](#create-react-app-preset), has been fully removed. If you're using CRA and migrating from an earlier Storybook version, please install [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app) if you haven't already. +### Core-JS dependency errors + +Some users have experienced `core-js` dependency errors when upgrading to 6.0, such as: + +``` +Module not found: Error: Can't resolve 'core-js/modules/web.dom-collections.iterator' +``` + +We think this comes from having multiple versions of `core-js` installed, but haven't isolated a good solution (see [#11255](https://github.com/storybookjs/storybook/issues/11255) for discussion). + +For now, the workaround is to install `core-js` directly in your project as a dev dependency: + +```sh +npm install core-js@^3.0.1 --save-dev +``` + ### Args passed as first argument to story -Starting in 6.0, the first argument to a story function is an [Args object](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs). In 5.3 and earlier, the first argument was a [StoryContext](https://github.com/storybookjs/storybook/blob/next/lib/addons/src/types.ts#L49-L61), and that context is now passed as the second argument by default. +Starting in 6.0, the first argument to a story function is an [Args object](https://storybook.js.org/docs/react/api/csf#args-story-inputs). In 5.3 and earlier, the first argument was a [StoryContext](https://github.com/storybookjs/storybook/blob/next/lib/addons/src/types.ts#L49-L61), and that context is now passed as the second argument by default. This breaking change only affects you if your stories actually use the context, which is not common. If you have any stories that use the context, you can either (1) update your stories, or (2) set a flag to opt-out of new behavior. @@ -253,6 +262,10 @@ export const parameters = { In SB 5.2, each framework had its own preset, e.g. `@storybook/addon-docs/react/preset`. In 5.3 we [unified this into a single preset](#unified-docs-preset): `@storybook/addon-docs/preset`. In 6.0 we've removed the deprecated preset. +#### Preview/Props renamed + +In 6.0 we renamed `Preview` to `Canvas`, `Props` to `ArgsTable`. The change should be otherwise backwards-compatible. + #### Docs theme separated In 6.0, you should theme Storybook Docs with the `docs.theme` parameter. @@ -265,7 +278,7 @@ In SB5.2, we introduced the concept of [DocsPage slots](https://github.com/story In 5.3, we introduced `docs.x` story parameters like `docs.prepareForInline` which get filled in by frameworks and can also be overwritten by users, which is a more natural/convenient way to make global customizations. -We also introduced introduced [Custom DocsPage](https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/docspage.md#replacing-docspage), which makes it possible to add/remove/update DocBlocks on the page. +We also introduced [Custom DocsPage](https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/docspage.md#replacing-docspage), which makes it possible to add/remove/update DocBlocks on the page. These mechanisms are superior to slots, so we've removed slots in 6.0. For each slot, we provide a migration path here: @@ -286,13 +299,73 @@ Starting in 6.0, we have [zero-config typescript support](#zero-config-typescrip There are also two typescript handling options that can be set in `.storybook/main.js`. `react-docgen-typescript` (default) and `react-docgen`. This is [discussed in detail in the docs](https://github.com/storybookjs/storybook/blob/next/addons/docs/react/README.md#typescript-props-with-react-docgen). +#### ConfigureJSX true by default in React + +In SB 6.0, the Storybook Docs preset option `configureJSX` is now set to `true` for all React projects. It was previously `false` by default for React only in 5.x). This `configureJSX` option adds `@babel/plugin-transform-react-jsx`, to process the output of the MDX compiler, which should be a safe change for all projects. + +If you need to restore the old JSX handling behavior, you can configure `.storybook/main.js`: + +```js +module.exports = { + addons: [ + { + name: '@storybook/addon-docs', + options: { configureJSX: false }, + }, + ], +}; +``` + +#### User babelrc disabled by default in MDX + +In SB 6.0, the Storybook Docs no longer applies the user's babelrc by default when processing MDX files. It caused lots of hard-to-diagnose bugs. + +To restore the old behavior, or pass any MDX-specific babel options, you can configure `.storybook/main.js`: + +```js +module.exports = { + addons: [ + { + name: '@storybook/addon-docs', + options: { mdxBabelOptions: { babelrc: true, configFile: true } }, + }, + ], +}; +``` + +#### Docs description parameter + +In 6.0, you can customize a component description using the `docs.description.component` parameter, and a story description using `docs.description.story` parameter. + +Example: + +```js +import { Button } from './Button'; + +export default { + title: 'Button' + parameters: { docs: { description: { component: 'some component **markdown**' }}} +} + +export const Basic = () => +); + +export const WithParams = () => ; +WithParams.parameters = { foo: 'bar' } + +export const WithDocsParams = () => ; +WithDocsParams.parameters = { docs: { iframeHeight: 200 } }; + +export const WithStorySourceParams = () => ; +WithStorySourceParams.parameters = { storySource: { source: 'foo' } }; + +const Template = (args: Args) => ; +``` + +## Multiple actions + +If your story requires multiple actions, it may be convenient to use `actions` to create many at once: + +```js +import { actions } from '@storybook/addon-actions'; +import Button from './button'; + +export default { + title: 'Button', + component: Button, +}; + +// This will lead to { onClick: action('onClick'), ... } +const eventsFromNames = actions('onClick', 'onMouseOver'); + +// This will lead to { onClick: action('clicked'), ... } +const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' }); + +export const first = () => ; + +export const second = () => ; +``` + +## Configuration + +Arguments which are passed to the action call will have to be serialized while be "transferred" over the channel. + +This is not very optimal and can cause lag when large objects are being logged, for this reason it is possible to configure a maximum depth. + +The action logger, by default, will log all actions fired during the lifetime of the story. After a while this can make the storybook laggy. As a workaround, you can configure an upper limit to how many actions should be logged. + +To apply the configuration globally use the `configureActions` function in your `preview.js` file. + +```js +import { configureActions } from '@storybook/addon-actions'; + +configureActions({ + depth: 100, + // Limit the number of items logged into the actions panel + limit: 20, +}); +``` + +To apply the configuration per action use: + +```js +action('my-action', { + depth: 5, +}); +``` + +### Available Options + +| Name | Type | Description | Default | +| -------------------- | ------- | ----------------------------------------------------------------------------------- | ------- | +| `depth` | Number | Configures the transferred depth of any logged objects. | `10` | +| `clearOnStoryChange` | Boolean | Flag whether to clear the action logger when switching away from the current story. | `true` | +| `limit` | Number | Limits the number of items logged in the action logger | `50` | diff --git a/addons/actions/README.md b/addons/actions/README.md index ea170685383..e01d797eba2 100644 --- a/addons/actions/README.md +++ b/addons/actions/README.md @@ -2,19 +2,19 @@ Storybook Addon Actions can be used to display data received by event handlers in [Storybook](https://storybook.js.org). -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) +[Framework Support](https://storybook.js.org/docs/react/api/frameworks-feature-support) ![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/actions/docs/screenshot.png) -## Getting Started +## Installation -Install: +Actions is part of [essentials](https://storybook.js.org/docs/react/essentials/introduction) and so is installed in all new Storybooks by default. If you need to add it to your Storybook, you can run: ```sh npm i -D @storybook/addon-actions ``` -Then, add following content to `.storybook/main.js` +Then, add following content to [`.storybook/main.js`](https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project): ```js module.exports = { @@ -22,132 +22,6 @@ module.exports = { }; ``` -## Actions args +## Usage -Starting in SB6.0, we recommend using story parameters to specify actions which get passed into your story as [Args](https://docs.google.com/document/d/1Mhp1UFRCKCsN8pjlfPdz8ZdisgjNXeMXpXvGoALjxYM/edit?usp=sharing) (passed as the first argument when `passArgsFirst` is set to `true`). - -The first option is to specify `argTypes` for your story with an `action` field. Take the following example: - -```js -import Button from './button'; - -export default { - title: 'Button', - argTypes: { onClick: { action: 'clicked' } }, -}; - -export const Basic = ({ onClick }) => ; -``` - -Alternatively, suppose you have a naming convention, like `onX` for event handlers. The following configuration automatically creates actions for each `onX` `argType` (which you can either specify manually or generate automatically using [Storybook Docs](https://www.npmjs.com/package/@storybook/addon-docs). - -```js -import Button from './button'; - -export default { - title: 'Button', - component: Button, - parameters: { actions: { argTypesRegex: '^on.*' } }, -}; - -export const Basic = ({ onClick }) => ; -``` - -> **NOTE:** If you're generating `argTypes` in using another addon (like Docs, which is the common behavior) you'll need to make sure that the actions addon loads **AFTER** the other addon. You can do this by listing it later in the `addons` registration code in `.storybook/main.js`. - -## Manually-specified actions - -Import the `action` function and use it to create actions handlers. When creating action handlers, provide a **name** to make it easier to identify. - -> _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybookjs/storybook-addon-actions/issues/29#issuecomment-288274794)_ - -```js -import { action } from '@storybook/addon-actions'; -import Button from './button'; - -export default { - title: 'Button', - component: Button, -}; - -export const defaultView = () => ; -``` - -## Multiple actions - -If your story requires multiple actions, it may be convenient to use `actions` to create many at once: - -```js -import { actions } from '@storybook/addon-actions'; -import Button from './button'; - -export default { - title: 'Button', - component: Button, -}; - -// This will lead to { onClick: action('onClick'), ... } -const eventsFromNames = actions('onClick', 'onMouseOver'); - -// This will lead to { onClick: action('clicked'), ... } -const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' }); - -export const first = () => ; - -export const second = () => ; -``` - -## Configuration - -Arguments which are passed to the action call will have to be serialized while be "transferred" over the channel. - -This is not very optimal and can cause lag when large objects are being logged, for this reason it is possible to configure a maximum depth. - -The action logger, by default, will log all actions fired during the lifetime of the story. After a while this can make the storybook laggy. As a workaround, you can configure an upper limit to how many actions should be logged. - -To apply the configuration globally use the `configureActions` function in your `preview.js` file. - -```js -import { configureActions } from '@storybook/addon-actions'; - -configureActions({ - depth: 100, - // Limit the number of items logged into the actions panel - limit: 20, -}); -``` - -To apply the configuration per action use: - -```js -action('my-action', { - depth: 5, -}); -``` - -### Available Options - -| Name | Type | Description | Default | -| -------------------- | ------- | ----------------------------------------------------------------------------------- | ------- | -| `depth` | Number | Configures the transferred depth of any logged objects. | `10` | -| `clearOnStoryChange` | Boolean | Flag whether to clear the action logger when switching away from the current story. | `true` | -| `limit` | Number | Limits the number of items logged in the action logger | `50` | - -## Declarative Configuration via Parameters - -You can define action handles in a declarative way using parameters. They accepts the same arguments as [`actions`](#multiple-actions) -Keys have `' '` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`. - -```js -import Button from './button'; - -export default { - title: 'Button', - parameters: { - actions: { - handles: ['mouseover', 'click .btn'] - } -}; - -export const first = () => ; -``` +The basic usage is documented in the [documentation](https://storybook.js.org/docs/react/essentials/actions). For legacy usage, see the [advanced README](./ADVANCED.md). diff --git a/addons/actions/package.json b/addons/actions/package.json index 0c391303fa3..d5cc89a4af0 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Action Logger addon for storybook", "keywords": [ "storybook" @@ -22,18 +22,18 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "fast-deep-equal": "^3.1.1", "global": "^4.3.2", @@ -41,6 +41,7 @@ "polished": "^3.4.4", "prop-types": "^15.7.2", "react": "^16.8.3", + "react-dom": "^16.8.3", "react-inspector": "^5.0.1", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1", @@ -52,17 +53,14 @@ "@types/uuid": "^7.0.3", "@types/webpack-env": "^1.15.2" }, - "peerDependencies": { - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/actions/src/containers/ActionLogger/index.tsx b/addons/actions/src/containers/ActionLogger/index.tsx index 4caa1b54b35..2de6c68f9ad 100644 --- a/addons/actions/src/containers/ActionLogger/index.tsx +++ b/addons/actions/src/containers/ActionLogger/index.tsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import deepEqual from 'fast-deep-equal'; import { API } from '@storybook/api'; -import { STORY_RENDERED } from '@storybook/core-events'; +import { STORY_CHANGED } from '@storybook/core-events'; import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger'; import { EVENT_ID } from '../..'; @@ -39,14 +39,14 @@ export default class ActionLogger extends Component ; -``` - -You can add the backgrounds to all stories by using `parameters` in `.storybook/preview.js`: - -```js -export const parameters = { - backgrounds: { - default: 'twitter', - values: [ - { name: 'twitter', value: '#00aced' }, - { name: 'facebook', value: '#3b5998' }, - ], - }, -}; -``` - -If you want to override backgrounds for a single story or group of stories, pass the `backgrounds` parameter: - -```jsx -import React from 'react'; - -export default { - title: 'Button', -}; - -export const defaultView = () => ; - -defaultView.parameters = { - backgrounds: { - default: 'red', - values: [{ name: 'red', value: 'rgba(255, 0, 0)' }], - }, -}; -``` - -Once you have defined backgrounds for your stories (as can be seen in the examples above), you can set a default background per story by passing the `default` property using a name from the available backgrounds: - -```jsx -import React from 'react'; - -/* - * Button.stories.js - * Applies default background to the Stories - */ -export default { - title: 'Button', - parameters: { - backgrounds: { default: 'twitter' }, - }, -}; - -export const twitterColorSelected = () => ; -``` - -If you don't want to use backgrounds for a story, you can set the `backgrounds` parameter to `{ disable: true }` to skip the addon: - -```jsx -import React from 'react'; - -/* - * Button.stories.js - * Disables backgrounds for one Story - */ -export default { - title: 'Button', -}; - -export const disabledBackgrounds = () => ; - -disabledBackgrounds.parameters = { - backgrounds: { disable: true }, -}; -``` +The usage is documented in the [documentation](https://storybook.js.org/docs/react/essentials/backgrounds). diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index c53b86b5b83..cebc0ac6e65 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "A storybook addon to show different backgrounds for your preview", "keywords": [ "addon", @@ -26,37 +26,35 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "memoizerific": "^1.11.3", "react": "^16.8.3", + "react-dom": "^16.8.3", "regenerator-runtime": "^0.13.3" }, "devDependencies": { "@types/webpack-env": "^1.15.2" }, - "peerDependencies": { - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/backgrounds/preset.js b/addons/backgrounds/preset.js new file mode 100644 index 00000000000..a83f95279e7 --- /dev/null +++ b/addons/backgrounds/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/backgrounds/src/containers/BackgroundSelector.tsx b/addons/backgrounds/src/containers/BackgroundSelector.tsx index 69cc0ede873..7532a4c184b 100644 --- a/addons/backgrounds/src/containers/BackgroundSelector.tsx +++ b/addons/backgrounds/src/containers/BackgroundSelector.tsx @@ -178,7 +178,7 @@ export class BackgroundSelector extends Component { ({ [`#${iframeId}`]: { - backgroundColor: + background: selectedBackgroundColor === 'transparent' ? theme.background.content : selectedBackgroundColor, diff --git a/addons/backgrounds/src/preset/defaultParameters.tsx b/addons/backgrounds/src/preset/defaultParameters.tsx new file mode 100644 index 00000000000..0abaae233b2 --- /dev/null +++ b/addons/backgrounds/src/preset/defaultParameters.tsx @@ -0,0 +1,8 @@ +export const parameters = { + backgrounds: { + values: [ + { name: 'light', value: '#F8F8F8' }, + { name: 'dark', value: '#333333' }, + ], + }, +}; diff --git a/addons/backgrounds/src/preset/index.ts b/addons/backgrounds/src/preset/index.ts new file mode 100644 index 00000000000..5575756415e --- /dev/null +++ b/addons/backgrounds/src/preset/index.ts @@ -0,0 +1,7 @@ +export function config(entry: any[] = []) { + return [...entry, require.resolve('./defaultParameters')]; +} + +export function managerEntries(entry: any[] = [], options: any) { + return [...entry, require.resolve('../register')]; +} diff --git a/addons/controls/README.md b/addons/controls/README.md index 1fdef536f64..a09068e0471 100644 --- a/addons/controls/README.md +++ b/addons/controls/README.md @@ -1,411 +1,39 @@ -
- -
+# Storybook Controls Addon -

Storybook Controls

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

Contents

- -- [Installation](#installation) -- [Writing stories](#writing-stories) - - [Getting started](#getting-started) - - [Auto-generated args](#auto-generated-args) - - [Custom controls args](#custom-controls-args) - - [Fully custom args](#fully-custom-args) - - [Template stories](#template-stories) -- [Configuration](#configuration) - - [Control annotations](#control-annotations) - - [Parameters](#parameters) - - [Expanded: show property documentation](#expanded-show-property-documentation) -- [Framework support](#framework-support) -- [FAQs](#faqs) - - [How will this replace addon-knobs?](#how-will-this-replace-addon-knobs) - - [How do I migrate from addon-knobs?](#how-do-i-migrate-from-addon-knobs) +![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/next/addons/controls/docs/media/addon-controls-hero.gif) ## Installation -Controls requires [Storybook Docs](https://github.com/storybookjs/storybook/tree/next/addons/docs). If you're not using it already, please install that first. - -Next, install the package: +Controls is part of [essentials](https://storybook.js.org/docs/react/essentials/introduction) and so is installed in all new Storybooks by default. If you need to add it to your Storybook, you can run: ```sh -npm install @storybook/addon-controls -D # or yarn +npm i -D @storybook/addon-controls ``` -And add it to your `.storybook/main.js` config: +Then, add following content to [`.storybook/main.js`](https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project): ```js module.exports = { - addons: [ - '@storybook/addon-docs' - '@storybook/addon-controls' - ], + addons: ['@storybook/addon-controls'], }; ``` -## Writing stories +## Usage -Let's see how to write stories that automatically generate controls based on your component properties. - -Controls is built on [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs), which is a small, backwards-compatible change to Storybook's [Component Story Format](https://medium.com/storybookjs/component-story-format-66f4c32366df). - -This section is a step-by-step walkthrough for how to upgrade your stories. It takes you from a starting point of the traditional "no args" stories, to auto-generated args, to auto-generated args with custom controls, to fully custom args if you need them. - -### Getting started - -Let's start with the following component/story combination, which should look familiar if you're coming from an older version of Storybook. - -```tsx -import React from 'react'; -interface ButtonProps { - /** The main label of the button */ - label?: string; -} -export const Button = ({ label = 'FIXME' }: ButtonProps) => ; -``` - -And here's a story that shows that Button component: - -```jsx -import React from 'react'; -import { Button } from './Button'; -export default { title: 'Button', component: Button }; - -export const Basic = () => -); -``` - -And the slightly expanded story: - -```jsx -export const Basic = (args) => ` - } -); -Reflow.args = { count: 3, label: 'reflow' }; -``` - -### Template stories - -Suppose you've created the `Basic` story from above, but now we want to create a second story with a different state, such as how the button renders with the label is really long. - -The simplest thing would be to create a second story: - -```jsx -export const VeryLongLabel = (args) => ; basic.parameters = { @@ -60,7 +62,7 @@ basic.parameters = { ```md import { Meta, Story } from '@storybook/addon-docs/blocks'; -import \* as stories from './Button.stories.js'; +import * as stories from './Button.stories.js'; import { SomeComponent } from 'path/to/SomeComponent'; @@ -69,7 +71,7 @@ import { SomeComponent } from 'path/to/SomeComponent'; I can define a story with the function imported from CSF: -{stories.basic()} + And I can also embed arbitrary markdown & JSX in this file. @@ -79,7 +81,8 @@ And I can also embed arbitrary markdown & JSX in this file. What's happening here: - Your stories are defined in CSF, but because of `includeStories: []`, they are not actually added to Storybook. -- The MDX file is simply importing stories as functions in the MDX, and other aspects of the CSF file, such as decorators, parameters, and any other metadata should be applied as needed in the MDX from the import. +- The named story exports are annotated with story-level decorators, parameters, args, and the `` construct respects this. +- All component-level decorators, parameters, etc. from `Button.stories` default export must be manually copied over into `` if desired. ## CSF Stories with arbitrary MDX @@ -226,13 +229,26 @@ Foo.parameters = { }; ``` -This can also be applied globally in `preview.js`: +This can also be applied globally in `.storybook/preview.js`: ```js // always reset the view mode to "docs" whenever the user navigates -addParameters({ +export const parameters = { viewMode: 'docs', -}); +}; +``` + +## Reordering Docs tab first + +You can configure Storybook's preview tabs with the `previewTabs` story parameter. + +Here's how to show the `Docs` tab first for a story (or globally in `.storybook/preview.js`): + +```js +export const Foo = () => ; +Foo.parameters = { + previewTabs: { 'storybook/docs/panel': { index: -1 } }, +}; ``` ## Customizing source snippets @@ -254,7 +270,7 @@ Alternatively, you can provide a function in the `docs.transformSource` paramete const SOURCE_REGEX = /^\(\) => `(.*)`$/; export const parameters = { docs: { - transformSource: (src, storyId) => { + transformSource: (src, storyContext) => { const match = SOURCE_REGEX.exec(src); return match ? match[1] : src; }, @@ -268,7 +284,7 @@ These two methods are complementary. The former is useful for story-specific, an What happens if you want to add some wrapper for your MDX page, or add some other kind of React context? -When you're writing stories you can do this by adding a [decorator](https://storybook.js.org/docs/basics/writing-stories/#decorators), but when you're adding arbitrary JSX to your MDX documentation outside of a `` block, decorators no longer apply, and you need to use the `docs.container` parameter. +When you're writing stories you can do this by adding a [decorator](https://storybook.js.org/docs/react/writing-stories/decorators), but when you're adding arbitrary JSX to your MDX documentation outside of a `` block, decorators no longer apply, and you need to use the `docs.container` parameter. The closest Docs equivalent of a decorator is the `container`, a wrapper element that is rendered around the page that is being rendered. Here's an example of adding a solid red border around the page. It uses Storybook's default page container (that sets up various contexts and other magic) and then inserts its own logic between that container and the contents of the page: diff --git a/addons/docs/docs/theming.md b/addons/docs/docs/theming.md index c691726d8e2..58424e64728 100644 --- a/addons/docs/docs/theming.md +++ b/addons/docs/docs/theming.md @@ -9,7 +9,7 @@ ## Storybook theming -Storybook theming is the **recommended way** to theme your docs. Docs uses the same theme system as [Storybook UI](https://storybook.js.org/docs/configurations/theming/), but is themed independently from the main UI. +Storybook theming is the **recommended way** to theme your docs. Docs uses the same theme system as [Storybook UI](https://storybook.js.org/docs/react/configure/theming), but is themed independently from the main UI. Supposing you have a Storybook theme defined for the main UI in `.storybook/manager.js`: diff --git a/addons/docs/ember/README.md b/addons/docs/ember/README.md index ad49342a228..b5fbcc5ffda 100644 --- a/addons/docs/ember/README.md +++ b/addons/docs/ember/README.md @@ -95,7 +95,7 @@ module.exports = { Finally, you can create MDX files like this: ```md -import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; +import { Meta, Story, ArgsTable } from '@storybook/addon-docs/blocks'; import { hbs } from 'ember-cli-htmlbars'; @@ -109,9 +109,9 @@ Some **markdown** description, or whatever you want. context: { title: "Title" }, }} -## Props +## ArgsTable - + ``` Yes, it's redundant to declare `component` twice. [Coming soon](https://github.com/storybookjs/storybook/issues/8673). diff --git a/addons/docs/package.json b/addons/docs/package.json index eb80367f61b..8bc86111b8c 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Superior documentation for your components", "keywords": [ "addon", @@ -32,61 +32,63 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "createDlls": "node -r esm ./scripts/createDlls.js", "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/generator": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@babel/preset-env": "^7.9.6", - "@egoist/vue-to-react": "^1.1.0", + "@babel/generator": "^7.11.5", + "@babel/parser": "^7.11.5", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/preset-env": "^7.11.5", "@jest/transform": "^26.0.0", "@mdx-js/loader": "^1.5.1", "@mdx-js/mdx": "^1.5.1", "@mdx-js/react": "^1.5.1", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", "@storybook/csf": "0.0.1", - "@storybook/node-logger": "6.0.0-beta.23", - "@storybook/postinstall": "6.0.0-beta.23", - "@storybook/source-loader": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/node-logger": "6.1.0-alpha.14", + "@storybook/postinstall": "6.1.0-alpha.14", + "@storybook/source-loader": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "acorn": "^7.1.0", "acorn-jsx": "^5.1.0", "acorn-walk": "^7.0.0", "core-js": "^3.0.1", "doctrine": "^3.0.0", "escodegen": "^1.12.0", + "fast-deep-equal": "^3.1.1", "global": "^4.3.2", "html-tags": "^3.1.0", "js-string-escape": "^1.0.1", "lodash": "^4.17.15", "prop-types": "^15.7.2", - "react-element-to-jsx-string": "^14.1.0", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "react-element-to-jsx-string": "^14.3.1", + "react-is": "^16.12.0", "regenerator-runtime": "^0.13.3", "remark-external-links": "^6.0.0", "remark-slug": "^6.0.0", "ts-dedent": "^1.1.1", - "util-deprecate": "^1.0.2", - "vue-docgen-api": "^4.7.0", - "vue-docgen-loader": "^1.4.0" + "util-deprecate": "^1.0.2" }, "devDependencies": { "@angular/core": "^9.1.0", - "@babel/core": "^7.9.6", + "@babel/core": "^7.11.5", "@emotion/core": "^10.0.20", "@emotion/styled": "^10.0.17", - "@storybook/react": "6.0.0-beta.23", - "@storybook/web-components": "6.0.0-beta.23", + "@storybook/react": "6.1.0-alpha.14", + "@storybook/vue": "6.1.0-alpha.14", + "@storybook/web-components": "6.1.0-alpha.14", "@types/cross-spawn": "^6.0.1", "@types/doctrine": "^0.0.3", "@types/enzyme": "^3.10.3", @@ -101,13 +103,10 @@ "cross-spawn": "^7.0.1", "fs-extra": "^9.0.0", "jest": "^26.0.0", - "jest-specific-snapshot": "^3.0.0", + "jest-specific-snapshot": "^4.0.0", "lit-element": "^2.2.1", "lit-html": "^1.0.0", - "prettier": "^2.0.5", - "react": "^16.8.3", - "react-dom": "^16.8.3", - "react-is": "^16.12.0", + "prettier": "~2.0.5", "require-from-string": "^2.0.2", "rxjs": "^6.5.4", "styled-components": "^5.0.1", @@ -119,15 +118,20 @@ "zone.js": "^0.10.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", + "@babel/core": "^7.11.5", + "@storybook/vue": "6.1.0-alpha.14", "babel-loader": "^8.0.0", - "react": ">=16.3.0", - "react-dom": "*", - "react-is": "^16.8.0", + "sveltedoc-parser": "^3.0.4", "vue": "^2.6.10", "webpack": ">=4" }, "peerDependenciesMeta": { + "@storybook/vue": { + "optional": true + }, + "sveltedoc-parser": { + "optional": true + }, "vue": { "optional": true }, @@ -138,11 +142,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/docs/react/README.md b/addons/docs/react/README.md index ec15e32e39d..12f4f2aec76 100644 --- a/addons/docs/react/README.md +++ b/addons/docs/react/README.md @@ -84,7 +84,7 @@ module.exports = { Finally, you can create MDX files like this: ```md -import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; +import { Meta, Story, ArgsTable } from '@storybook/addon-docs/blocks'; import { Button } from './Button'; @@ -97,9 +97,9 @@ Some **markdown** description, or whatever you want. -## Props +## ArgsTable - + ``` ## Inline stories @@ -107,13 +107,11 @@ Some **markdown** description, or whatever you want. 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({ +export const parameters = { docs: { inlineStories: false, }, -}); +}; ``` ## TypeScript props with `react-docgen` @@ -144,7 +142,7 @@ Neither option is perfect, so here's everything you should know if you're thinki | Docgen | Build time | | ----------------------- | ---------- | -| react-docgen-typescript | 59s | +| react-docgen-typescript | 33s | | react-docgen | 29s | | none | 28s | diff --git a/addons/docs/src/blocks/ArgsTable.tsx b/addons/docs/src/blocks/ArgsTable.tsx new file mode 100644 index 00000000000..362a672b3aa --- /dev/null +++ b/addons/docs/src/blocks/ArgsTable.tsx @@ -0,0 +1,254 @@ +/* eslint-disable no-underscore-dangle */ +import React, { FC, useContext, useEffect, useState, useCallback } from 'react'; +import mapValues from 'lodash/mapValues'; +import pickBy from 'lodash/pickBy'; +import { + ArgsTable as PureArgsTable, + ArgsTableProps as PureArgsTableProps, + ArgsTableError, + ArgTypes, + TabbedArgsTable, +} from '@storybook/components'; +import { Args } from '@storybook/addons'; +import { StoryStore } from '@storybook/client-api'; +import Events from '@storybook/core-events'; + +import { DocsContext, DocsContextProps } from './DocsContext'; +import { Component, CURRENT_SELECTION, PRIMARY_STORY } from './types'; +import { getComponentName, getDocsStories } from './utils'; +import { ArgTypesExtractor } from '../lib/docgen/types'; +import { lookupStoryId } from './Story'; + +type PropDescriptor = string[] | RegExp; + +interface BaseProps { + include?: PropDescriptor; + exclude?: PropDescriptor; +} + +type OfProps = BaseProps & { + of: '.' | '^' | Component; +}; + +type ComponentsProps = BaseProps & { + components: { + [label: string]: Component; + }; +}; + +type StoryProps = BaseProps & { + story: '.' | '^' | string; + showComponent?: boolean; +}; + +type ArgsTableProps = BaseProps | OfProps | ComponentsProps | StoryProps; + +const useArgs = ( + storyId: string, + storyStore: StoryStore +): [Args, (args: Args) => void, (argNames?: string[]) => void] => { + const story = storyStore.fromId(storyId); + if (!story) { + throw new Error(`Unknown story: ${storyId}`); + } + + const { args: initialArgs } = story; + const [args, setArgs] = useState(initialArgs); + useEffect(() => { + const cb = (changed: { storyId: string; args: Args }) => { + if (changed.storyId === storyId) { + setArgs(changed.args); + } + }; + storyStore._channel.on(Events.STORY_ARGS_UPDATED, cb); + return () => storyStore._channel.off(Events.STORY_ARGS_UPDATED, cb); + }, [storyId]); + const updateArgs = useCallback((newArgs) => storyStore.updateStoryArgs(storyId, newArgs), [ + storyId, + ]); + const resetArgs = useCallback( + (argNames?: string[]) => storyStore.resetStoryArgs(storyId, argNames), + [storyId] + ); + return [args, updateArgs, resetArgs]; +}; + +const matches = (name: string, descriptor: PropDescriptor) => + Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor); + +const filterArgTypes = (argTypes: ArgTypes, include?: PropDescriptor, exclude?: PropDescriptor) => { + if (!include && !exclude) { + return argTypes; + } + return ( + argTypes && + pickBy(argTypes, (argType, key) => { + const name = argType.name || key; + return (!include || matches(name, include)) && (!exclude || !matches(name, exclude)); + }) + ); +}; + +export const extractComponentArgTypes = ( + component: Component, + { parameters }: DocsContextProps, + include?: PropDescriptor, + exclude?: PropDescriptor +): ArgTypes => { + const params = parameters || {}; + const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = params.docs || {}; + if (!extractArgTypes) { + throw new Error(ArgsTableError.ARGS_UNSUPPORTED); + } + let argTypes = extractArgTypes(component); + argTypes = filterArgTypes(argTypes, include, exclude); + + return argTypes; +}; + +const isShortcut = (value?: string) => { + return value && [CURRENT_SELECTION, PRIMARY_STORY].includes(value); +}; + +export const getComponent = (props: ArgsTableProps = {}, context: DocsContextProps): Component => { + const { of } = props as OfProps; + const { story } = props as StoryProps; + const { parameters = {} } = context; + const { component } = parameters; + if (isShortcut(of) || isShortcut(story)) { + return component || null; + } + if (!of) { + throw new Error(ArgsTableError.NO_COMPONENT); + } + return of; +}; + +const addComponentTabs = ( + tabs: Record, + components: Record, + context: DocsContextProps, + include?: PropDescriptor, + exclude?: PropDescriptor +) => ({ + ...tabs, + ...mapValues(components, (comp) => ({ + rows: extractComponentArgTypes(comp, context, include, exclude), + })), +}); + +export const StoryTable: FC< + StoryProps & { component: Component; subcomponents: Record } +> = (props) => { + const context = useContext(DocsContext); + const { + id: currentId, + parameters: { argTypes }, + storyStore, + } = context; + const { story, component, subcomponents, showComponent, include, exclude } = props; + let storyArgTypes; + try { + let storyId; + switch (story) { + case CURRENT_SELECTION: { + storyId = currentId; + storyArgTypes = argTypes; + break; + } + case PRIMARY_STORY: { + const primaryStory = getDocsStories(context)[0]; + storyId = primaryStory.id; + storyArgTypes = primaryStory.parameters.argTypes; + break; + } + default: { + storyId = lookupStoryId(story, context); + const data = storyStore.fromId(storyId); + storyArgTypes = data.parameters.argTypes; + } + } + storyArgTypes = filterArgTypes(storyArgTypes, include, exclude); + + // eslint-disable-next-line prefer-const + let [args, updateArgs, resetArgs] = useArgs(storyId, storyStore); + let tabs = { Story: { rows: storyArgTypes, args, updateArgs, resetArgs } } as Record< + string, + PureArgsTableProps + >; + + // Use the dynamically generated component tabs if there are no controls + const storyHasArgsWithControls = + storyArgTypes && Object.values(storyArgTypes).find((v) => !!v?.control); + + if (!storyHasArgsWithControls) { + updateArgs = null; + resetArgs = null; + tabs = {}; + } + + if (component && (!storyHasArgsWithControls || showComponent)) { + const mainLabel = getComponentName(component); + tabs = addComponentTabs(tabs, { [mainLabel]: component }, context, include, exclude); + } + + if (subcomponents) { + if (Array.isArray(subcomponents)) { + throw new Error( + `Unexpected subcomponents array. Expected an object whose keys are tab labels and whose values are components.` + ); + } + tabs = addComponentTabs(tabs, subcomponents, context, include, exclude); + } + return ; + } catch (err) { + return ; + } +}; + +export const ComponentsTable: FC = (props) => { + const context = useContext(DocsContext); + const { components, include, exclude } = props; + + const tabs = addComponentTabs({}, components, context, include, exclude); + return ; +}; + +export const ArgsTable: FC = (props) => { + const context = useContext(DocsContext); + const { parameters: { subcomponents } = {} } = context; + + const { include, exclude, components } = props as ComponentsProps; + const { story } = props as StoryProps; + + const main = getComponent(props, context); + if (story) { + return ; + } + + if (!components && !subcomponents) { + let mainProps; + try { + mainProps = { rows: extractComponentArgTypes(main, context, include, exclude) }; + } catch (err) { + mainProps = { error: err.message }; + } + return ; + } + + if (components) { + return ; + } + + const mainLabel = getComponentName(main); + return ( + + ); +}; + +ArgsTable.defaultProps = { + of: CURRENT_SELECTION, +}; diff --git a/addons/docs/src/blocks/Canvas.tsx b/addons/docs/src/blocks/Canvas.tsx new file mode 100644 index 00000000000..9d1609ef6ef --- /dev/null +++ b/addons/docs/src/blocks/Canvas.tsx @@ -0,0 +1,71 @@ +import React, { FC, ReactElement, ReactNode, ReactNodeArray, useContext } from 'react'; +import { MDXProvider } from '@mdx-js/react'; +import { toId, storyNameFromExport } from '@storybook/csf'; +import { resetComponents } from '@storybook/components/html'; +import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components'; +import { DocsContext, DocsContextProps } from './DocsContext'; +import { SourceContext, SourceContextProps } from './SourceContainer'; +import { getSourceProps } from './Source'; + +export enum SourceState { + OPEN = 'open', + CLOSED = 'closed', + NONE = 'none', +} + +type CanvasProps = PurePreviewProps & { + withSource?: SourceState; + mdxSource?: string; +}; + +const getPreviewProps = ( + { + withSource = SourceState.CLOSED, + mdxSource, + children, + ...props + }: CanvasProps & { children?: ReactNode }, + docsContext: DocsContextProps, + sourceContext: SourceContextProps +): PurePreviewProps => { + if (withSource === SourceState.NONE) { + return props; + } + if (mdxSource) { + return { + ...props, + withSource: getSourceProps({ code: decodeURI(mdxSource) }, docsContext, sourceContext), + }; + } + const childArray: ReactNodeArray = Array.isArray(children) ? children : [children]; + const stories = childArray.filter( + (c: ReactElement) => c.props && (c.props.id || c.props.name) + ) as ReactElement[]; + const { mdxComponentMeta, mdxStoryNameToKey } = docsContext; + const targetIds = stories.map( + (s) => + s.props.id || + toId( + mdxComponentMeta.id || mdxComponentMeta.title, + storyNameFromExport(mdxStoryNameToKey[s.props.name]) + ) + ); + const sourceProps = getSourceProps({ ids: targetIds }, docsContext, sourceContext); + return { + ...props, // pass through columns etc. + withSource: sourceProps, + isExpanded: withSource === SourceState.OPEN, + }; +}; + +export const Canvas: FC = (props) => { + const docsContext = useContext(DocsContext); + const sourceContext = useContext(SourceContext); + const previewProps = getPreviewProps(props, docsContext, sourceContext); + const { children } = props; + return ( + + {children} + + ); +}; diff --git a/addons/docs/src/blocks/Description.tsx b/addons/docs/src/blocks/Description.tsx index 520b6263236..f8bf8e711db 100644 --- a/addons/docs/src/blocks/Description.tsx +++ b/addons/docs/src/blocks/Description.tsx @@ -37,8 +37,15 @@ export const getDescriptionProps = ( return { markdown: children || markdown }; } const { component, notes, info, docs } = parameters; - const { extractComponentDescription = noDescription } = docs || {}; + const { extractComponentDescription = noDescription, description } = docs || {}; const target = of === CURRENT_SELECTION ? component : of; + + // override component description + const componentDescriptionParameter = description?.component; + if (componentDescriptionParameter) { + return { markdown: componentDescriptionParameter }; + } + switch (type) { case DescriptionType.INFO: return { markdown: getInfo(info) }; diff --git a/addons/docs/src/blocks/DocsContainer.tsx b/addons/docs/src/blocks/DocsContainer.tsx index 2a89ede5832..2329290c24e 100644 --- a/addons/docs/src/blocks/DocsContainer.tsx +++ b/addons/docs/src/blocks/DocsContainer.tsx @@ -9,6 +9,7 @@ import { components as htmlComponents } from '@storybook/components/html'; import { DocsContextProps, DocsContext } from './DocsContext'; import { anchorBlockIdFromId } from './Anchor'; import { storyBlockIdFromId } from './Story'; +import { SourceContainer } from './SourceContainer'; import { CodeOrSourceMdx, AnchorMdx, HeadersMdx } from './mdx'; import { scrollToElement } from './utils'; @@ -23,18 +24,21 @@ const defaultComponents = { ...HeadersMdx, }; +const warnOptionsTheme = deprecate( + () => {}, + dedent` + Deprecated parameter: options.theme => docs.theme + + https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/theming.md#storybook-theming +` +); + export const DocsContainer: FunctionComponent = ({ context, children }) => { const { id: storyId = null, parameters = {} } = context || {}; const { options = {}, docs = {} } = parameters; let themeVars = docs.theme; if (!themeVars && options.theme) { - deprecate( - () => {}, - dedent` - options.theme => Deprecated: use story.parameters.docs.theme instead. - See https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/theming.md#storybook-theming for details. - ` - )(); + warnOptionsTheme(); themeVars = options.theme; } const theme = ensureTheme(themeVars); @@ -61,13 +65,14 @@ export const DocsContainer: FunctionComponent = ({ context, document.getElementById(storyBlockIdFromId(storyId)); if (element) { const allStories = element.parentElement.querySelectorAll('[id|="anchor-"]'); - let block = 'start'; + let scrollTarget = element; if (allStories && allStories[0] === element) { - block = 'end'; // first story should be shown with the intro content above + // Include content above first story + scrollTarget = document.getElementById('docs-root'); } // Introducing a delay to ensure scrolling works when it's a full refresh. setTimeout(() => { - scrollToElement(element, block); + scrollToElement(scrollTarget, 'start'); }, 200); } } @@ -75,13 +80,15 @@ export const DocsContainer: FunctionComponent = ({ context, return ( - - - - {children} - - - + + + + + {children} + + + + ); }; diff --git a/addons/docs/src/blocks/DocsPage.test.ts b/addons/docs/src/blocks/DocsPage.test.ts index 2562fb2b515..34a120d6e90 100644 --- a/addons/docs/src/blocks/DocsPage.test.ts +++ b/addons/docs/src/blocks/DocsPage.test.ts @@ -1,24 +1,10 @@ import { extractTitle } from './Title'; describe('defaultTitleSlot', () => { - it('showRoots', () => { - const parameters = { - options: { showRoots: true }, - }; + it('splits on last /', () => { + const parameters = {}; expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); expect(extractTitle({ kind: 'a|b', parameters })).toBe('a|b'); expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('c.d'); }); - it('no showRoots', () => { - const parameters = {}; - expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); - expect(extractTitle({ kind: 'a|b', parameters })).toBe('b'); - expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('d'); - }); - it('empty options', () => { - const parameters = { options: {} }; - expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c'); - expect(extractTitle({ kind: 'a|b', parameters })).toBe('b'); - expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('d'); - }); }); diff --git a/addons/docs/src/blocks/DocsPage.tsx b/addons/docs/src/blocks/DocsPage.tsx index f5f69a69933..291c7868ae6 100644 --- a/addons/docs/src/blocks/DocsPage.tsx +++ b/addons/docs/src/blocks/DocsPage.tsx @@ -3,9 +3,9 @@ import { Title } from './Title'; import { Subtitle } from './Subtitle'; import { Description } from './Description'; import { Primary } from './Primary'; -import { Props } from './Props'; -import { Stories } from './Stories'; import { PRIMARY_STORY } from './types'; +import { ArgsTable } from './ArgsTable'; +import { Stories } from './Stories'; export const DocsPage: FC = () => ( <> @@ -13,7 +13,7 @@ export const DocsPage: FC = () => ( - + ); diff --git a/addons/docs/src/blocks/DocsStory.tsx b/addons/docs/src/blocks/DocsStory.tsx index f522175c5ea..4eab78e0d89 100644 --- a/addons/docs/src/blocks/DocsStory.tsx +++ b/addons/docs/src/blocks/DocsStory.tsx @@ -1,10 +1,21 @@ import React, { FunctionComponent } from 'react'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; import { Subheading } from './Subheading'; import { DocsStoryProps } from './types'; import { Anchor } from './Anchor'; import { Description } from './Description'; import { Story } from './Story'; -import { Preview } from './Preview'; +import { Canvas } from './Canvas'; + +const warnStoryDescription = deprecate( + () => {}, + dedent` + Deprecated parameter: docs.storyDescription => docs.description.story + + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#docs-description-parameter + ` +); export const DocsStory: FunctionComponent = ({ id, @@ -12,14 +23,21 @@ export const DocsStory: FunctionComponent = ({ expanded = true, withToolbar = false, parameters, -}) => ( - - {expanded && {name}} - {expanded && parameters && parameters.docs && parameters.docs.storyDescription && ( - - )} - - - - -); +}) => { + let description = expanded && parameters?.docs?.description?.story; + if (!description) { + description = parameters?.docs?.storyDescription; + if (description) warnStoryDescription(); + } + const subheading = expanded && name; + + return ( + + {subheading && {subheading}} + {description && } + + + + + ); +}; diff --git a/addons/docs/src/blocks/Meta.tsx b/addons/docs/src/blocks/Meta.tsx index f3f5f49a3a0..6acfe44b844 100644 --- a/addons/docs/src/blocks/Meta.tsx +++ b/addons/docs/src/blocks/Meta.tsx @@ -10,7 +10,7 @@ type Decorator = (...args: any) => any; interface MetaProps { title: string; component?: Component; - subcomponents: Record; + subcomponents?: Record; decorators?: [Decorator]; parameters?: any; } diff --git a/addons/docs/src/blocks/Preview.tsx b/addons/docs/src/blocks/Preview.tsx index a9008b5aaf3..94bb226fa0f 100644 --- a/addons/docs/src/blocks/Preview.tsx +++ b/addons/docs/src/blocks/Preview.tsx @@ -1,69 +1,13 @@ -import React, { FunctionComponent, ReactElement, ReactNode, ReactNodeArray } from 'react'; -import { MDXProvider } from '@mdx-js/react'; -import { toId, storyNameFromExport } from '@storybook/csf'; -import { resetComponents } from '@storybook/components/html'; -import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components'; -import { getSourceProps } from './Source'; -import { DocsContext, DocsContextProps } from './DocsContext'; +import React, { ComponentProps } from 'react'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; +import { Canvas } from './Canvas'; -export enum SourceState { - OPEN = 'open', - CLOSED = 'closed', - NONE = 'none', -} +export const Preview = deprecate( + (props: ComponentProps) => , + dedent` + Preview doc block has been renamed to Canvas. -type PreviewProps = PurePreviewProps & { - withSource?: SourceState; - mdxSource?: string; -}; - -const getPreviewProps = ( - { - withSource = SourceState.CLOSED, - mdxSource, - children, - ...props - }: PreviewProps & { children?: ReactNode }, - { mdxStoryNameToKey, mdxComponentMeta, storyStore }: DocsContextProps -): PurePreviewProps => { - if (withSource === SourceState.NONE) { - return props; - } - if (mdxSource) { - return { - ...props, - withSource: getSourceProps({ code: decodeURI(mdxSource) }, { storyStore }), - }; - } - const childArray: ReactNodeArray = Array.isArray(children) ? children : [children]; - const stories = childArray.filter( - (c: ReactElement) => c.props && (c.props.id || c.props.name) - ) as ReactElement[]; - const targetIds = stories.map( - (s) => - s.props.id || - toId( - mdxComponentMeta.id || mdxComponentMeta.title, - storyNameFromExport(mdxStoryNameToKey[s.props.name]) - ) - ); - const sourceProps = getSourceProps({ ids: targetIds }, { storyStore }); - return { - ...props, // pass through columns etc. - withSource: sourceProps, - isExpanded: withSource === SourceState.OPEN, - }; -}; - -export const Preview: FunctionComponent = (props) => ( - - {(context) => { - const previewProps = getPreviewProps(props, context); - return ( - - {props.children} - - ); - }} - + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#previewprops-renamed + ` ); diff --git a/addons/docs/src/blocks/Props.tsx b/addons/docs/src/blocks/Props.tsx index 2bb365863ac..f251ed3575b 100644 --- a/addons/docs/src/blocks/Props.tsx +++ b/addons/docs/src/blocks/Props.tsx @@ -1,237 +1,19 @@ -/* eslint-disable no-underscore-dangle */ -import React, { FC, useContext, useEffect, useState, useCallback } from 'react'; -import mapValues from 'lodash/mapValues'; -import pickBy from 'lodash/pickBy'; -import { - ArgsTable, - ArgsTableProps, - ArgsTableError, - ArgTypes, - TabbedArgsTable, -} from '@storybook/components'; -import { Args } from '@storybook/addons'; -import { StoryStore } from '@storybook/client-api'; -import Events from '@storybook/core-events'; +import React, { ComponentProps } from 'react'; +import deprecate from 'util-deprecate'; +import dedent from 'ts-dedent'; +import { ArgsTable } from './ArgsTable'; +import { CURRENT_SELECTION } from './types'; -import { DocsContext, DocsContextProps } from './DocsContext'; -import { Component, CURRENT_SELECTION, PRIMARY_STORY } from './types'; -import { getComponentName, getDocsStories } from './utils'; -import { ArgTypesExtractor } from '../lib/docgen/types'; -import { lookupStoryId } from './Story'; +export const Props = deprecate( + (props: ComponentProps) => , + dedent` + Props doc block has been renamed to ArgsTable. -type PropDescriptor = string[] | RegExp; - -interface BaseProps { - include?: PropDescriptor; - exclude?: PropDescriptor; -} - -type OfProps = BaseProps & { - of: '.' | Component; -}; - -type ComponentsProps = BaseProps & { - components: { - [label: string]: Component; - }; -}; - -type StoryProps = BaseProps & { - story: '.' | string; - showComponents?: boolean; -}; - -type PropsProps = BaseProps | OfProps | ComponentsProps | StoryProps; - -const useArgs = (storyId: string, storyStore: StoryStore): [Args, (args: Args) => void] => { - const story = storyStore.fromId(storyId); - if (!story) { - throw new Error(`Unknown story: ${storyId}`); - } - - const { args: initialArgs } = story; - const [args, setArgs] = useState(initialArgs); - useEffect(() => { - const cb = (changedId: string, newArgs: Args) => { - if (changedId === storyId) { - setArgs(newArgs); - } - }; - storyStore._channel.on(Events.STORY_ARGS_UPDATED, cb); - return () => storyStore._channel.off(Events.STORY_ARGS_UPDATED, cb); - }, [storyId]); - const updateArgs = useCallback((newArgs) => storyStore.updateStoryArgs(storyId, newArgs), [ - storyId, - ]); - return [args, updateArgs]; -}; - -const matches = (name: string, descriptor: PropDescriptor) => - Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor); - -const filterArgTypes = (argTypes: ArgTypes, include?: PropDescriptor, exclude?: PropDescriptor) => { - if (!include && !exclude) { - return argTypes; - } - return ( - argTypes && - pickBy(argTypes, (argType, key) => { - const name = argType.name || key; - return (!include || matches(name, include)) && (!exclude || !matches(name, exclude)); - }) - ); -}; - -export const extractComponentArgTypes = ( - component: Component, - { parameters }: DocsContextProps, - include?: PropDescriptor, - exclude?: PropDescriptor -): ArgTypes => { - const params = parameters || {}; - const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = params.docs || {}; - if (!extractArgTypes) { - throw new Error(ArgsTableError.ARGS_UNSUPPORTED); - } - let argTypes = extractArgTypes(component); - argTypes = filterArgTypes(argTypes, include, exclude); - - return argTypes; -}; - -export const getComponent = (props: PropsProps = {}, context: DocsContextProps): Component => { - const { of } = props as OfProps; - const { parameters = {} } = context; - const { component } = parameters; - - const target = of === CURRENT_SELECTION ? component : of; - if (!target) { - if (of === CURRENT_SELECTION) { - return null; - } - throw new Error(ArgsTableError.NO_COMPONENT); - } - return target; -}; - -const addComponentTabs = ( - tabs: Record, - components: Record, - context: DocsContextProps, - include?: PropDescriptor, - exclude?: PropDescriptor -) => ({ - ...tabs, - ...mapValues(components, (comp) => ({ - rows: extractComponentArgTypes(comp, context, include, exclude), - })), -}); - -export const StoryTable: FC }> = (props) => { - const context = useContext(DocsContext); - const { - id: currentId, - parameters: { argTypes }, - storyStore, - } = context; - const { story, showComponents, components, include, exclude } = props; - let storyArgTypes; - try { - let storyId; - switch (story) { - case CURRENT_SELECTION: { - storyId = currentId; - storyArgTypes = argTypes; - break; - } - case PRIMARY_STORY: { - const primaryStory = getDocsStories(context)[0]; - storyId = primaryStory.id; - storyArgTypes = primaryStory.parameters.argTypes; - break; - } - default: { - storyId = lookupStoryId(story, context); - const data = storyStore.fromId(storyId); - storyArgTypes = data.parameters.argTypes; - } - } - storyArgTypes = filterArgTypes(storyArgTypes, include, exclude); - - // This code handles three cases: - // 1. the story has args, in which case we want to show controls for the story - // 2. the story has args, and the user specifies showComponents, in which case - // we want to show controls for the primary component AND show props for each component - // 3. the story has NO args, in which case we want to show props for each component - - // eslint-disable-next-line prefer-const - let [args, updateArgs] = useArgs(storyId, storyStore); - let tabs = { Story: { rows: storyArgTypes, args, updateArgs } } as Record< - string, - ArgsTableProps - >; - - // Use the dynamically generated component tabs if there are no controls - const storyHasArgsWithControls = - storyArgTypes && Object.values(storyArgTypes).find((v) => !!v?.control); - - if (!storyHasArgsWithControls) { - updateArgs = null; - tabs = {}; - } - - if (showComponents || !storyHasArgsWithControls) { - tabs = addComponentTabs(tabs, components, context, include, exclude); - } - - return ; - } catch (err) { - return ; - } -}; - -export const ComponentsTable: FC = (props) => { - const context = useContext(DocsContext); - const { components, include, exclude } = props; - - const tabs = addComponentTabs({}, components, context, include, exclude); - return ; -}; - -export const Props: FC = (props) => { - const context = useContext(DocsContext); - const { - parameters: { subcomponents }, - } = context; - - const { include, exclude, components } = props as ComponentsProps; - const { story } = props as StoryProps; - - let allComponents = components; - const main = getComponent(props, context); - - if (!allComponents && main) { - const mainLabel = getComponentName(main); - allComponents = { [mainLabel]: main, ...subcomponents }; - } - - if (story) { - return ; - } - - if (!components && !subcomponents) { - let mainProps; - try { - mainProps = { rows: extractComponentArgTypes(main, context, include, exclude) }; - } catch (err) { - mainProps = { error: err.message }; - } - return ; - } - - return ; -}; + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#previewprops-renamed + ` +); +// @ts-ignore Props.defaultProps = { of: CURRENT_SELECTION, }; diff --git a/addons/docs/src/blocks/Source.tsx b/addons/docs/src/blocks/Source.tsx index fcf7b0998fa..d95ce5eaa25 100644 --- a/addons/docs/src/blocks/Source.tsx +++ b/addons/docs/src/blocks/Source.tsx @@ -1,12 +1,24 @@ -import React, { FunctionComponent } from 'react'; -import { Source, SourceProps as PureSourceProps, SourceError } from '@storybook/components'; +import React, { FC, useContext } from 'react'; +import { + Source as PureSource, + SourceError, + SourceProps as PureSourceProps, +} from '@storybook/components'; +import { StoryId } from '@storybook/api'; +import { logger } from '@storybook/client-logger'; +import { StoryContext } from '@storybook/addons'; + import { DocsContext, DocsContextProps } from './DocsContext'; +import { SourceContext, SourceContextProps } from './SourceContainer'; import { CURRENT_SELECTION } from './types'; +import { SourceType } from '../shared'; + import { enhanceSource } from './enhanceSource'; interface CommonProps { language?: string; dark?: boolean; + code?: string; } type SingleSourceProps = { @@ -25,10 +37,70 @@ type NoneProps = CommonProps; type SourceProps = SingleSourceProps | MultiSourceProps | CodeProps | NoneProps; +const getStoryContext = (storyId: StoryId, docsContext: DocsContextProps): StoryContext | null => { + const { storyStore } = docsContext; + const storyContext = storyStore?.fromId(storyId); + + if (!storyContext) { + // Fallback if we can't get the story data for this story + logger.warn(`Unable to find information for story ID '${storyId}'`); + return null; + } + + return storyContext; +}; + +const getStorySource = (storyId: StoryId, sourceContext: SourceContextProps): string => { + const { sources } = sourceContext; + + const source = sources?.[storyId]; + + if (!source) { + logger.warn(`Unable to find source for story ID '${storyId}'`); + return ''; + } + + return source; +}; + +const getSnippet = (snippet: string, storyContext?: StoryContext): string => { + if (!storyContext) { + return snippet; + } + + const { parameters } = storyContext; + // eslint-disable-next-line no-underscore-dangle + const isArgsStory = parameters.__isArgsStory; + const type = parameters.docs?.source?.type || SourceType.AUTO; + + // if user has hard-coded the snippet, that takes precedence + const userCode = parameters.docs?.source?.code; + if (userCode) { + return userCode; + } + + // if user has explicitly set this as dynamic, use snippet + if (type === SourceType.DYNAMIC) { + return parameters.docs?.transformSource?.(snippet, storyContext) || snippet; + } + + // if this is an args story and there's a snippet + if (type === SourceType.AUTO && snippet && isArgsStory) { + return parameters.docs?.transformSource?.(snippet, storyContext) || snippet; + } + + // otherwise, use the source code logic + const enhanced = enhanceSource(storyContext) || parameters; + return enhanced?.docs?.source?.code || ''; +}; + export const getSourceProps = ( props: SourceProps, - { id: currentId, storyStore }: DocsContextProps + docsContext: DocsContextProps, + sourceContext: SourceContextProps ): PureSourceProps => { + const { id: currentId } = docsContext; + const codeProps = props as CodeProps; const singleProps = props as SingleSourceProps; const multiProps = props as MultiSourceProps; @@ -39,10 +111,10 @@ export const getSourceProps = ( singleProps.id === CURRENT_SELECTION || !singleProps.id ? currentId : singleProps.id; const targetIds = multiProps.ids || [targetId]; source = targetIds - .map((sourceId) => { - const data = storyStore.fromId(sourceId); - const enhanced = data && (enhanceSource(data) || data.parameters); - return enhanced?.docs?.source?.code || ''; + .map((storyId) => { + const storySource = getStorySource(storyId, sourceContext); + const storyContext = getStoryContext(storyId, docsContext); + return getSnippet(storySource, storyContext); }) .join('\n\n'); } @@ -56,13 +128,9 @@ export const getSourceProps = ( * or the source for a story if `storyId` is provided, or * the source for the current story if nothing is provided. */ -const SourceContainer: FunctionComponent = (props) => ( - - {(context) => { - const sourceProps = getSourceProps(props, context); - return ; - }} - -); - -export { SourceContainer as Source }; +export const Source: FC = (props) => { + const sourceContext = useContext(SourceContext); + const docsContext = useContext(DocsContext); + const sourceProps = getSourceProps(props, docsContext, sourceContext); + return ; +}; diff --git a/addons/docs/src/blocks/SourceContainer.tsx b/addons/docs/src/blocks/SourceContainer.tsx new file mode 100644 index 00000000000..aa4971286ed --- /dev/null +++ b/addons/docs/src/blocks/SourceContainer.tsx @@ -0,0 +1,44 @@ +import React, { FC, Context, createContext, useEffect, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; +import { addons } from '@storybook/addons'; +import { StoryId } from '@storybook/api'; +import { SNIPPET_RENDERED } from '../shared'; + +export type SourceItem = string; +export type StorySources = Record; + +export interface SourceContextProps { + sources: StorySources; + setSource?: (id: StoryId, item: SourceItem) => void; +} + +export const SourceContext: Context = createContext({ sources: {} }); + +export const SourceContainer: FC<{}> = ({ children }) => { + const [sources, setSources] = useState({}); + const channel = addons.getChannel(); + + const sourcesRef = React.useRef(); + const handleSnippetRendered = (id: StoryId, newItem: SourceItem) => { + if (newItem !== sources[id]) { + const newSources = { ...sourcesRef.current, [id]: newItem }; + sourcesRef.current = newSources; + } + }; + + // Bind this early (instead of inside `useEffect`), because the `SNIPPET_RENDERED` event + // is triggered *during* the rendering process, not after. We have to use the ref + // to ensure we don't end up calling setState outside the effect though. + channel.on(SNIPPET_RENDERED, handleSnippetRendered); + + useEffect(() => { + const current = sourcesRef.current || {}; + if (!deepEqual(sources, current)) { + setSources(current); + } + + return () => channel.off(SNIPPET_RENDERED, handleSnippetRendered); + }); + + return {children}; +}; diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index 58996267741..48c0291ad6a 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, ReactNode, ComponentProps } from 'react'; +import React, { FunctionComponent, ReactNode, ElementType, ComponentProps } from 'react'; import { MDXProvider } from '@mdx-js/react'; import { resetComponents } from '@storybook/components/html'; import { Story as PureStory } from '@storybook/components'; @@ -25,6 +25,11 @@ type StoryRefProps = { id?: string; } & CommonProps; +type StoryImportProps = { + name: string; + story: ElementType; +} & CommonProps; + export type StoryProps = StoryDefProps | StoryRefProps; export const lookupStoryId = ( @@ -64,7 +69,7 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur parameters, inline: storyIsInline, id: previewId, - storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn) : storyFn, + storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn, data) : storyFn, height: height || (storyIsInline ? undefined : iframeHeight), title: storyName, }; diff --git a/addons/docs/src/blocks/Title.tsx b/addons/docs/src/blocks/Title.tsx index fd9bf9acbc2..36f5590c9b9 100644 --- a/addons/docs/src/blocks/Title.tsx +++ b/addons/docs/src/blocks/Title.tsx @@ -1,5 +1,4 @@ import React, { useContext, FunctionComponent } from 'react'; -import { parseKind } from '@storybook/csf'; import { Title as PureTitle } from '@storybook/components'; import { DocsContext, DocsContextProps } from './DocsContext'; @@ -7,22 +6,7 @@ interface TitleProps { children?: JSX.Element | string; } export const extractTitle = ({ kind, parameters }: DocsContextProps) => { - const { - showRoots, - hierarchyRootSeparator: rootSeparator = '|', - hierarchySeparator: groupSeparator = /\/|\./, - } = (parameters && parameters.options) || {}; - - let groups; - if (typeof showRoots !== 'undefined') { - groups = kind.split('/'); - } else { - // This covers off all the remaining cases: - // - If the separators were set above, we should use them - // - If they weren't set, we should only should use the old defaults if the kind contains '.' or '|', - // which for this particular splitting is the only case in which it actually matters. - ({ groups } = parseKind(kind, { rootSeparator, groupSeparator })); - } + const groups = kind.split('/'); return (groups && groups[groups.length - 1]) || kind; }; diff --git a/addons/docs/src/blocks/enhanceSource.test.ts b/addons/docs/src/blocks/enhanceSource.test.ts index 393cbd9c2b9..006463374fd 100644 --- a/addons/docs/src/blocks/enhanceSource.test.ts +++ b/addons/docs/src/blocks/enhanceSource.test.ts @@ -6,12 +6,11 @@ const emptyContext: StoryContext = { kind: 'foo', name: 'bar', args: {}, - globalArgs: {}, + argTypes: {}, + globals: {}, parameters: {}, }; -const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); - describe('addon-docs enhanceSource', () => { describe('no source loaded', () => { const baseContext = emptyContext; @@ -19,14 +18,16 @@ describe('addon-docs enhanceSource', () => { expect(enhanceSource(baseContext)).toBeNull(); }); it('transformSource', () => { + const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); const parameters = { ...baseContext.parameters, docs: { transformSource } }; expect(enhanceSource({ ...baseContext, parameters })).toBeNull(); }); }); describe('custom/mdx source loaded', () => { + const source = 'storySource.source'; const baseContext = { ...emptyContext, - parameters: { storySource: { source: 'storySource.source' } }, + parameters: { storySource: { source } }, }; it('no transformSource', () => { expect(enhanceSource(baseContext)).toEqual({ @@ -34,11 +35,19 @@ describe('addon-docs enhanceSource', () => { }); }); it('transformSource', () => { + const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); const parameters = { ...baseContext.parameters, docs: { transformSource } }; expect(enhanceSource({ ...baseContext, parameters }).docs.source).toEqual({ code: 'formatted: storySource.source', }); }); + it('receives context as 2nd argument', () => { + const transformSource = jest.fn(); + const parameters = { ...baseContext.parameters, docs: { transformSource } }; + const context = { ...baseContext, parameters }; + enhanceSource(context); + expect(transformSource).toHaveBeenCalledWith(source, context); + }); }); describe('storysource source loaded w/ locationsMap', () => { const baseContext = { @@ -47,7 +56,7 @@ describe('addon-docs enhanceSource', () => { storySource: { source: 'storySource.source', locationsMap: { - 'foo--bar': { startBody: { line: 1, col: 5 }, endBody: { line: 1, col: 11 } }, + bar: { startBody: { line: 1, col: 5 }, endBody: { line: 1, col: 11 } }, }, }, }, @@ -56,11 +65,19 @@ describe('addon-docs enhanceSource', () => { expect(enhanceSource(baseContext)).toEqual({ docs: { source: { code: 'Source' } } }); }); it('transformSource', () => { + const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); const parameters = { ...baseContext.parameters, docs: { transformSource } }; expect(enhanceSource({ ...baseContext, parameters }).docs.source).toEqual({ code: 'formatted: Source', }); }); + it('receives context as 2nd argument', () => { + const transformSource = jest.fn(); + const parameters = { ...baseContext.parameters, docs: { transformSource } }; + const context = { ...baseContext, parameters }; + enhanceSource(context); + expect(transformSource).toHaveBeenCalledWith('Source', context); + }); }); describe('custom docs.source provided', () => { const baseContext = { @@ -74,6 +91,7 @@ describe('addon-docs enhanceSource', () => { expect(enhanceSource(baseContext)).toBeNull(); }); it('transformSource', () => { + const transformSource = (src?: string) => (src ? `formatted: ${src}` : 'no src'); const { source } = baseContext.parameters.docs; const parameters = { ...baseContext.parameters, docs: { source, transformSource } }; expect(enhanceSource({ ...baseContext, parameters })).toBeNull(); diff --git a/addons/docs/src/blocks/enhanceSource.ts b/addons/docs/src/blocks/enhanceSource.ts index 77afcbfa707..e336c63c650 100644 --- a/addons/docs/src/blocks/enhanceSource.ts +++ b/addons/docs/src/blocks/enhanceSource.ts @@ -1,40 +1,28 @@ import { combineParameters } from '@storybook/client-api'; import { StoryContext, Parameters } from '@storybook/addons'; - -interface Location { - line: number; - col: number; -} +import { extractSource, LocationsMap } from '@storybook/source-loader/extract-source'; interface StorySource { source: string; - locationsMap: { [id: string]: { startBody: Location; endBody: Location } }; + locationsMap: LocationsMap; } +/** + * Replaces full story id name like: story-kind--story-name -> story-name + * @param id + */ +const storyIdToSanitizedStoryName = (id: string) => id.replace(/^.*?--/, ''); + const extract = (targetId: string, { source, locationsMap }: StorySource) => { if (!locationsMap) { return source; } - const location = locationsMap[targetId]; - // FIXME: bad locationsMap generated for module export functions whose titles are overridden - if (!location) return null; - const { startBody: start, endBody: end } = location; + const sanitizedStoryName = storyIdToSanitizedStoryName(targetId); + const location = locationsMap[sanitizedStoryName]; const lines = source.split('\n'); - if (start.line === end.line && lines[start.line - 1] !== undefined) { - return lines[start.line - 1].substring(start.col, end.col); - } - // NOTE: storysource locations are 1-based not 0-based! - const startLine = lines[start.line - 1]; - const endLine = lines[end.line - 1]; - if (startLine === undefined || endLine === undefined) { - return source; - } - return [ - startLine.substring(start.col), - ...lines.slice(start.line, end.line - 1), - endLine.substring(0, end.col), - ].join('\n'); + + return extractSource(location, lines); }; export const enhanceSource = (context: StoryContext): Parameters => { @@ -48,7 +36,7 @@ export const enhanceSource = (context: StoryContext): Parameters => { } const input = extract(id, storySource); - const code = transformSource ? transformSource(input, id) : input; + const code = transformSource ? transformSource(input, context) : input; return { docs: combineParameters(docs, { source: { code } }) }; }; diff --git a/addons/docs/src/blocks/index.ts b/addons/docs/src/blocks/index.ts index 00569b5874f..8331044d174 100644 --- a/addons/docs/src/blocks/index.ts +++ b/addons/docs/src/blocks/index.ts @@ -1,6 +1,8 @@ export { ColorPalette, ColorItem, IconGallery, IconItem, Typeset } from '@storybook/components'; export * from './Anchor'; +export * from './ArgsTable'; +export * from './Canvas'; export * from './Description'; export * from './DocsContext'; export * from './DocsPage'; diff --git a/addons/docs/src/frameworks/angular/compodoc.ts b/addons/docs/src/frameworks/angular/compodoc.ts index 4298a2e2ed2..e0c47427d1f 100644 --- a/addons/docs/src/frameworks/angular/compodoc.ts +++ b/addons/docs/src/frameworks/angular/compodoc.ts @@ -93,7 +93,11 @@ const getComponentData = (component: Component | Directive) => { const compodocJson = getCompdocJson(); checkValidCompodocJson(compodocJson); const { name } = component; - return findComponentByName(name, compodocJson); + const metadata = findComponentByName(name, compodocJson); + if (!metadata) { + logger.warn(`Component not found in compodoc JSON: '${name}'`); + } + return metadata; }; const displaySignature = (item: Method): string => { @@ -221,8 +225,5 @@ export const extractArgTypes = (component: Component | Directive) => { export const extractComponentDescription = (component: Component | Directive) => { const componentData = getComponentData(component); - if (!componentData) { - return null; - } - return componentData.rawdescription || componentData.description; + return componentData && (componentData.rawdescription || componentData.description); }; diff --git a/addons/docs/src/frameworks/angular/config.ts b/addons/docs/src/frameworks/angular/config.ts index dc06b77c8d3..cb0e0dd3691 100644 --- a/addons/docs/src/frameworks/angular/config.ts +++ b/addons/docs/src/frameworks/angular/config.ts @@ -1,9 +1,8 @@ -import { addParameters } from '@storybook/client-api'; import { extractArgTypes, extractComponentDescription } from './compodoc'; -addParameters({ +export const parameters = { docs: { extractArgTypes, extractComponentDescription, }, -}); +}; diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts index 10ba2a3fc24..252dd5cfe01 100644 --- a/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.test.ts @@ -1,4 +1,4 @@ -import { ArgType, ArgTypes, Args } from '@storybook/api'; +import { ArgType, ArgTypes } from '@storybook/api'; import { enhanceArgTypes } from './enhanceArgTypes'; expect.addSnapshotSerializer({ @@ -10,20 +10,20 @@ const enhance = ({ argType, arg, extractedArgTypes, - storyFn = (args: Args) => 0, + isArgsStory = true, }: { argType?: ArgType; arg?: any; extractedArgTypes?: ArgTypes; - storyFn?: any; + isArgsStory?: boolean; }) => { const context = { id: 'foo--bar', kind: 'foo', name: 'bar', - storyFn, parameters: { component: 'dummy', + __isArgsStory: isArgsStory, docs: { extractArgTypes: extractedArgTypes && (() => extractedArgTypes), }, @@ -35,7 +35,8 @@ const enhance = ({ }, }, args: {}, - globalArgs: {}, + argTypes: {}, + globals: {}, }; return enhanceArgTypes(context); }; @@ -46,7 +47,7 @@ describe('enhanceArgTypes', () => { expect( enhance({ argType: { foo: 'unmodified', type: { name: 'number' } }, - storyFn: () => 0, + isArgsStory: false, }).input ).toMatchInlineSnapshot(` { @@ -69,25 +70,6 @@ describe('enhanceArgTypes', () => { }).input ).toMatchInlineSnapshot(` { - "control": { - "type": "number" - }, - "name": "input", - "type": { - "name": "number" - } - } - `); - }); - }); - - describe('args input', () => { - it('number', () => { - expect(enhance({ arg: 5 }).input).toMatchInlineSnapshot(` - { - "control": { - "type": "number" - }, "name": "input", "type": { "name": "number" @@ -104,9 +86,6 @@ describe('enhanceArgTypes', () => { .input ).toMatchInlineSnapshot(` { - "control": { - "type": "number" - }, "name": "input", "type": { "name": "number" @@ -163,9 +142,6 @@ describe('enhanceArgTypes', () => { }).input ).toMatchInlineSnapshot(` { - "control": { - "type": "number" - }, "type": { "name": "number" }, @@ -182,9 +158,6 @@ describe('enhanceArgTypes', () => { }).input ).toMatchInlineSnapshot(` { - "control": { - "type": "number" - }, "name": "input", "type": { "name": "number" @@ -201,10 +174,6 @@ describe('enhanceArgTypes', () => { }).input ).toMatchInlineSnapshot(` { - "control": { - "type": "text" - }, - "name": "input", "type": { "name": "string" } @@ -221,15 +190,12 @@ describe('enhanceArgTypes', () => { }).input ).toMatchInlineSnapshot(` { + "name": "input", + "defaultValue": 5, "control": { "type": "range", "step": 50 - }, - "name": "input", - "type": { - "name": "number" - }, - "defaultValue": 5 + } } `); }); @@ -243,18 +209,9 @@ describe('enhanceArgTypes', () => { ).toMatchInlineSnapshot(` { "input": { - "control": { - "type": "number" - }, - "name": "input", - "type": { - "name": "number" - } + "name": "input" }, "foo": { - "control": { - "type": "number" - }, "type": { "name": "number" } @@ -272,18 +229,12 @@ describe('enhanceArgTypes', () => { ).toMatchInlineSnapshot(` { "input": { - "control": { - "type": "number" - }, "name": "input", "type": { "name": "number" } }, "foo": { - "control": { - "type": "number" - }, "type": { "name": "number" } @@ -300,10 +251,12 @@ describe('enhanceArgTypes', () => { }) ).toMatchInlineSnapshot(` { + "foo": { + "type": { + "name": "number" + } + }, "input": { - "control": { - "type": "number" - }, "name": "input", "type": { "name": "number" diff --git a/addons/docs/src/frameworks/common/enhanceArgTypes.ts b/addons/docs/src/frameworks/common/enhanceArgTypes.ts index 3a3936f4b04..f1ee242400e 100644 --- a/addons/docs/src/frameworks/common/enhanceArgTypes.ts +++ b/addons/docs/src/frameworks/common/enhanceArgTypes.ts @@ -1,41 +1,17 @@ import mapValues from 'lodash/mapValues'; import { ArgTypesEnhancer, combineParameters } from '@storybook/client-api'; -import { ArgTypes } from '@storybook/api'; -import { inferArgTypes } from './inferArgTypes'; -import { inferControls } from './inferControls'; - -const isSubset = (kind: string, subset: object, superset: object) => { - const keys = Object.keys(subset); - // eslint-disable-next-line no-prototype-builtins - const overlap = keys.filter((key) => superset.hasOwnProperty(key)); - return overlap.length === keys.length; -}; +import { normalizeArgTypes } from './normalizeArgTypes'; export const enhanceArgTypes: ArgTypesEnhancer = (context) => { - const { component, argTypes: userArgTypes = {}, docs = {}, args = {} } = context.parameters; - const { extractArgTypes, forceExtractedArgTypes = false } = docs; + const { component, argTypes: userArgTypes = {}, docs = {} } = context.parameters; + const { extractArgTypes } = docs; - const namedArgTypes = mapValues(userArgTypes, (val, key) => ({ name: key, ...val })); - const inferredArgTypes = inferArgTypes(args); - let extractedArgTypes: ArgTypes = extractArgTypes && component ? extractArgTypes(component) : {}; + const normalizedArgTypes = normalizeArgTypes(userArgTypes); + const namedArgTypes = mapValues(normalizedArgTypes, (val, key) => ({ name: key, ...val })); + const extractedArgTypes = extractArgTypes && component ? extractArgTypes(component) : {}; + const withExtractedTypes = extractedArgTypes + ? combineParameters(extractedArgTypes, namedArgTypes) + : namedArgTypes; - if ( - !forceExtractedArgTypes && - ((Object.keys(userArgTypes).length > 0 && - !isSubset(context.kind, userArgTypes, extractedArgTypes)) || - (Object.keys(inferredArgTypes).length > 0 && - !isSubset(context.kind, inferredArgTypes, extractedArgTypes))) - ) { - extractedArgTypes = {}; - } - - const withArgTypes = combineParameters(inferredArgTypes, extractedArgTypes, namedArgTypes); - - if (context.storyFn.length === 0) { - return withArgTypes; - } - - const withControls = inferControls(withArgTypes); - const result = combineParameters(withControls, withArgTypes); - return result; + return withExtractedTypes; }; diff --git a/addons/docs/src/frameworks/common/normalizeArgTypes.ts b/addons/docs/src/frameworks/common/normalizeArgTypes.ts new file mode 100644 index 00000000000..8af93c8e414 --- /dev/null +++ b/addons/docs/src/frameworks/common/normalizeArgTypes.ts @@ -0,0 +1,18 @@ +import mapValues from 'lodash/mapValues'; +import { ArgTypes } from '@storybook/api'; +import { SBType } from '@storybook/client-api'; + +const normalizeType = (type: SBType | string) => (typeof type === 'string' ? { name: type } : type); + +const normalizeControl = (control?: any) => + typeof control === 'string' ? { type: control } : control; + +export const normalizeArgTypes = (argTypes: ArgTypes) => + mapValues(argTypes, (argType) => { + if (!argType) return argType; + const normalized = { ...argType }; + const { type, control } = argType; + if (type) normalized.type = normalizeType(type); + if (control) normalized.control = normalizeControl(control); + return normalized; + }); diff --git a/addons/docs/src/frameworks/common/preset.ts b/addons/docs/src/frameworks/common/preset.ts index 6d650289cec..69aa62750ff 100644 --- a/addons/docs/src/frameworks/common/preset.ts +++ b/addons/docs/src/frameworks/common/preset.ts @@ -13,18 +13,28 @@ const context = coreDirName.includes('node_modules') ? path.join(coreDirName, '../../') // Real life case, already in node_modules : path.join(coreDirName, '../../node_modules'); // SB Monorepo -function createBabelOptions(babelOptions?: any, configureJSX?: boolean) { - if (!configureJSX) { - return babelOptions; - } - - const babelPlugins = (babelOptions && babelOptions.plugins) || []; +// for frameworks that are not working with react, we need to configure +// the jsx to transpile mdx, for now there will be a flag for that +// for more complex solutions we can find alone that we need to add '@babel/plugin-transform-react-jsx' +type BabelParams = { + babelOptions?: any; + mdxBabelOptions?: any; + configureJSX?: boolean; +}; +function createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }: BabelParams) { + const babelPlugins = mdxBabelOptions?.plugins || babelOptions?.plugins || []; + const jsxPlugin = [ + require.resolve('@babel/plugin-transform-react-jsx'), + { pragma: 'React.createElement', pragmaFrag: 'React.Fragment' }, + ]; + const plugins = configureJSX ? [...babelPlugins, jsxPlugin] : babelPlugins; return { + // don't use the root babelrc by default (users can override this in mdxBabelOptions) + babelrc: false, + configFile: false, ...babelOptions, - // for frameworks that are not working with react, we need to configure - // the jsx to transpile mdx, for now there will be a flag for that - // for more complex solutions we can find alone that we need to add '@babel/plugin-transform-react-jsx' - plugins: [...babelPlugins, '@babel/plugin-transform-react-jsx'], + ...mdxBabelOptions, + plugins, }; } @@ -38,8 +48,10 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { // also, these babel options are chained with other presets. const { babelOptions, - configureJSX = options.framework !== 'react', // if not user-specified - sourceLoaderOptions = {}, + mdxBabelOptions, + configureJSX = true, + sourceLoaderOptions = { injectStoryParameters: true }, + transcludeMarkdown = false, } = options; const mdxLoaderOptions = { @@ -58,12 +70,32 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { ] : []; + let rules = module.rules || []; + if (transcludeMarkdown) { + rules = [ + ...rules.filter((rule: any) => rule.test.toString() !== '/\\.md$/'), + { + test: /\.md$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }), + }, + { + loader: require.resolve('@mdx-js/loader'), + options: mdxLoaderOptions, + }, + ], + }, + ]; + } + const result = { ...webpackConfig, module: { ...module, rules: [ - ...(module.rules || []), + ...rules, { test: /\.js$/, include: new RegExp(`node_modules\\${path.sep}acorn-jsx`), @@ -81,7 +113,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { use: [ { loader: require.resolve('babel-loader'), - options: createBabelOptions(babelOptions, configureJSX), + options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }), }, { loader: require.resolve('@mdx-js/loader'), @@ -98,7 +130,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) { use: [ { loader: require.resolve('babel-loader'), - options: createBabelOptions(babelOptions, configureJSX), + options: createBabelOptions({ babelOptions, mdxBabelOptions, configureJSX }), }, { loader: require.resolve('@mdx-js/loader'), diff --git a/addons/docs/src/frameworks/ember/config.js b/addons/docs/src/frameworks/ember/config.js index 9745c9eca4c..8c2ce673d38 100644 --- a/addons/docs/src/frameworks/ember/config.js +++ b/addons/docs/src/frameworks/ember/config.js @@ -1,10 +1,9 @@ -import { addParameters } from '@storybook/client-api'; import { extractArgTypes, extractComponentDescription } from './jsondoc'; -addParameters({ +export const parameters = { docs: { iframeHeight: 80, extractArgTypes, extractComponentDescription, }, -}); +}; diff --git a/addons/docs/src/frameworks/ember/jsondoc.js b/addons/docs/src/frameworks/ember/jsondoc.js index c9d95fedac6..f9e648ebc21 100644 --- a/addons/docs/src/frameworks/ember/jsondoc.js +++ b/addons/docs/src/frameworks/ember/jsondoc.js @@ -18,20 +18,21 @@ export const extractArgTypes = (componentName) => { if (!componentDoc) { return null; } - const rows = componentDoc.attributes.arguments.map((prop) => { - return { + return componentDoc.attributes.arguments.reduce((acc, prop) => { + acc[prop.name] = { name: prop.name, defaultValue: prop.defaultValue, description: prop.description, table: { + defaultValue: { summary: prop.defaultValue }, type: { summary: prop.type, required: prop.tags.length ? prop.tags.some((tag) => tag.name === 'required') : false, }, }, }; - }); - return { rows }; + return acc; + }, {}); }; export const extractComponentDescription = (componentName) => { diff --git a/addons/docs/src/frameworks/html/config.tsx b/addons/docs/src/frameworks/html/config.tsx new file mode 100644 index 00000000000..143aba939e7 --- /dev/null +++ b/addons/docs/src/frameworks/html/config.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { StoryFn } from '@storybook/addons'; + +export const parameters = { + docs: { + inlineStories: true, + prepareForInline: (storyFn: StoryFn) => { + const html = storyFn(); + if (typeof html === 'string') { + // eslint-disable-next-line react/no-danger + return
; + } + return
(node ? node.appendChild(html) : null)} />; + }, + }, +}; diff --git a/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot index 36a0021a621..3f60a5fc45f 100644 --- a/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot +++ b/addons/docs/src/frameworks/react/__testfixtures__/8894-9511-ts-forward-ref/docgen.snapshot @@ -2,7 +2,7 @@ exports[`react component properties 8894-9511-ts-forward-ref 1`] = ` "import React, { forwardRef } from 'react'; -const Button = forwardRef(({ +const Button = /*#__PURE__*/forwardRef(({ disabled = false, variant = 'small', children diff --git a/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot index 67c39e90300..e31e4ae1258 100644 --- a/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot +++ b/addons/docs/src/frameworks/react/__testfixtures__/9586-js-react-memo/docgen.snapshot @@ -18,7 +18,7 @@ Button.propTypes = { label: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired }; -const MemoButton = React.memo(Button); +const MemoButton = /*#__PURE__*/React.memo(Button); export const component = MemoButton; Button.__docgenInfo = { \\"description\\": \\"\\", diff --git a/addons/docs/src/frameworks/react/config.ts b/addons/docs/src/frameworks/react/config.ts index c84f20f7025..61a01e8a3af 100644 --- a/addons/docs/src/frameworks/react/config.ts +++ b/addons/docs/src/frameworks/react/config.ts @@ -1,6 +1,7 @@ import { StoryFn } from '@storybook/addons'; import { extractArgTypes } from './extractArgTypes'; import { extractComponentDescription } from '../../lib/docgen'; +import { jsxDecorator } from './jsxDecorator'; export const parameters = { docs: { @@ -11,3 +12,5 @@ export const parameters = { extractComponentDescription, }, }; + +export const decorators = [jsxDecorator]; diff --git a/addons/docs/src/frameworks/react/extractProps.ts b/addons/docs/src/frameworks/react/extractProps.ts index be03ff06f9b..9fc460e5d99 100644 --- a/addons/docs/src/frameworks/react/extractProps.ts +++ b/addons/docs/src/frameworks/react/extractProps.ts @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import { isForwardRef, isMemo } from 'react-is'; +import { isMemo } from 'react-is'; import { PropDef, hasDocgen, @@ -29,13 +29,8 @@ function getPropDefs(component: Component, section: string): PropDef[] { let processedComponent = component; // eslint-disable-next-line react/forbid-foreign-prop-types - if (!hasDocgen(component) && !component.propTypes) { - if (isForwardRef(component) || component.render) { - processedComponent = component.render({}).type; - } - if (isMemo(component)) { - processedComponent = component.type().type; - } + if (!hasDocgen(component) && !component.propTypes && isMemo(component)) { + processedComponent = component.type().type; } const extractedProps = extractComponentProps(processedComponent, section); diff --git a/addons/docs/src/frameworks/react/jsxDecorator.test.tsx b/addons/docs/src/frameworks/react/jsxDecorator.test.tsx new file mode 100644 index 00000000000..853c213b2ce --- /dev/null +++ b/addons/docs/src/frameworks/react/jsxDecorator.test.tsx @@ -0,0 +1,182 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ +import React from 'react'; +import range from 'lodash/range'; +import addons, { StoryContext } from '@storybook/addons'; +import { renderJsx, jsxDecorator } from './jsxDecorator'; +import { SNIPPET_RENDERED } from '../../shared'; + +jest.mock('@storybook/addons'); +const mockedAddons = addons as jest.Mocked; + +expect.addSnapshotSerializer({ + print: (val: any) => val, + test: (val) => typeof val === 'string', +}); + +describe('renderJsx', () => { + it('basic', () => { + expect(renderJsx(
hello
, {})).toMatchInlineSnapshot(` +
+ hello +
+ `); + }); + it('functions', () => { + // eslint-disable-next-line no-console + const onClick = () => console.log('onClick'); + expect(renderJsx(
hello
, {})).toMatchInlineSnapshot(` +
{}}> + hello +
+ `); + }); + it('undefined values', () => { + expect(renderJsx(
hello
, {})).toMatchInlineSnapshot(` +
+ hello +
+ `); + }); + it('null values', () => { + expect(renderJsx(
hello
, {})).toMatchInlineSnapshot(` +
+ hello +
+ `); + }); + it('large objects', () => { + const obj: Record = {}; + range(20).forEach((i) => { + obj[`key_${i}`] = `val_${i}`; + }); + expect(renderJsx(
, {})).toMatchInlineSnapshot(` +
+ `); + }); + + it('long arrays', () => { + const arr = range(20).map((i) => `item ${i}`); + expect(renderJsx(
, {})).toMatchInlineSnapshot(` +
+ `); + }); +}); + +// @ts-ignore +const makeContext = (name: string, parameters: any, args: any): StoryContext => ({ + id: `jsx-test--${name}`, + kind: 'js-text', + name, + parameters, + args, +}); + +describe('jsxDecorator', () => { + let mockChannel: { on: jest.Mock; emit?: jest.Mock }; + beforeEach(() => { + mockedAddons.getChannel.mockReset(); + + mockChannel = { on: jest.fn(), emit: jest.fn() }; + mockedAddons.getChannel.mockReturnValue(mockChannel as any); + }); + + it('should render dynamically for args stories', () => { + const storyFn = (args: any) =>
args story
; + const context = makeContext('args', { __isArgsStory: true }, {}); + jsxDecorator(storyFn, context); + expect(mockChannel.emit).toHaveBeenCalledWith( + SNIPPET_RENDERED, + 'jsx-test--args', + '
\n args story\n
' + ); + }); + + it('should skip dynamic rendering for no-args stories', () => { + const storyFn = () =>
classic story
; + const context = makeContext('classic', {}, {}); + jsxDecorator(storyFn, context); + expect(mockChannel.emit).not.toHaveBeenCalled(); + }); + + // This is deprecated, but still test it + it('allows the snippet output to be modified by onBeforeRender', () => { + const storyFn = (args: any) =>
args story
; + const onBeforeRender = (dom: string) => `

${dom}

`; + const jsx = { onBeforeRender }; + const context = makeContext('args', { __isArgsStory: true, jsx }, {}); + jsxDecorator(storyFn, context); + expect(mockChannel.emit).toHaveBeenCalledWith( + SNIPPET_RENDERED, + 'jsx-test--args', + '

\n args story\n

' + ); + }); + + it('allows the snippet output to be modified by transformSource', () => { + const storyFn = (args: any) =>
args story
; + const transformSource = (dom: string) => `

${dom}

`; + const jsx = { transformSource }; + const context = makeContext('args', { __isArgsStory: true, jsx }, {}); + jsxDecorator(storyFn, context); + expect(mockChannel.emit).toHaveBeenCalledWith( + SNIPPET_RENDERED, + 'jsx-test--args', + '

\n args story\n

' + ); + }); + + it('provides the story context to transformSource', () => { + const storyFn = (args: any) =>
args story
; + const transformSource = jest.fn(); + const jsx = { transformSource }; + const context = makeContext('args', { __isArgsStory: true, jsx }, {}); + jsxDecorator(storyFn, context); + expect(transformSource).toHaveBeenCalledWith('
\n args story\n
', context); + }); +}); diff --git a/addons/docs/src/frameworks/react/jsxDecorator.tsx b/addons/docs/src/frameworks/react/jsxDecorator.tsx new file mode 100644 index 00000000000..5069995fd39 --- /dev/null +++ b/addons/docs/src/frameworks/react/jsxDecorator.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import reactElementToJSXString, { Options } from 'react-element-to-jsx-string'; +import dedent from 'ts-dedent'; +import deprecate from 'util-deprecate'; + +import { addons, StoryContext } from '@storybook/addons'; +import { logger } from '@storybook/client-logger'; + +import { SourceType, SNIPPET_RENDERED } from '../../shared'; + +type JSXOptions = Options & { + /** How many wrappers to skip when rendering the jsx */ + skip?: number; + /** Whether to show the function in the jsx tab */ + showFunctions?: boolean; + /** Whether to format HTML or Vue markup */ + enableBeautify?: boolean; + /** Override the display name used for a component */ + displayName?: string | Options['displayName']; + /** Deprecated: A function ran after the story is rendered */ + onBeforeRender?(dom: string): string; + /** A function ran after a story is rendered (prefer this over `onBeforeRender`) */ + transformSource?(dom: string, context?: StoryContext): string; +}; + +/** Run the user supplied onBeforeRender function if it exists */ +const applyBeforeRender = (domString: string, options: JSXOptions) => { + if (typeof options.onBeforeRender !== 'function') { + return domString; + } + + const deprecatedOnBeforeRender = deprecate( + options.onBeforeRender, + dedent` + StoryFn.parameters.jsx.onBeforeRender was deprecated. + Prefer StoryFn.parameters.jsx.transformSource instead. + See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#deprecated-onbeforerender for details. + ` + ); + + return deprecatedOnBeforeRender(domString); +}; + +/** Run the user supplied transformSource function if it exists */ +const applyTransformSource = (domString: string, options: JSXOptions, context?: StoryContext) => { + if (typeof options.transformSource !== 'function') { + return domString; + } + + return options.transformSource(domString, context); +}; + +/** Apply the users parameters and render the jsx for a story */ +export const renderJsx = (code: React.ReactElement, options: JSXOptions) => { + if (typeof code === 'undefined') { + logger.warn('Too many skip or undefined component'); + return null; + } + + let renderedJSX = code; + const Type = renderedJSX.type; + + for (let i = 0; i < options.skip; i += 1) { + if (typeof renderedJSX === 'undefined') { + logger.warn('Cannot skip undefined element'); + return null; + } + + if (React.Children.count(renderedJSX) > 1) { + logger.warn('Trying to skip an array of elements'); + return null; + } + + if (typeof renderedJSX.props.children === 'undefined') { + logger.warn('Not enough children to skip elements.'); + + if (typeof Type === 'function' && Type.name === '') { + renderedJSX = ; + } + } else if (typeof renderedJSX.props.children === 'function') { + renderedJSX = renderedJSX.props.children(); + } else { + renderedJSX = renderedJSX.props.children; + } + } + + const displayNameDefaults = + typeof options.displayName === 'string' + ? { showFunctions: true, displayName: () => options.displayName } + : {}; + + const filterDefaults = { + filterProps: (value: any, key: string): boolean => value !== undefined, + }; + + const opts = { + ...displayNameDefaults, + ...filterDefaults, + ...options, + }; + + const result = React.Children.map(code, (c) => { + // @ts-ignore FIXME: workaround react-element-to-jsx-string + const child = typeof c === 'number' ? c.toString() : c; + let string = applyBeforeRender(reactElementToJSXString(child, opts as Options), options); + const matches = string.match(/\S+=\\"([^"]*)\\"/g); + + if (matches) { + matches.forEach((match) => { + string = string.replace(match, match.replace(/"/g, "'")); + }); + } + + return string; + }).join('\n'); + + return result.replace(/function\s+noRefCheck\(\)\s+\{\}/, '() => {}'); +}; + +const defaultOpts = { + skip: 0, + showFunctions: false, + enableBeautify: true, +}; + +export const skipJsxRender = (context: StoryContext) => { + const sourceParams = context?.parameters.docs?.source; + const isArgsStory = context?.parameters.__isArgsStory; + + // always render if the user forces it + if (sourceParams?.type === SourceType.DYNAMIC) { + return false; + } + + // never render if the user is forcing the block to render code, or + // if the user provides code, or if it's not an args story. + return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE; +}; + +export const jsxDecorator = (storyFn: any, context: StoryContext) => { + const story = storyFn(); + + // We only need to render JSX if the source block is actually going to + // consume it. Otherwise it's just slowing us down. + if (skipJsxRender(context)) { + return story; + } + + const channel = addons.getChannel(); + + const options = { + ...defaultOpts, + ...(context?.parameters.jsx || {}), + } as Required; + + let jsx = ''; + const rendered = renderJsx(story, options); + if (rendered) { + jsx = applyTransformSource(rendered, options, context); + } + + channel.emit(SNIPPET_RENDERED, (context || {}).id, jsx); + + return story; +}; diff --git a/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts index cf20749ff8d..5041f189197 100644 --- a/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts +++ b/addons/docs/src/frameworks/react/lib/defaultValues/createFromRawDefaultProp.ts @@ -45,7 +45,7 @@ function generateReactObject(rawDefaultProp: any) { const { type } = rawDefaultProp; const { displayName } = type; - const jsx = reactElementToJSXString(rawDefaultProp); + const jsx = reactElementToJSXString(rawDefaultProp, {}); if (displayName != null) { const prettyIdentifier = getPrettyElementIdentifier(displayName); diff --git a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx index 5441c838cad..c661df23d55 100644 --- a/addons/docs/src/frameworks/react/react-argtypes.stories.tsx +++ b/addons/docs/src/frameworks/react/react-argtypes.stories.tsx @@ -1,18 +1,17 @@ import React, { useState } from 'react'; import mapValues from 'lodash/mapValues'; -import { storiesOf } from '@storybook/react'; +import { storiesOf, StoryContext } from '@storybook/react'; import { ArgsTable } from '@storybook/components'; import { Args } from '@storybook/api'; -import { combineParameters } from '@storybook/client-api'; +import { inferControls } from '@storybook/client-api'; import { extractArgTypes } from './extractArgTypes'; -import { inferControls } from '../common/inferControls'; import { Component } from '../../blocks'; const argsTableProps = (component: Component) => { const argTypes = extractArgTypes(component); - const controls = inferControls(argTypes); - const rows = combineParameters(argTypes, controls); + const parameters = { __isArgsStory: true, argTypes }; + const rows = inferControls(({ parameters } as unknown) as StoryContext); return { rows }; }; @@ -55,12 +54,13 @@ const typescriptFixtures = [ 'scalars', 'tuples', 'unions', + 'optionals', ]; const typescriptStories = storiesOf('ArgTypes/TypeScript', module); typescriptFixtures.forEach((fixture) => { // eslint-disable-next-line import/no-dynamic-require, global-require, no-shadow - const { Component } = require(`../../lib/sbtypes/__testfixtures__/typescript/${fixture}`); + const { Component } = require(`../../lib/convert/__testfixtures__/typescript/${fixture}`); typescriptStories.add(fixture, () => ); }); @@ -69,7 +69,7 @@ const proptypesFixtures = ['arrays', 'enums', 'misc', 'objects', 'react', 'scala const proptypesStories = storiesOf('ArgTypes/PropTypes', module); proptypesFixtures.forEach((fixture) => { // eslint-disable-next-line import/no-dynamic-require, global-require, no-shadow - const { Component } = require(`../../lib/sbtypes/__testfixtures__/proptypes/${fixture}`); + const { Component } = require(`../../lib/convert/__testfixtures__/proptypes/${fixture}`); proptypesStories.add(fixture, () => ); }); diff --git a/addons/docs/src/frameworks/svelte/HOC.svelte b/addons/docs/src/frameworks/svelte/HOC.svelte new file mode 100644 index 00000000000..12cf1cf5e20 --- /dev/null +++ b/addons/docs/src/frameworks/svelte/HOC.svelte @@ -0,0 +1,19 @@ + + + +
diff --git a/addons/docs/src/frameworks/svelte/config.ts b/addons/docs/src/frameworks/svelte/config.ts new file mode 100644 index 00000000000..4a4fd37a39f --- /dev/null +++ b/addons/docs/src/frameworks/svelte/config.ts @@ -0,0 +1,12 @@ +import { extractArgTypes } from './extractArgTypes'; +import { extractComponentDescription } from '../../lib/docgen'; +import { prepareForInline } from './prepareForInline'; + +export const parameters = { + docs: { + inlineStories: true, + prepareForInline, + extractArgTypes, + extractComponentDescription, + }, +}; diff --git a/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts b/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts new file mode 100644 index 00000000000..81543fc3648 --- /dev/null +++ b/addons/docs/src/frameworks/svelte/extractArgTypes.test.ts @@ -0,0 +1,94 @@ +import svelteDoc from 'sveltedoc-parser'; +import * as fs from 'fs'; +import { createArgTypes } from './extractArgTypes'; + +const content = fs.readFileSync(`${__dirname}/sample/MockButton.svelte`, 'utf-8'); + +describe('Extracting Arguments', () => { + it('should be svelte', () => { + expect(content).toMatchInlineSnapshot(` + + + + + + `); + }); + + it('should generate ArgTypes', async () => { + const doc = await svelteDoc.parse({ fileContent: content, version: 3 }); + + const results = createArgTypes(doc); + + expect(results).toMatchInlineSnapshot(` + Object { + "rounded": Object { + "control": Object { + "type": "boolean", + }, + "defaultValue": true, + "description": null, + "name": "rounded", + "table": Object { + "defaultValue": Object { + "summary": true, + }, + }, + "type": Object {}, + }, + "text": Object { + "control": Object { + "type": "text", + }, + "defaultValue": "", + "description": null, + "name": "text", + "table": Object { + "defaultValue": Object { + "summary": "", + }, + }, + "type": Object {}, + }, + } + `); + }); +}); diff --git a/addons/docs/src/frameworks/svelte/extractArgTypes.ts b/addons/docs/src/frameworks/svelte/extractArgTypes.ts new file mode 100644 index 00000000000..c560ce65b28 --- /dev/null +++ b/addons/docs/src/frameworks/svelte/extractArgTypes.ts @@ -0,0 +1,83 @@ +import { ArgTypes } from '@storybook/api'; + +import { ArgTypesExtractor } from '../../lib/docgen'; + +type ComponentWithDocgen = { + __docgen: Docgen; +}; + +type Docgen = { + components: []; + computed: []; + data: [ + { + defaultValue: any; + description: string; + keywords: []; + kind: string; + name: string; + readonly: boolean; + static: boolean; + type: { kind: string; text: string; type: string }; + visibility: string; + } + ]; + description: null; + events: []; + keywords: []; + methods: []; + name: string; + refs: []; + slots: []; + version: number; +}; + +export const extractArgTypes: ArgTypesExtractor = (component) => { + // eslint-disable-next-line new-cap + const comp: ComponentWithDocgen = new component({ props: {} }); + // eslint-disable-next-line no-underscore-dangle + const docs = comp.__docgen; + + const results = createArgTypes(docs); + + return results; +}; + +export const createArgTypes = (docs: Docgen) => { + const results: ArgTypes = {}; + docs.data.forEach((item) => { + results[item.name] = { + control: { type: parseType(item.type.type) }, + name: item.name, + description: item.description, + type: {}, + defaultValue: item.defaultValue, + table: { + defaultValue: { + summary: item.defaultValue, + }, + }, + }; + }); + + return results; +}; + +/** + * Function to convert the type from sveltedoc-parser to a storybook type + * @param typeName + * @returns string + */ +const parseType = (typeName: string) => { + switch (typeName) { + case 'string': + return 'text'; + + case 'enum': + return 'radio'; + case 'any': + return 'object'; + default: + return typeName; + } +}; diff --git a/addons/docs/src/frameworks/svelte/prepareForInline.ts b/addons/docs/src/frameworks/svelte/prepareForInline.ts new file mode 100644 index 00000000000..5496bf45a1e --- /dev/null +++ b/addons/docs/src/frameworks/svelte/prepareForInline.ts @@ -0,0 +1,26 @@ +import { StoryFn, StoryContext } from '@storybook/addons'; + +import React from 'react'; + +// @ts-ignore +import HOC from './HOC.svelte'; + +export const prepareForInline = (storyFn: StoryFn, context: StoryContext) => { + // @ts-ignore + const story: { Component: any; props: any } = storyFn(); + const el = React.useRef(null); + React.useEffect(() => { + const root = new HOC({ + target: el.current, + props: { + component: story.Component, + context, + props: story.props, + slot: story.Component, + }, + }); + return () => root.$destroy(); + }); + + return React.createElement('div', { ref: el }); +}; diff --git a/addons/docs/src/frameworks/svelte/preset.ts b/addons/docs/src/frameworks/svelte/preset.ts new file mode 100644 index 00000000000..f68fa8ce340 --- /dev/null +++ b/addons/docs/src/frameworks/svelte/preset.ts @@ -0,0 +1,13 @@ +import path from 'path'; + +import { Configuration } from 'webpack'; + +export function webpackFinal(webpackConfig: Configuration, options: any = {}) { + webpackConfig.module.rules.push({ + test: /\.svelte$/, + loader: path.resolve(`${__dirname}/svelte-docgen-loader`), + enforce: 'pre', + }); + + return webpackConfig; +} diff --git a/addons/docs/src/frameworks/svelte/sample/MockButton.svelte b/addons/docs/src/frameworks/svelte/sample/MockButton.svelte new file mode 100644 index 00000000000..9aa293e9a5d --- /dev/null +++ b/addons/docs/src/frameworks/svelte/sample/MockButton.svelte @@ -0,0 +1,38 @@ + + + + + + diff --git a/addons/docs/src/frameworks/svelte/svelte-docgen-loader.ts b/addons/docs/src/frameworks/svelte/svelte-docgen-loader.ts new file mode 100644 index 00000000000..ca853d29ebc --- /dev/null +++ b/addons/docs/src/frameworks/svelte/svelte-docgen-loader.ts @@ -0,0 +1,38 @@ +import svelteDoc from 'sveltedoc-parser'; + +import * as path from 'path'; + +/** + * webpack loader for sveltedoc-parser + * @param source raw svelte component + */ +export default async function svelteDocgen(source: string) { + // get filename for source content + // eslint-disable-next-line no-underscore-dangle + const file = path.basename(this._module.resource); + + // set SvelteDoc options + const options = { + fileContent: source, + version: 3, + }; + + let docgen = ''; + + try { + const componentDoc = await svelteDoc.parse(options); + + // populate filename in docgen + componentDoc.name = path.basename(file); + + docgen = ` + export const __docgen = ${JSON.stringify(componentDoc)}; + `; + } catch (error) { + console.error(error); + } + // inject __docgen prop in svelte component + const output = source.replace('', `${docgen}`); + + return output; +} diff --git a/addons/docs/src/frameworks/vue/config.ts b/addons/docs/src/frameworks/vue/config.ts new file mode 100644 index 00000000000..4a4fd37a39f --- /dev/null +++ b/addons/docs/src/frameworks/vue/config.ts @@ -0,0 +1,12 @@ +import { extractArgTypes } from './extractArgTypes'; +import { extractComponentDescription } from '../../lib/docgen'; +import { prepareForInline } from './prepareForInline'; + +export const parameters = { + docs: { + inlineStories: true, + prepareForInline, + extractArgTypes, + extractComponentDescription, + }, +}; diff --git a/addons/docs/src/frameworks/vue/config.tsx b/addons/docs/src/frameworks/vue/config.tsx deleted file mode 100644 index f796827b56b..00000000000 --- a/addons/docs/src/frameworks/vue/config.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import toReact from '@egoist/vue-to-react'; -import { StoryFn } from '@storybook/addons'; -import { addParameters } from '@storybook/client-api'; -import { extractArgTypes } from './extractArgTypes'; -import { extractComponentDescription } from '../../lib/docgen'; - -addParameters({ - docs: { - inlineStories: true, - prepareForInline: (storyFn: StoryFn) => { - const Story = toReact(storyFn()); - return ; - }, - extractArgTypes, - extractComponentDescription, - }, -}); diff --git a/addons/docs/src/frameworks/vue/extractArgTypes.ts b/addons/docs/src/frameworks/vue/extractArgTypes.ts index 8ee12409c1d..ca65e50f1e3 100644 --- a/addons/docs/src/frameworks/vue/extractArgTypes.ts +++ b/addons/docs/src/frameworks/vue/extractArgTypes.ts @@ -1,7 +1,7 @@ import { ArgTypes } from '@storybook/api'; import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen'; -import { convert } from '../../lib/sbtypes'; -import { trimQuotes } from '../../lib/sbtypes/utils'; +import { convert } from '../../lib/convert'; +import { trimQuotes } from '../../lib/convert/utils'; const SECTIONS = ['props', 'events', 'slots']; @@ -15,17 +15,24 @@ export const extractArgTypes: ArgTypesExtractor = (component) => { SECTIONS.forEach((section) => { const props = extractComponentProps(component, section); props.forEach(({ propDef, docgenInfo, jsDocTags }) => { - const { name, type, description, defaultValue, required } = propDef; + const { name, type, description, defaultValue: defaultSummary, required } = propDef; const sbType = section === 'props' ? convert(docgenInfo) : { name: 'void' }; + let defaultValue = defaultSummary && (defaultSummary.detail || defaultSummary.summary); + try { + // eslint-disable-next-line no-eval + defaultValue = eval(defaultValue); + // eslint-disable-next-line no-empty + } catch {} + results[name] = { name, description, type: { required, ...sbType }, - defaultValue: defaultValue && trim(defaultValue.detail || defaultValue.summary), + defaultValue, table: { type, jsDocTags, - defaultValue, + defaultValue: defaultSummary, category: section, }, }; diff --git a/addons/docs/src/frameworks/vue/prepareForInline.ts b/addons/docs/src/frameworks/vue/prepareForInline.ts new file mode 100644 index 00000000000..fdb278ee964 --- /dev/null +++ b/addons/docs/src/frameworks/vue/prepareForInline.ts @@ -0,0 +1,36 @@ +/* eslint-disable react/no-this-in-sfc */ +import React from 'react'; +import Vue from 'vue'; +import { StoryFn, StoryContext } from '@storybook/addons'; + +// Inspired by https://github.com/egoist/vue-to-react, +// modified to store args as props in the root store + +// FIXME get this from @storybook/vue +const COMPONENT = 'STORYBOOK_COMPONENT'; +const VALUES = 'STORYBOOK_VALUES'; + +export const prepareForInline = (storyFn: StoryFn, { args }: StoryContext) => { + const component = storyFn(); + const el = React.useRef(null); + + // FIXME: This recreates the Vue instance every time, which should be optimized + React.useEffect(() => { + const root = new Vue({ + el: el.current, + data() { + return { + [COMPONENT]: component, + [VALUES]: args, + }; + }, + render(h) { + const children = this[COMPONENT] ? [h(this[COMPONENT])] : undefined; + return h('div', { attrs: { id: 'root' } }, children); + }, + }); + return () => root.$destroy(); + }); + + return React.createElement('div', null, React.createElement('div', { ref: el })); +}; diff --git a/addons/docs/src/frameworks/vue/preset.ts b/addons/docs/src/frameworks/vue/preset.ts index f7452659969..9613eaa38b6 100644 --- a/addons/docs/src/frameworks/vue/preset.ts +++ b/addons/docs/src/frameworks/vue/preset.ts @@ -1,7 +1,7 @@ export function webpackFinal(webpackConfig: any = {}, options: any = {}) { webpackConfig.module.rules.push({ test: /\.vue$/, - loader: 'vue-docgen-loader', + loader: require.resolve('vue-docgen-loader', { paths: [require.resolve('@storybook/vue')] }), enforce: 'post', options: { docgenOptions: { diff --git a/addons/docs/src/frameworks/web-components/config.js b/addons/docs/src/frameworks/web-components/config.js index 18eec305f12..12e5c134d2c 100644 --- a/addons/docs/src/frameworks/web-components/config.js +++ b/addons/docs/src/frameworks/web-components/config.js @@ -1,11 +1,10 @@ /* global window */ /* eslint-disable import/no-extraneous-dependencies */ -import { addParameters } from '@storybook/client-api'; import React from 'react'; import { render } from 'lit-html'; import { extractArgTypes, extractComponentDescription } from './custom-elements'; -addParameters({ +export const parameters = { docs: { extractArgTypes, extractComponentDescription, @@ -25,8 +24,7 @@ addParameters({ return React.createElement('div', { ref: this.wrapperRef }); } } - return React.createElement(Story); }, }, -}); +}; diff --git a/addons/docs/src/frameworks/web-components/custom-elements.ts b/addons/docs/src/frameworks/web-components/custom-elements.ts index 9752ca667dd..432f08cab72 100644 --- a/addons/docs/src/frameworks/web-components/custom-elements.ts +++ b/addons/docs/src/frameworks/web-components/custom-elements.ts @@ -1,6 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import { getCustomElements, isValidComponent, isValidMetaData } from '@storybook/web-components'; import { ArgTypes } from '@storybook/api'; +import { logger } from '@storybook/client-logger'; interface TagItem { name: string; @@ -16,6 +17,7 @@ interface Tag { attributes?: TagItem[]; properties?: TagItem[]; events?: TagItem[]; + methods?: TagItem[]; slots?: TagItem[]; cssProperties?: TagItem[]; cssParts?: TagItem[]; @@ -55,40 +57,39 @@ function mapData(data: TagItem[], category: string) { ); } -function isEmpty(obj: object) { - return Object.entries(obj).length === 0 && obj.constructor === Object; -} - -export const extractArgTypesFromElements = (tagName: string, customElements: CustomElements) => { +const getMetaData = (tagName: string, customElements: CustomElements) => { if (!isValidComponent(tagName) || !isValidMetaData(customElements)) { return null; } const metaData = customElements.tags.find( (tag) => tag.name.toUpperCase() === tagName.toUpperCase() ); - const argTypes = { - ...mapData(metaData.attributes, 'attributes'), - ...mapData(metaData.properties, 'properties'), - ...mapData(metaData.events, 'events'), - ...mapData(metaData.slots, 'slots'), - ...mapData(metaData.cssProperties, 'css custom properties'), - ...mapData(metaData.cssParts, 'css shadow parts'), - }; - return argTypes; + if (!metaData) { + logger.warn(`Component not found in custom-elements.json: ${tagName}`); + } + return metaData; +}; + +export const extractArgTypesFromElements = (tagName: string, customElements: CustomElements) => { + const metaData = getMetaData(tagName, customElements); + return ( + metaData && { + ...mapData(metaData.attributes, 'attributes'), + ...mapData(metaData.properties, 'properties'), + ...mapData(metaData.events, 'events'), + ...mapData(metaData.methods, 'methods'), + ...mapData(metaData.slots, 'slots'), + ...mapData(metaData.cssProperties, 'css custom properties'), + ...mapData(metaData.cssParts, 'css shadow parts'), + } + ); }; export const extractArgTypes = (tagName: string) => { - const customElements: CustomElements = getCustomElements(); - return extractArgTypesFromElements(tagName, customElements); + return extractArgTypesFromElements(tagName, getCustomElements()); }; export const extractComponentDescription = (tagName: string) => { - const customElements: CustomElements = getCustomElements(); - if (!isValidComponent(tagName) || !isValidMetaData(customElements)) { - return null; - } - const metaData = customElements.tags.find( - (tag) => tag.name.toUpperCase() === tagName.toUpperCase() - ); + const metaData = getMetaData(tagName, getCustomElements()); return metaData && metaData.description; }; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/arrays.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/arrays.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/arrays.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/arrays.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/enums.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/enums.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/enums.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/enums.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/misc.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/misc.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/misc.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/misc.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/objects.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/objects.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/objects.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/objects.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/react.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/react.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/react.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/react.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/scalars.js b/addons/docs/src/lib/convert/__testfixtures__/proptypes/scalars.js similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/proptypes/scalars.js rename to addons/docs/src/lib/convert/__testfixtures__/proptypes/scalars.js diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/aliases.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/aliases.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/aliases.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/aliases.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/arrays.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/arrays.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/arrays.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/arrays.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/enums.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/enums.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/enums.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/enums.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/functions.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/functions.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/functions.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/functions.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/interfaces.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/interfaces.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/interfaces.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/interfaces.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/intersections.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/intersections.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/intersections.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/intersections.tsx diff --git a/addons/docs/src/lib/convert/__testfixtures__/typescript/optionals.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/optionals.tsx new file mode 100644 index 00000000000..cced6cf2007 --- /dev/null +++ b/addons/docs/src/lib/convert/__testfixtures__/typescript/optionals.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; + +interface Props { + any?: any; + string?: string; + bool?: boolean; + number?: number; + symbol?: symbol; + readonly readonlyPrimitive?: string; +} +export const Component: FC = ({ + any = 'foo', + string = 'bar', + bool = true, + number = 4, + ...rest +}: Props) => { + const props = { any, string, bool, number, ...rest }; + return <>JSON.stringify(props); +}; diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/records.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/records.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/records.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/records.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/scalars.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/scalars.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/scalars.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/scalars.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/tuples.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/tuples.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/tuples.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/tuples.tsx diff --git a/addons/docs/src/lib/sbtypes/__testfixtures__/typescript/unions.tsx b/addons/docs/src/lib/convert/__testfixtures__/typescript/unions.tsx similarity index 100% rename from addons/docs/src/lib/sbtypes/__testfixtures__/typescript/unions.tsx rename to addons/docs/src/lib/convert/__testfixtures__/typescript/unions.tsx diff --git a/addons/docs/src/lib/sbtypes/convert.test.ts b/addons/docs/src/lib/convert/convert.test.ts similarity index 99% rename from addons/docs/src/lib/sbtypes/convert.test.ts rename to addons/docs/src/lib/convert/convert.test.ts index 337586d82f4..0cbdb3a324f 100644 --- a/addons/docs/src/lib/sbtypes/convert.test.ts +++ b/addons/docs/src/lib/convert/convert.test.ts @@ -4,7 +4,7 @@ import { transformSync } from '@babel/core'; import requireFromString from 'require-from-string'; import fs from 'fs'; -import { convert } from './convert'; +import { convert } from './index'; import { normalizeNewlines } from '../utils'; expect.addSnapshotSerializer({ diff --git a/addons/docs/src/lib/convert/flow/convert.ts b/addons/docs/src/lib/convert/flow/convert.ts new file mode 100644 index 00000000000..b9ef15867ea --- /dev/null +++ b/addons/docs/src/lib/convert/flow/convert.ts @@ -0,0 +1,55 @@ +/* eslint-disable no-case-declarations */ +import { SBType } from '@storybook/client-api'; +import { FlowType, FlowSigType, FlowLiteralType } from './types'; + +const isLiteral = (type: FlowType) => type.name === 'literal'; +const toEnumOption = (element: FlowLiteralType) => element.value.replace(/['|"]/g, ''); + +const convertSig = (type: FlowSigType) => { + switch (type.type) { + case 'function': + return { name: 'function' }; + case 'object': + const values: any = {}; + type.signature.properties.forEach((prop) => { + values[prop.key] = convert(prop.value); + }); + return { + name: 'object', + value: values, + }; + default: + throw new Error(`Unknown: ${type}`); + } +}; + +export const convert = (type: FlowType): SBType | void => { + const { name, raw } = type; + const base: any = {}; + if (typeof raw !== 'undefined') base.raw = raw; + switch (type.name) { + case 'literal': + return { ...base, name: 'other', value: type.value }; + case 'string': + case 'number': + case 'symbol': + case 'boolean': { + return { ...base, name }; + } + case 'Array': { + return { ...base, name: 'array', value: type.elements.map(convert) }; + } + case 'signature': + return { ...base, ...convertSig(type) }; + case 'union': + if (type.elements.every(isLiteral)) { + return { ...base, name: 'enum', value: type.elements.map(toEnumOption) }; + } + return { ...base, name, value: type.elements.map(convert) }; + + case 'intersection': + return { ...base, name, value: type.elements.map(convert) }; + default: + return { ...base, name: 'other', value: name }; + } +}; diff --git a/addons/docs/src/lib/sbtypes/index.ts b/addons/docs/src/lib/convert/flow/index.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/index.ts rename to addons/docs/src/lib/convert/flow/index.ts diff --git a/addons/docs/src/lib/convert/flow/types.ts b/addons/docs/src/lib/convert/flow/types.ts new file mode 100644 index 00000000000..b131ac57552 --- /dev/null +++ b/addons/docs/src/lib/convert/flow/types.ts @@ -0,0 +1,56 @@ +interface FlowBaseType { + name: string; + type?: string; + raw?: string; + required?: boolean; +} + +type FlowArgType = FlowType; + +type FlowCombinationType = FlowBaseType & { + name: 'union' | 'intersection'; + elements: FlowType[]; +}; + +type FlowFuncSigType = FlowBaseType & { + name: 'signature'; + type: 'function'; + signature: { + arguments: FlowArgType[]; + return: FlowType; + }; +}; + +type FlowObjectSigType = FlowBaseType & { + name: 'signature'; + type: 'object'; + signature: { + properties: { + key: string; + value: FlowType; + }[]; + }; +}; + +type FlowScalarType = FlowBaseType & { + name: 'any' | 'boolean' | 'number' | 'void' | 'string' | 'symbol'; +}; + +export type FlowLiteralType = FlowBaseType & { + name: 'literal'; + value: string; +}; + +type FlowArrayType = FlowBaseType & { + name: 'Array'; + elements: FlowType[]; +}; + +export type FlowSigType = FlowObjectSigType | FlowFuncSigType; + +export type FlowType = + | FlowScalarType + | FlowLiteralType + | FlowCombinationType + | FlowSigType + | FlowArrayType; diff --git a/addons/docs/src/lib/sbtypes/convert.ts b/addons/docs/src/lib/convert/index.ts similarity index 66% rename from addons/docs/src/lib/sbtypes/convert.ts rename to addons/docs/src/lib/convert/index.ts index 9b7b0fb4df2..f6cd38a7785 100644 --- a/addons/docs/src/lib/sbtypes/convert.ts +++ b/addons/docs/src/lib/convert/index.ts @@ -1,11 +1,13 @@ import { DocgenInfo } from '../docgen/types'; import { convert as tsConvert, TSType } from './typescript'; +import { convert as flowConvert, FlowType } from './flow'; import { convert as propTypesConvert } from './proptypes'; export const convert = (docgenInfo: DocgenInfo) => { - const { type, tsType } = docgenInfo; + const { type, tsType, flowType } = docgenInfo; if (type != null) return propTypesConvert(type); if (tsType != null) return tsConvert(tsType as TSType); + if (flowType != null) return flowConvert(flowType as FlowType); return null; }; diff --git a/addons/docs/src/lib/sbtypes/proptypes/convert.ts b/addons/docs/src/lib/convert/proptypes/convert.ts similarity index 84% rename from addons/docs/src/lib/sbtypes/proptypes/convert.ts rename to addons/docs/src/lib/convert/proptypes/convert.ts index 65a8c6a1b24..bc4da957340 100644 --- a/addons/docs/src/lib/sbtypes/proptypes/convert.ts +++ b/addons/docs/src/lib/convert/proptypes/convert.ts @@ -1,7 +1,7 @@ /* eslint-disable no-case-declarations */ import mapValues from 'lodash/mapValues'; +import { SBType } from '@storybook/client-api'; import { PTType } from './types'; -import { SBType } from '../types'; import { trimQuotes } from '../utils'; const SIGNATURE_REGEXP = /^\(.*\) => /; @@ -43,10 +43,9 @@ export const convert = (type: PTType): SBType | any => { case 'elementType': default: { if (name?.indexOf('|') > 0) { - // react-docgen-typescript-loader doesn't always produce proper - // enum types, possibly due to https://github.com/strothj/react-docgen-typescript-loader/issues/81 - // this hack tries to parse out values from the string and should be - // removed when RDTL gets a little smarter about this + // react-docgen-typescript-plugin doesn't always produce enum-like unions + // (like if a user has turned off shouldExtractValuesFromUnion) so here we + // try to recover and construct one. try { const literalValues = name.split('|').map((v: string) => JSON.parse(v)); return { ...base, name: 'enum', value: literalValues }; diff --git a/addons/docs/src/lib/sbtypes/proptypes/index.ts b/addons/docs/src/lib/convert/proptypes/index.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/proptypes/index.ts rename to addons/docs/src/lib/convert/proptypes/index.ts diff --git a/addons/docs/src/lib/sbtypes/proptypes/types.ts b/addons/docs/src/lib/convert/proptypes/types.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/proptypes/types.ts rename to addons/docs/src/lib/convert/proptypes/types.ts diff --git a/addons/docs/src/lib/sbtypes/typescript/convert.ts b/addons/docs/src/lib/convert/typescript/convert.ts similarity index 96% rename from addons/docs/src/lib/sbtypes/typescript/convert.ts rename to addons/docs/src/lib/convert/typescript/convert.ts index a53d0d79cbc..3affe3e7048 100644 --- a/addons/docs/src/lib/sbtypes/typescript/convert.ts +++ b/addons/docs/src/lib/convert/typescript/convert.ts @@ -1,6 +1,6 @@ /* eslint-disable no-case-declarations */ +import { SBType } from '@storybook/client-api'; import { TSType, TSSigType } from './types'; -import { SBType } from '../types'; const convertSig = (type: TSSigType) => { switch (type.type) { diff --git a/addons/docs/src/lib/sbtypes/typescript/index.ts b/addons/docs/src/lib/convert/typescript/index.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/typescript/index.ts rename to addons/docs/src/lib/convert/typescript/index.ts diff --git a/addons/docs/src/lib/sbtypes/typescript/types.ts b/addons/docs/src/lib/convert/typescript/types.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/typescript/types.ts rename to addons/docs/src/lib/convert/typescript/types.ts diff --git a/addons/docs/src/lib/sbtypes/utils.ts b/addons/docs/src/lib/convert/utils.ts similarity index 100% rename from addons/docs/src/lib/sbtypes/utils.ts rename to addons/docs/src/lib/convert/utils.ts diff --git a/addons/docs/src/lib/docgen/createPropDef.ts b/addons/docs/src/lib/docgen/createPropDef.ts index 4e2b1a6bd3c..c25faea5464 100644 --- a/addons/docs/src/lib/docgen/createPropDef.ts +++ b/addons/docs/src/lib/docgen/createPropDef.ts @@ -5,7 +5,7 @@ import { createSummaryValue } from '../utils'; import { createFlowPropDef } from './flow/createPropDef'; import { isDefaultValueBlacklisted } from './utils/defaultValue'; import { createTsPropDef } from './typeScript/createPropDef'; -import { convert } from '../sbtypes'; +import { convert } from '../convert'; export type PropDefFactory = ( propName: string, diff --git a/addons/docs/src/lib/docgen/extractDocgenProps.ts b/addons/docs/src/lib/docgen/extractDocgenProps.ts index 099e75f3abd..c7a9090adf4 100644 --- a/addons/docs/src/lib/docgen/extractDocgenProps.ts +++ b/addons/docs/src/lib/docgen/extractDocgenProps.ts @@ -33,9 +33,19 @@ export const extractComponentSectionArray = (docgenSection: any) => { const typeSystem = getTypeSystem(docgenSection[0]); const createPropDef = getPropDefFactory(typeSystem); - return docgenSection - .map((item: any) => extractProp(item.name, item, typeSystem, createPropDef)) - .filter(Boolean); + return docgenSection.map((item: any) => { + let sanitizedItem = item; + if (item.type?.elements) { + sanitizedItem = { + ...item, + type: { + ...item.type, + value: item.type.elements, + }, + }; + } + return extractProp(sanitizedItem.name, sanitizedItem, typeSystem, createPropDef); + }); }; export const extractComponentSectionObject = (docgenSection: any) => { diff --git a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts index be904ce7af7..4b563e3f86d 100644 --- a/addons/docs/src/lib/docgen/flow/createPropDef.test.ts +++ b/addons/docs/src/lib/docgen/flow/createPropDef.test.ts @@ -269,6 +269,80 @@ describe('type', () => { expect(type.summary).toBe('number | string'); }); + it('should support nested union elements', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: '"minimum" | "maximum" | UserSize', + elements: [ + { + name: 'literal', + value: '"minimum"', + }, + { + name: 'literal', + value: '"maximum"', + }, + { + name: 'union', + raw: 'string | number', + elements: [ + { + name: 'number', + }, + { + name: 'string', + }, + ], + }, + ], + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('"minimum" | "maximum" | number | string'); + }); + + it('uses raw union value if elements are missing', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: '"minimum" | "maximum" | UserSize', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('"minimum" | "maximum" | UserSize'); + }); + + it('removes a leading | if raw union value is used', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: '| "minimum" | "maximum" | UserSize', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('"minimum" | "maximum" | UserSize'); + }); + + it('even removes extra spaces after a leading | if raw union value is used', () => { + const docgenInfo = createDocgenInfo({ + flowType: { + name: 'union', + raw: '| "minimum" | "maximum" | UserSize', + }, + }); + + const { type } = createFlowPropDef(PROP_NAME, docgenInfo); + + expect(type.summary).toBe('"minimum" | "maximum" | UserSize'); + }); + it('should support intersection', () => { const docgenInfo = createDocgenInfo({ flowType: { diff --git a/addons/docs/src/lib/docgen/flow/createType.ts b/addons/docs/src/lib/docgen/flow/createType.ts index 5cf193fb53b..2e9d86a93a4 100644 --- a/addons/docs/src/lib/docgen/flow/createType.ts +++ b/addons/docs/src/lib/docgen/flow/createType.ts @@ -7,17 +7,41 @@ enum FlowTypesType { SIGNATURE = 'signature', } -interface DocgenFlowUnionType extends DocgenFlowType { - elements: { name: string; value: string }[]; +interface DocgenFlowUnionElement { + name: string; + value?: string; + elements?: DocgenFlowUnionElement[]; + raw?: string; } -function generateUnion({ name, raw, elements }: DocgenFlowUnionType): PropType { - if (raw != null) { - return createSummaryValue(raw); +interface DocgenFlowUnionType extends DocgenFlowType { + elements: DocgenFlowUnionElement[]; +} + +function generateUnionElement({ name, value, elements, raw }: DocgenFlowUnionElement): string { + if (value != null) { + return value; } if (elements != null) { - return createSummaryValue(elements.map((x) => x.value).join(' | ')); + return elements.map(generateUnionElement).join(' | '); + } + + if (raw != null) { + return raw; + } + + return name; +} + +function generateUnion({ name, raw, elements }: DocgenFlowUnionType): PropType { + if (elements != null) { + return createSummaryValue(elements.map(generateUnionElement).join(' | ')); + } + + if (raw != null) { + // Flow Unions can be defined with or without a leading `|` character, so try to remove it. + return createSummaryValue(raw.replace(/^\|\s*/, '')); } return createSummaryValue(name); diff --git a/addons/docs/src/lib/sbtypes/types.ts b/addons/docs/src/lib/sbtypes/types.ts deleted file mode 100644 index d0fbed123f3..00000000000 --- a/addons/docs/src/lib/sbtypes/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -interface SBBaseType { - required?: boolean; - raw?: string; -} - -export type SBScalarType = SBBaseType & { - name: 'boolean' | 'string' | 'number' | 'function'; -}; - -export type SBArrayType = SBBaseType & { - name: 'array'; - value: SBType; -}; -export type SBObjectType = SBBaseType & { - name: 'object'; - value: Record; -}; -export type SBEnumType = SBBaseType & { - name: 'enum'; - value: (string | number)[]; -}; -export type SBIntersectionType = SBBaseType & { - name: 'intersection'; - value: SBType[]; -}; -export type SBUnionType = SBBaseType & { - name: 'union'; - value: SBType[]; -}; -export type SBOtherType = SBBaseType & { - name: 'other'; - value: string; -}; - -export type SBType = - | SBScalarType - | SBEnumType - | SBArrayType - | SBObjectType - | SBIntersectionType - | SBUnionType - | SBOtherType; diff --git a/addons/docs/src/lib/utils.ts b/addons/docs/src/lib/utils.ts index 44725a08695..58f9d75efdf 100644 --- a/addons/docs/src/lib/utils.ts +++ b/addons/docs/src/lib/utils.ts @@ -1,14 +1,14 @@ import { PropSummaryValue } from '@storybook/components'; export const MAX_TYPE_SUMMARY_LENGTH = 90; -export const MAX_DEFALUT_VALUE_SUMMARY_LENGTH = 50; +export const MAX_DEFAULT_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; + return value.length > MAX_DEFAULT_VALUE_SUMMARY_LENGTH; } export function createSummaryValue(summary: string, detail?: string): PropSummaryValue { diff --git a/addons/docs/src/mdx/__testfixtures__/csf-imports.mdx b/addons/docs/src/mdx/__testfixtures__/csf-imports.mdx new file mode 100644 index 00000000000..3ba992d778a --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/csf-imports.mdx @@ -0,0 +1,16 @@ +import { Story, Meta, Canvas } from '@storybook/addon-docs/blocks'; +import { Welcome, Button } from '@storybook/angular/demo'; +import * as MyStories from './My.stories'; +import { Other } from './Other.stories'; + + + +# Stories from CSF imports + + + + + + + + diff --git a/addons/docs/src/mdx/__testfixtures__/csf-imports.output.snapshot b/addons/docs/src/mdx/__testfixtures__/csf-imports.output.snapshot new file mode 100644 index 00000000000..1cadb82f9dd --- /dev/null +++ b/addons/docs/src/mdx/__testfixtures__/csf-imports.output.snapshot @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docs-mdx-compiler-plugin csf-imports.mdx 1`] = ` +"/* @jsx mdx */ +import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; + +import { Story, Meta, Canvas } from '@storybook/addon-docs/blocks'; +import { Welcome, Button } from '@storybook/angular/demo'; +import * as MyStories from './My.stories'; +import { Other } from './Other.stories'; + +const makeShortcode = (name) => + function MDXDefaultShortcode(props) { + console.warn( + 'Component ' + + name + + ' was not imported, exported, or provided by MDXProvider as global scope' + ); + return
; + }; + +const layoutProps = {}; +const MDXLayout = 'wrapper'; +function MDXContent({ components, ...props }) { + return ( + + +

{\`Stories from CSF imports\`}

+ + + + + +
+ ); +} + +MDXContent.isMDXComponent = true; + +export const _Basic_ = MyStories.Basic; + +export const _Other_ = Other; + +export const _Foo_ = MyStories.Foo; +_Foo_.storyName = 'renamed'; + +const componentMeta = { title: 'MDX/CSF imports', includeStories: ['_Basic_', '_Other_', '_Foo_'] }; + +const mdxStoryNameToKey = { _Basic_: '_Basic_', _Other_: '_Other_', renamed: '_Foo_' }; + +componentMeta.parameters = componentMeta.parameters || {}; +componentMeta.parameters.docs = { + ...(componentMeta.parameters.docs || {}), + page: () => ( + + + + ), +}; + +export default componentMeta; +" +`; diff --git a/addons/docs/src/mdx/__testfixtures__/previews.mdx b/addons/docs/src/mdx/__testfixtures__/previews.mdx index 8ec8874d102..bc69d50c787 100644 --- a/addons/docs/src/mdx/__testfixtures__/previews.mdx +++ b/addons/docs/src/mdx/__testfixtures__/previews.mdx @@ -1,13 +1,13 @@ import { Button } from '@storybook/react/demo'; -import { Preview, Story, Meta } from '@storybook/addon-docs/blocks'; +import { Canvas, Story, Meta } from '@storybook/addon-docs/blocks'; -# Preview +# Canvas -Previews can contain normal components, stories, and story references +Canvases can contain normal components, stories, and story references - + @@ -16,11 +16,10 @@ Previews can contain normal components, stories, and story references - + +Canvas wthout a story -Preview wthout a story - - + - \ No newline at end of file + diff --git a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot index 17b6711fa28..2e6da3e0bd4 100644 --- a/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/previews.output.snapshot @@ -5,7 +5,7 @@ exports[`docs-mdx-compiler-plugin previews.mdx 1`] = ` import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; -import { Preview, Story, Meta } from '@storybook/addon-docs/blocks'; +import { Canvas, Story, Meta } from '@storybook/addon-docs/blocks'; const makeShortcode = (name) => function MDXDefaultShortcode(props) { @@ -30,9 +30,9 @@ function MDXContent({ components, ...props }) { }} mdxType=\\"Meta\\" /> -

{\`Preview\`}

-

{\`Previews can contain normal components, stories, and story references\`}

- +

{\`Canvas\`}

+

{\`Canvases can contain normal components, stories, and story references\`}

+ @@ -41,11 +41,11 @@ function MDXContent({ components, ...props }) { -
-

{\`Preview wthout a story\`}

- + +

{\`Canvas wthout a story\`}

+ -
+ ); } diff --git a/addons/docs/src/mdx/__testfixtures__/story-args.mdx b/addons/docs/src/mdx/__testfixtures__/story-args.mdx index 57a20578d9e..6472dbd22a3 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-args.mdx +++ b/addons/docs/src/mdx/__testfixtures__/story-args.mdx @@ -5,6 +5,12 @@ import { Story, Meta } from '@storybook/addon-docs/blocks'; # Args - - +export const Template = (args) => ; + + + {Template.bind({})} diff --git a/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot b/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot index ef73decd103..04a4e851ad9 100644 --- a/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot +++ b/addons/docs/src/mdx/__testfixtures__/story-args.output.snapshot @@ -6,7 +6,7 @@ import { assertIsFn, AddContext } from '@storybook/addon-docs/blocks'; import { Button } from '@storybook/react/demo'; import { Story, Meta } from '@storybook/addon-docs/blocks'; - +export const Template = (args) => ; const makeShortcode = (name) => function MDXDefaultShortcode(props) { console.warn( @@ -17,13 +17,16 @@ const makeShortcode = (name) => return
; }; -const layoutProps = {}; +const layoutProps = { + Template, +}; const MDXLayout = 'wrapper'; function MDXContent({ components, ...props }) { return (

{\`Args\`}

+ - + {Template.bind({})}
); @@ -48,7 +51,7 @@ function MDXContent({ components, ...props }) { MDXContent.isMDXComponent = true; -export const componentNotes = () => ; +export const componentNotes = Template.bind({}); componentNotes.storyName = 'component notes'; componentNotes.argTypes = { a: { @@ -62,7 +65,7 @@ componentNotes.args = { a: 1, b: 2, }; -componentNotes.parameters = { storySource: { source: '' } }; +componentNotes.parameters = { storySource: { source: 'args => ' } }; const componentMeta = { title: 'Button', includeStories: ['componentNotes'] }; diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.js b/addons/docs/src/mdx/mdx-compiler-plugin.js index be7d6952ba2..82c8053c352 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.js @@ -8,7 +8,7 @@ const jsStringEscape = require('js-string-escape'); // story in the contents const STORY_REGEX = /^]/; -const PREVIEW_REGEX = /^]/; +const CANVAS_REGEX = /^<(Preview|Canvas)[\s>]/; const META_REGEX = /^]/; const RESERVED = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/; @@ -41,63 +41,126 @@ function genAttribute(key, element) { return undefined; } +function genImportStory(ast, storyDef, storyName, context) { + const { code: story } = generate(storyDef.expression, {}); + + const storyKey = `_${story.split('.').pop()}_`; + + const statements = [`export const ${storyKey} = ${story};`]; + if (storyName) { + // eslint-disable-next-line no-param-reassign + context.storyNameToKey[storyName] = storyKey; + statements.push(`${storyKey}.storyName = '${storyName}';`); + } else { + // eslint-disable-next-line no-param-reassign + context.storyNameToKey[storyKey] = storyKey; + ast.openingElement.attributes.push({ + type: 'JSXAttribute', + name: { + type: 'JSXIdentifier', + name: 'name', + }, + value: { + type: 'StringLiteral', + value: storyKey, + }, + }); + } + return { + [storyKey]: statements.join('\n'), + }; +} + +function getBodyPart(bodyNode, context) { + const body = bodyNode.type === 'JSXExpressionContainer' ? bodyNode.expression : bodyNode; + let sourceBody = body; + if ( + body.type === 'CallExpression' && + body.callee.type === 'MemberExpression' && + body.callee.object.type === 'Identifier' && + body.callee.property.type === 'Identifier' && + body.callee.property.name === 'bind' && + (body.arguments.length === 0 || + (body.arguments.length === 1 && + body.arguments[0].type === 'ObjectExpression' && + body.arguments[0].properties.length === 0)) + ) { + const bound = body.callee.object.name; + const namedExport = context.namedExports[bound]; + if (namedExport) { + sourceBody = namedExport; + } + } + + const { code: storyCode } = generate(body, {}); + const { code: sourceCode } = generate(sourceBody, {}); + return { storyCode, sourceCode, body }; +} + function genStoryExport(ast, context) { let storyName = getAttr(ast.openingElement, 'name'); let storyId = getAttr(ast.openingElement, 'id'); + const storyAttr = getAttr(ast.openingElement, 'story'); storyName = storyName && storyName.value; storyId = storyId && storyId.value; - if (!storyId && !storyName) { - throw new Error('Expected a story name or ID attribute'); + if (!storyId && !storyName && !storyAttr) { + throw new Error('Expected a Story name, id, or story attribute'); } // We don't generate exports for story references or the smart "current story" - if (storyId || !storyName) { + if (storyId) { return null; } - // console.log('genStoryExport', JSON.stringify(ast, null, 2)); + if (storyAttr) { + return genImportStory(ast, storyAttr, storyName, context); + } const statements = []; const storyKey = getStoryKey(storyName, context.counter); const bodyNodes = ast.children.filter((n) => n.type !== 'JSXText'); let storyCode = null; + let sourceCode = null; let storyVal = null; if (!bodyNodes.length) { // plain text node const { code } = generate(ast.children[0], {}); storyCode = `'${code}'`; + sourceCode = storyCode; storyVal = `() => ( ${storyCode} )`; } else { - const bodyParts = bodyNodes.map((bodyNode) => { - const body = bodyNode.type === 'JSXExpressionContainer' ? bodyNode.expression : bodyNode; - const { code } = generate(body, {}); - return { code, body }; - }); + const bodyParts = bodyNodes.map((bodyNode) => getBodyPart(bodyNode, context)); // if we have more than two children // 1. Add line breaks // 2. Enclose in <> ... - storyCode = bodyParts.map(({ code }) => code).join('\n'); + storyCode = bodyParts.map(({ storyCode: code }) => code).join('\n'); + sourceCode = bodyParts.map(({ sourceCode: code }) => code).join('\n'); const storyReactCode = bodyParts.length > 1 ? `<>\n${storyCode}\n` : storyCode; // keep track if an indentifier or function call // avoid breaking change for 5.3 - switch (bodyParts.length === 1 && bodyParts[0].body.type) { - // We don't know what type the identifier is, but this code - // assumes it's a function from CSF. Let's see who complains! - case 'Identifier': - storyVal = `assertIsFn(${storyCode})`; - break; - case 'ArrowFunctionExpression': - storyVal = `(${storyCode})`; - break; - default: - storyVal = `() => ( + const BIND_REGEX = /\.bind\(.*\)/; + if (bodyParts.length === 1 && BIND_REGEX.test(bodyParts[0].storyCode)) { + storyVal = bodyParts[0].storyCode; + } else { + switch (bodyParts.length === 1 && bodyParts[0].body.type) { + // We don't know what type the identifier is, but this code + // assumes it's a function from CSF. Let's see who complains! + case 'Identifier': + storyVal = `assertIsFn(${storyCode})`; + break; + case 'ArrowFunctionExpression': + storyVal = `(${storyCode})`; + break; + default: + storyVal = `() => ( ${storyReactCode} )`; - break; + break; + } } } @@ -114,7 +177,7 @@ function genStoryExport(ast, context) { let parameters = getAttr(ast.openingElement, 'parameters'); parameters = parameters && parameters.expression; - const source = jsStringEscape(storyCode); + const source = jsStringEscape(sourceCode); const sourceParam = `storySource: { source: '${source}' }`; if (parameters) { const { code: params } = generate(parameters, {}); @@ -138,22 +201,22 @@ function genStoryExport(ast, context) { }; } -function genPreviewExports(ast, context) { - // console.log('genPreviewExports', JSON.stringify(ast, null, 2)); - - const previewExports = {}; +function genCanvasExports(ast, context) { + const canvasExports = {}; for (let i = 0; i < ast.children.length; i += 1) { const child = ast.children[i]; if (child.type === 'JSXElement' && child.openingElement.name.name === 'Story') { const storyExport = genStoryExport(child, context); + const { code } = generate(child, {}); + child.value = code; if (storyExport) { - Object.assign(previewExports, storyExport); + Object.assign(canvasExports, storyExport); // eslint-disable-next-line no-param-reassign context.counter += 1; } } } - return previewExports; + return canvasExports; } function genMeta(ast, options) { @@ -203,15 +266,27 @@ function getExports(node, counter, options) { // Single story const ast = parser.parseExpression(value, { plugins: ['jsx'] }); const storyExport = genStoryExport(ast, counter); + const { code } = generate(ast, {}); + // eslint-disable-next-line no-param-reassign + node.value = code; return storyExport && { stories: storyExport }; } - if (PREVIEW_REGEX.exec(value)) { - // Preview, possibly containing multiple stories + if (CANVAS_REGEX.exec(value)) { + // Canvas/Preview, possibly containing multiple stories const ast = parser.parseExpression(value, { plugins: ['jsx'] }); - return { stories: genPreviewExports(ast, counter) }; + + const canvasExports = genCanvasExports(ast, counter); + + // We're overwriting the Canvas tag here with a version that + // has the `name` attribute (e.g. ``) + // even if the user didn't provide one. We need the name attribute when + // we render the node at runtime. + const { code } = generate(ast, {}); + // eslint-disable-next-line no-param-reassign + node.value = code; + return { stories: canvasExports }; } if (META_REGEX.exec(value)) { - // Preview, possibly containing multiple stories const ast = parser.parseExpression(value, { plugins: ['jsx'] }); return { meta: genMeta(ast, options) }; } @@ -253,19 +328,59 @@ const hasStoryChild = (node) => { return null; }; -function extractExports(node, options) { - node.children.forEach((child) => { +const getMdxSource = (children) => + encodeURI( + children + .map( + (el) => + generate(el, { + quotes: 'double', + }).code + ) + .join('\n') + ); + +// Parse out the named exports from a node, where the key +// is the variable name and the value is the AST of the +// variable declaration initializer +const getNamedExports = (node) => { + const namedExports = {}; + const ast = parser.parse(node.value, { + sourceType: 'module', + presets: ['env'], + plugins: ['jsx'], + }); + if (ast.type === 'File' && ast.program.type === 'Program' && ast.program.body.length === 1) { + const exported = ast.program.body[0]; + if ( + exported.type === 'ExportNamedDeclaration' && + exported.declaration.type === 'VariableDeclaration' && + exported.declaration.declarations.length === 1 + ) { + const declaration = exported.declaration.declarations[0]; + if (declaration.type === 'VariableDeclarator' && declaration.id.type === 'Identifier') { + const { name } = declaration.id; + namedExports[name] = declaration.init; + } + } + } + return namedExports; +}; + +function extractExports(root, options) { + const namedExports = {}; + root.children.forEach((child) => { if (child.type === 'jsx') { try { const ast = parser.parseExpression(child.value, { plugins: ['jsx'] }); if ( ast.openingElement && ast.openingElement.type === 'JSXOpeningElement' && - ast.openingElement.name.name === 'Preview' && + ['Preview', 'Canvas'].includes(ast.openingElement.name.name) && !hasStoryChild(ast) ) { - const previewAst = ast.openingElement; - previewAst.attributes.push({ + const canvasAst = ast.openingElement; + canvasAst.attributes.push({ type: 'JSXAttribute', name: { type: 'JSXIdentifier', @@ -273,16 +388,7 @@ function extractExports(node, options) { }, value: { type: 'StringLiteral', - value: encodeURI( - ast.children - .map( - (el) => - generate(el, { - quotes: 'double', - }).code - ) - .join('\n') - ), + value: getMdxSource(ast.children), }, }); } @@ -303,18 +409,21 @@ function extractExports(node, options) { * */ } + } else if (child.type === 'export') { + Object.assign(namedExports, getNamedExports(child)); } }); // we're overriding default export - const defaultJsx = mdxToJsx.toJSX(node, {}, { ...options, skipExport: true }); const storyExports = []; const includeStories = []; let metaExport = null; const context = { counter: 0, storyNameToKey: {}, + root, + namedExports, }; - node.children.forEach((n) => { + root.children.forEach((n) => { const exports = getExports(n, context, options); if (exports) { const { stories, meta } = exports; @@ -343,6 +452,7 @@ function extractExports(node, options) { } metaExport.includeStories = JSON.stringify(includeStories); + const defaultJsx = mdxToJsx.toJSX(root, {}, { ...options, skipExport: true }); const fullJsx = [ 'import { assertIsFn, AddContext } from "@storybook/addon-docs/blocks";', defaultJsx, @@ -358,7 +468,7 @@ function extractExports(node, options) { function createCompiler(mdxOptions) { return function compiler(options = {}) { - this.Compiler = (tree) => extractExports(tree, options, mdxOptions); + this.Compiler = (root) => extractExports(root, options, mdxOptions); }; } diff --git a/addons/docs/src/mdx/mdx-compiler-plugin.test.js b/addons/docs/src/mdx/mdx-compiler-plugin.test.js index 0a01ee227d3..d48101490b8 100644 --- a/addons/docs/src/mdx/mdx-compiler-plugin.test.js +++ b/addons/docs/src/mdx/mdx-compiler-plugin.test.js @@ -40,6 +40,6 @@ describe('docs-mdx-compiler-plugin', () => { it('errors on missing story props', async () => { await expect( generate(path.resolve(__dirname, './__testfixtures__/story-missing-props.mdx')) - ).rejects.toThrow('Expected a story name or ID attribute'); + ).rejects.toThrow('Expected a Story name, id, or story attribute'); }); }); diff --git a/addons/docs/src/preset.ts b/addons/docs/src/preset.ts index e7348adfb3a..5f4ea58dc41 100644 --- a/addons/docs/src/preset.ts +++ b/addons/docs/src/preset.ts @@ -7,6 +7,9 @@ const getFrameworkPresets = (framework: string) => { } }; -module.exports = ({ framework }: any) => { - return [require.resolve('./frameworks/common/preset'), ...getFrameworkPresets(framework)]; +module.exports = (storybookOptions: any, presetOptions: any) => { + return [ + { name: require.resolve('./frameworks/common/preset'), options: presetOptions }, + ...getFrameworkPresets(storybookOptions.framework), + ]; }; diff --git a/addons/docs/src/shared.ts b/addons/docs/src/shared.ts index 215736fd9e5..178bf46ca38 100644 --- a/addons/docs/src/shared.ts +++ b/addons/docs/src/shared.ts @@ -1,3 +1,28 @@ export const ADDON_ID = 'storybook/docs'; export const PANEL_ID = `${ADDON_ID}/panel`; export const PARAM_KEY = `docs`; + +export const SNIPPET_RENDERED = `${ADDON_ID}/snippet-rendered`; + +export enum SourceType { + /** + * AUTO is the default + * + * Use the CODE logic if: + * - the user has set a custom source snippet in `docs.source.code` story parameter + * - the story is not an args-based story + * + * Use the DYNAMIC rendered snippet if the story is an args story + */ + AUTO = 'auto', + + /** + * Render the code extracted by source-loader + */ + CODE = 'code', + + /** + * Render dynamically-rendered source snippet from the story's virtual DOM (currently React only) + */ + DYNAMIC = 'dynamic', +} diff --git a/addons/docs/src/typings.d.ts b/addons/docs/src/typings.d.ts index 91ec717a4f9..b4dfdb4df18 100644 --- a/addons/docs/src/typings.d.ts +++ b/addons/docs/src/typings.d.ts @@ -7,3 +7,7 @@ declare module 'babel-plugin-react-docgen'; declare module 'require-from-string'; declare module 'styled-components'; declare module 'acorn-jsx'; + +declare module 'sveltedoc-parser' { + export function parse(options: any): Promise; +} diff --git a/addons/docs/vue/README.md b/addons/docs/vue/README.md index 925e4c7a3f2..60bf61fee6c 100644 --- a/addons/docs/vue/README.md +++ b/addons/docs/vue/README.md @@ -110,7 +110,7 @@ module.exports = { Finally, you can create MDX files like this: ```md -import { Meta, Story, Props } from '@storybook/addon-docs/blocks'; +import { Meta, Story, ArgsTable } from '@storybook/addon-docs/blocks'; import { InfoButton } from './InfoButton.vue'; @@ -124,9 +124,9 @@ Some **markdown** description, or whatever you want. template: '', }} -## Props +## ArgsTable - + ``` Yes, it's redundant to declare `component` twice. [Coming soon](https://github.com/storybookjs/storybook/issues/8685). diff --git a/addons/essentials/README.md b/addons/essentials/README.md index f89346d497d..5fffed589c3 100644 --- a/addons/essentials/README.md +++ b/addons/essentials/README.md @@ -10,8 +10,10 @@ Storybook essentials includes the following addons. Addons can be disabled and r - [Actions](https://github.com/storybookjs/storybook/tree/next/addons/actions) - [Backgrounds](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds) +- [Controls](https://github.com/storybookjs/storybook/tree/next/addons/controls) - [Docs](https://github.com/storybookjs/storybook/tree/next/addons/docs) - [Viewport](https://github.com/storybookjs/storybook/tree/next/addons/viewport) +- [Toolbars](https://github.com/storybookjs/storybook/tree/next/addons/toolbars) ## Installation @@ -50,4 +52,4 @@ module.exports = { }; ``` -Valid addon keys include: `actions`, `backgrounds`, `docs`, `viewport`. +Valid addon keys include: `actions`, `backgrounds`, `controls`, `docs`, `viewport`, `toolbars`. diff --git a/addons/essentials/package.json b/addons/essentials/package.json index 08f26dc3314..9fda9330957 100644 --- a/addons/essentials/package.json +++ b/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", @@ -22,41 +22,56 @@ "files": [ "dist/**/*", "README.md", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addon-actions": "6.0.0-beta.23", - "@storybook/addon-backgrounds": "6.0.0-beta.23", - "@storybook/addon-docs": "6.0.0-beta.23", - "@storybook/addon-viewport": "6.0.0-beta.23", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/node-logger": "6.0.0-beta.23", + "@storybook/addon-actions": "6.1.0-alpha.14", + "@storybook/addon-backgrounds": "6.1.0-alpha.14", + "@storybook/addon-controls": "6.1.0-alpha.14", + "@storybook/addon-docs": "6.1.0-alpha.14", + "@storybook/addon-toolbars": "6.1.0-alpha.14", + "@storybook/addon-viewport": "6.1.0-alpha.14", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/node-logger": "6.1.0-alpha.14", "core-js": "^3.0.1", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "react-is": "^16.8.0", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1" }, "devDependencies": { + "@babel/core": "^7.9.6", + "@storybook/vue": "6.1.0-alpha.14", "@types/jest": "^25.1.1", "@types/webpack-env": "^1.15.2" }, "peerDependencies": { + "@babel/core": "^7.9.6", + "@storybook/vue": "6.1.0-alpha.14", "babel-loader": "^8.0.0", - "react": "^16.8.0", - "react-dom": "*", - "react-is": "^16.8.0" + "webpack": ">=4" + }, + "peerDependenciesMeta": { + "@storybook/vue": { + "optional": true + }, + "webpack": { + "optional": true + } }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/essentials/src/index.ts b/addons/essentials/src/index.ts index d95fb32acf8..5a3eb4b928b 100644 --- a/addons/essentials/src/index.ts +++ b/addons/essentials/src/index.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import path, { join } from 'path'; import { logger } from '@storybook/node-logger'; interface PresetOptions { @@ -27,14 +27,36 @@ export function addons(options: PresetOptions = {}) { return name?.startsWith(addon); }); if (existingAddon) { - logger.warn(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`); + logger.info(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`); } return !!existingAddon; }; const main = requireMain(options.configDir); - return ['actions', 'docs', 'backgrounds', 'viewport'] - .filter((key) => (options as any)[key] !== false) - .map((key) => `@storybook/addon-${key}`) - .filter((addon) => !checkInstalled(addon, main)); + return ( + ['actions', 'docs', 'controls', 'backgrounds', 'viewport', 'toolbars'] + .filter((key) => (options as any)[key] !== false) + .map((key) => `@storybook/addon-${key}`) + .filter((addon) => !checkInstalled(addon, main)) + // Use `require.resolve` to ensure Yarn PnP compatibility + // Files of various addons should be resolved in the context of `addon-essentials` as they are listed as deps here + // and not in `@storybook/core` nor in SB user projects. If `@storybook/core` make the require itself Yarn 2 will + // throw an error saying that the package to require must be added as a dependency. Doing `require.resolve` will + // allow `@storybook/core` to work with absolute path directly, no more require of dep no more issue. + // File to load can be `preset.js`, `register.js`, or the package entry point, so we need to check all these cases + // as it's done in `lib/core/src/server/presets.js`. + .map((addon) => { + try { + return require.resolve(join(addon, 'preset')); + // eslint-disable-next-line no-empty + } catch (err) {} + + try { + return require.resolve(join(addon, 'register')); + // eslint-disable-next-line no-empty + } catch (err) {} + + return require.resolve(addon); + }) + ); } diff --git a/addons/events/package.json b/addons/events/package.json index 320a0c669a7..435d1d15aa1 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -25,41 +25,38 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "format-json": "^1.0.3", "lodash": "^4.17.15", "prop-types": "^15.7.2", "react": "^16.8.3", + "react-dom": "^16.8.3", "react-lifecycles-compat": "^3.0.4", - "react-textarea-autosize": "^8.0.1", + "react-textarea-autosize": "^8.1.1", "regenerator-runtime": "^0.13.3" }, "devDependencies": { "@types/webpack-env": "^1.15.2" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/google-analytics/package.json b/addons/google-analytics/package.json index c0028ad4fbc..1e31726b486 100644 --- a/addons/google-analytics/package.json +++ b/addons/google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-google-analytics", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook addon for google analytics", "keywords": [ "addon", @@ -20,20 +20,17 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", + "react": "^16.8.3", + "react-dom": "^16.8.3", "react-ga": "^2.5.7", "regenerator-runtime": "^0.13.3" }, - "peerDependencies": { - "prop-types": "^15.6.0", - "react": "^15.6.2 || ^16.0", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a" } diff --git a/addons/graphql/package.json b/addons/graphql/package.json index f1681ae0787..9bf9669acab 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "addon", @@ -23,16 +23,16 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-classes": "^7.9.2", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", + "@babel/core": "^7.11.5", + "@babel/plugin-transform-classes": "^7.10.4", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", "@types/webpack": "^4.41.9", "babel-loader": "^8.0.6", "core-js": "^3.0.1", @@ -40,21 +40,19 @@ "graphiql": "^0.17.5", "graphql": "^15.0.0", "prop-types": "^15.7.2", + "react": "^16.8.3", + "react-dom": "^16.8.3", "regenerator-runtime": "^0.13.3", "webpack": "^4.43.0" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/graphql/src/manager.tsx b/addons/graphql/src/manager.tsx index 1febdc1fce9..7a858517da8 100644 --- a/addons/graphql/src/manager.tsx +++ b/addons/graphql/src/manager.tsx @@ -15,7 +15,7 @@ const GQL: FunctionComponent = ({ active }) => { return active ? ( {({ api, state }: Combo) => { - const story = api.getData(state.storyId); + const story = api.getData(state.storyId, state.refId); const parameters = story ? api.getCurrentParameter<{ query: string; diff --git a/addons/jest/README.md b/addons/jest/README.md index 10b97de36c5..1931727b5a0 100644 --- a/addons/jest/README.md +++ b/addons/jest/README.md @@ -171,7 +171,7 @@ In your `.storybook/preview.ts`: import { addDecorator } from '@storybook/angular'; import { withTests } from '@storybook/addon-jest'; -import * as results from '../.jest-test-results.json'; +import results from '../.jest-test-results.json'; addDecorator( withTests({ diff --git a/addons/jest/package.json b/addons/jest/package.json index 7576417f55d..a6d5143312b 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -29,20 +29,21 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", + "react-dom": "^16.8.3", "react-sizeme": "^2.5.2", "regenerator-runtime": "^0.13.3", "upath": "^1.1.0" @@ -50,18 +51,14 @@ "devDependencies": { "@types/webpack-env": "^1.15.2" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/knobs/README.md b/addons/knobs/README.md index a7bcd2683c4..ee3daace483 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -400,7 +400,7 @@ const value = radios(label, options, defaultValue, groupId); Configurable UI for selecting a value from a set of options. ```js -import { optionsKnob as options } from '@storybook/addon-knobs'; +import { optionsKnob } from '@storybook/addon-knobs'; const label = 'Fruits'; const valuesObj = { @@ -414,6 +414,15 @@ const optionsObj = { }; const groupId = 'GROUP-ID1'; +const value = optionsKnob(label, valuesObj, defaultValue, optionsObj, groupId); +``` + +Alternatively you can use this import: +``` +import { optionsKnob as options } from '@storybook/addon-knobs'; + +... + const value = options(label, valuesObj, defaultValue, optionsObj, groupId); ``` diff --git a/addons/knobs/package.json b/addons/knobs/package.json index b1e9eee3ef5..9c285fc6001 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-knobs", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook Addon Prop Editor Component", "keywords": [ "addon", @@ -23,20 +23,19 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/channels": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", - "@types/react-color": "^3.0.1", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/channels": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "copy-to-clipboard": "^3.0.8", "core-js": "^3.0.1", "escape-html": "^1.0.3", @@ -45,7 +44,9 @@ "lodash": "^4.17.15", "prop-types": "^15.7.2", "qs": "^6.6.0", + "react": "^16.8.3", "react-color": "^2.17.0", + "react-dom": "^16.8.3", "react-lifecycles-compat": "^3.0.4", "react-select": "^3.0.8", "regenerator-runtime": "^0.13.3" @@ -53,23 +54,20 @@ "devDependencies": { "@types/enzyme": "^3.10.5", "@types/escape-html": "0.0.20", + "@types/react-color": "^3.0.1", "@types/react-lifecycles-compat": "^3.0.1", "@types/react-select": "^3.0.12", "@types/webpack-env": "^1.15.2", "enzyme": "^3.11.0" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/knobs/src/__types__/knob-test-cases.ts b/addons/knobs/src/__types__/knob-test-cases.ts index 19a147f5000..1857fcb7ce1 100644 --- a/addons/knobs/src/__types__/knob-test-cases.ts +++ b/addons/knobs/src/__types__/knob-test-cases.ts @@ -112,6 +112,14 @@ expectKnobOfType( select('select with string enum options', ButtonVariant, ButtonVariant.primary) ); +expectKnobOfType( + select( + 'select with an undefined in array', + ['Apple', 'Banana', 'Grapes', undefined, null, false] as const, + undefined + ) +); + expectKnobOfType( select('select with null option', { a: 'Option', b: null }, null, groupId) ); diff --git a/addons/knobs/src/components/types/Checkboxes.tsx b/addons/knobs/src/components/types/Checkboxes.tsx index 4c343d45064..56fc40e993e 100644 --- a/addons/knobs/src/components/types/Checkboxes.tsx +++ b/addons/knobs/src/components/types/Checkboxes.tsx @@ -78,7 +78,7 @@ export default class CheckboxesType extends Component) => { + private handleChange = (e: ChangeEvent) => { const { onChange } = this.props; const currentValue = (e.target as HTMLInputElement).value; const { values } = this.state; @@ -94,10 +94,10 @@ export default class CheckboxesType extends Component + private renderCheckboxList = ({ options }: CheckboxesTypeKnob) => Object.keys(options).map((key) => this.renderCheckbox(key, options[key])); - renderCheckbox = (label: string, value: string) => { + private renderCheckbox = (label: string, value: string) => { const { knob } = this.props; const { name } = knob; const id = `${name}-${value}`; diff --git a/addons/knobs/src/components/types/Color.tsx b/addons/knobs/src/components/types/Color.tsx index c738e2763ad..bd8bd751439 100644 --- a/addons/knobs/src/components/types/Color.tsx +++ b/addons/knobs/src/components/types/Color.tsx @@ -95,7 +95,7 @@ export default class ColorType extends Component }); }; - handleClick = () => { + private handleClick = () => { const { displayColorPicker } = this.state; this.setState({ @@ -103,7 +103,7 @@ export default class ColorType extends Component }); }; - handleChange = (color: ColorResult) => { + private handleChange = (color: ColorResult) => { const { onChange } = this.props; onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`); diff --git a/addons/knobs/src/components/types/Date.tsx b/addons/knobs/src/components/types/Date.tsx index 93753275cb6..b28e58b0160 100644 --- a/addons/knobs/src/components/types/Date.tsx +++ b/addons/knobs/src/components/types/Date.tsx @@ -78,7 +78,7 @@ export default class DateType extends Component { } } - onDateChange = (e: ChangeEvent) => { + private onDateChange = (e: ChangeEvent) => { const { knob, onChange } = this.props; const { state } = this; @@ -99,7 +99,7 @@ export default class DateType extends Component { } }; - onTimeChange = (e: ChangeEvent) => { + private onTimeChange = (e: ChangeEvent) => { const { knob, onChange } = this.props; const { state } = this; diff --git a/addons/knobs/src/components/types/Number.tsx b/addons/knobs/src/components/types/Number.tsx index bc242b009ef..53389c226df 100644 --- a/addons/knobs/src/components/types/Number.tsx +++ b/addons/knobs/src/components/types/Number.tsx @@ -80,7 +80,7 @@ export default class NumberType extends Component { return nextProps.knob.value !== knob.value; } - handleChange = (event: ChangeEvent) => { + private handleChange = (event: ChangeEvent) => { const { onChange } = this.props; const { value } = event.target; diff --git a/addons/knobs/src/components/types/Radio.tsx b/addons/knobs/src/components/types/Radio.tsx index 7cee7cfeea4..68bff78d68c 100644 --- a/addons/knobs/src/components/types/Radio.tsx +++ b/addons/knobs/src/components/types/Radio.tsx @@ -59,14 +59,14 @@ class RadiosType extends Component { static deserialize = (value: RadiosTypeKnobValue) => value; - renderRadioButtonList({ options }: RadiosTypeKnob) { + private renderRadioButtonList({ options }: RadiosTypeKnob) { if (Array.isArray(options)) { return options.map((val) => this.renderRadioButton(val, val)); } return Object.keys(options).map((key) => this.renderRadioButton(key, options[key])); } - renderRadioButton(label: string, value: RadiosTypeKnobValue) { + private renderRadioButton(label: string, value: RadiosTypeKnobValue) { const opts = { label, value }; const { onChange, knob } = this.props; const { name } = knob; diff --git a/addons/knobs/src/components/types/Select.tsx b/addons/knobs/src/components/types/Select.tsx index 49c87db80cf..0dd8e146c98 100644 --- a/addons/knobs/src/components/types/Select.tsx +++ b/addons/knobs/src/components/types/Select.tsx @@ -4,13 +4,13 @@ import PropTypes from 'prop-types'; import { Form } from '@storybook/components'; import { KnobControlConfig, KnobControlProps } from './types'; -export type SelectTypeKnobValue = string | number | null | undefined | PropertyKey[]; +export type SelectTypeKnobValue = string | number | boolean | null | undefined | PropertyKey[]; export type SelectTypeOptionsProp = | Record | Record, T[keyof T]> - | Extract[] - | readonly Extract[]; + | T[] + | readonly T[]; export interface SelectTypeKnob extends KnobControlConfig { diff --git a/addons/knobs/src/components/types/Text.tsx b/addons/knobs/src/components/types/Text.tsx index 57db9c6941c..8f060e7d5cd 100644 --- a/addons/knobs/src/components/types/Text.tsx +++ b/addons/knobs/src/components/types/Text.tsx @@ -32,7 +32,7 @@ export default class TextType extends Component { return nextProps.knob.value !== knob.value; } - handleChange = (event: ChangeEvent) => { + private handleChange = (event: ChangeEvent) => { const { onChange } = this.props; const { value } = event.target; diff --git a/addons/links/package.json b/addons/links/package.json index 2d277b91bfe..908ed921e4a 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Story Links addon for storybook", "keywords": [ "addon", @@ -23,22 +23,24 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", "@storybook/csf": "0.0.1", - "@storybook/router": "6.0.0-beta.23", + "@storybook/router": "6.1.0-alpha.14", "@types/qs": "^6.9.0", "core-js": "^3.0.1", "global": "^4.3.2", "prop-types": "^15.7.2", "qs": "^6.6.0", + "react": "^16.8.3", + "react-dom": "^16.8.3", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1" }, @@ -46,18 +48,14 @@ "@types/webpack-env": "^1.15.2", "enzyme": "^3.11.0" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/links/src/preview.test.js b/addons/links/src/preview.test.js index 88008541f0d..5f66db4db49 100644 --- a/addons/links/src/preview.test.js +++ b/addons/links/src/preview.test.js @@ -18,7 +18,6 @@ jest.mock('global', () => ({ })), }, window: global, - __STORYBOOK_LOGGER: console, __STORYBOOK_CLIENT_API__: { raw: jest.fn(() => [ { diff --git a/addons/queryparams/package.json b/addons/queryparams/package.json index 020693fc42e..f8831df532d 100644 --- a/addons/queryparams/package.json +++ b/addons/queryparams/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-queryparams", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -24,39 +24,37 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "qs": "^6.6.0", "react": "^16.8.3", + "react-dom": "^16.8.3", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1" }, "devDependencies": { "@types/webpack-env": "^1.15.2" }, - "peerDependencies": { - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/queryparams/src/index.ts b/addons/queryparams/src/index.ts index 62138a138ba..0070b840986 100644 --- a/addons/queryparams/src/index.ts +++ b/addons/queryparams/src/index.ts @@ -17,10 +17,10 @@ export const withQuery = makeDecorator({ ? qs.parse(parameters, { ignoreQueryPrefix: true }) : parameters; - const newQuery = qs.stringify({ ...currentQuery, ...additionalQuery }); - const newLocation = location.href.replace(location.search, `?${newQuery}`); + const newLocation = new URL(document.location.href); + newLocation.search = qs.stringify({ ...currentQuery, ...additionalQuery }); - history.replaceState({}, document.title, newLocation); + history.replaceState({}, document.title, newLocation.toString()); return getStory(context); }, diff --git a/addons/storyshots/storyshots-core/README.md b/addons/storyshots/storyshots-core/README.md index 61a9fe18b43..fa725962739 100644 --- a/addons/storyshots/storyshots-core/README.md +++ b/addons/storyshots/storyshots-core/README.md @@ -33,6 +33,20 @@ Now run your Jest test command. (Usually, `npm test`.) Then you can see all of y ![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/HEAD/addons/storyshots/storyshots-core/docs/storyshots.png) +### Testing stories that rely on addon-added decorators + +If you have stories in your Storybook that can only render inside a decorator (for instance the [`apollo-storybook-decorator`](https://github.com/abhiaiyer91/apollo-storybook-decorator)), you'll need to ensure those decorators are applied in Storyshots. + +If you export those decorators from your `.storybook/preview.js` then Storyshots will apply those decorators for you in the same way that Storybook does. However if the addon _automatically_ adds the decorator for you (which is a new feature in Storybook 6.0), you will find the decorator does not get added in Storyshots. This is a limitation in Storyshots currently. + +To ensure such decorators get added, export them from `.storybook/preview.js`: + +```js +import addonDecorator from 'some-addon'; + +export const decorators = [addonDecorator]; +``` + ## Configure your app for Jest In many cases, for example Create React App, it's already configured for Jest. You need to create a filename with the extension `.test.js`. @@ -52,7 +66,7 @@ If you still need to configure jest you can use the resources mentioned below: **NOTE**: if you are using Storybook 5.3's `main.js` to list story files, this is no longer needed. -Sometimes it's useful to configure Storybook with Webpack's require.context feature. You could be loading stories [one of two ways](https://storybook.js.org/docs/basics/writing-stories/#loading-stories). +Sometimes it's useful to configure Storybook with Webpack's require.context feature. You could be loading stories [one of two ways](https://storybook.js.org/docs/react/writing-stories/loading-stories). 1. If you're using the `storiesOf` API, you can integrate it this way: @@ -205,6 +219,12 @@ StoryShots addon for Preact is dependent on [preact-render-to-json](https://gith yarn add preact-render-to-json --dev ``` +### Configure Jest for Web Components + +StoryShots addon for Web Components requires [jsdom](https://github.com/jsdom/jsdom) 16 or later to fully support the +web component shadow dom. To use jsdom 16 or later you can set the Jest `testEnvironment` configuration key to +`jest-environment-jsdom-sixteen`. This should work back to Jest 24 and is the default in Jest 26 and later. + ### Configure Jest for MDX Docs Add-On Stories If using the [Docs add-on](../../docs/README.md) with @@ -625,7 +645,7 @@ Like the default, but allows you to specify a set of options for the renderer, j ### `multiSnapshotWithOptions(options)` Like `snapshotWithOptions`, but generate a separate snapshot file for each stories file rather than a single monolithic file (as is the convention in Jest). This makes it dramatically easier to review changes. If you'd like the benefit of separate snapshot files, but don't have custom options to pass, you can pass an empty object. -If you use [Component Story Format](https://storybook.js.org/docs/formats/component-story-format/), you may also need to add an additional Jest transform to automate detecting story file names: +If you use [Component Story Format](https://storybook.js.org/docs/react/api/csf), you may also need to add an additional Jest transform to automate detecting story file names: ```js // jest.config.js diff --git a/addons/storyshots/storyshots-core/package.json b/addons/storyshots/storyshots-core/package.json index 01de1917f6a..a9190eb139d 100644 --- a/addons/storyshots/storyshots-core/package.json +++ b/addons/storyshots/storyshots-core/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storyshots", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.", "keywords": [ "addon", @@ -23,7 +23,7 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "build-storybook": "build-storybook", @@ -33,9 +33,9 @@ }, "dependencies": { "@jest/transform": "^26.0.0", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/glob": "^7.1.1", "@types/jest": "^25.1.1", "@types/jest-specific-snapshot": "^0.5.3", @@ -43,32 +43,31 @@ "core-js": "^3.0.1", "glob": "^7.1.3", "global": "^4.3.2", - "jest-specific-snapshot": "^3.0.0", - "pretty-format": "^25.5.0", + "jest-specific-snapshot": "^4.0.0", + "pretty-format": "^26.4.0", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "react-test-renderer": "^16.8.3", "read-pkg-up": "^7.0.0", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1" }, "devDependencies": { - "@storybook/addon-docs": "6.0.0-beta.23", - "@storybook/react": "6.0.0-beta.23", + "@storybook/addon-docs": "6.1.0-alpha.14", + "@storybook/react": "6.1.0-alpha.14", "babel-loader": "^8.0.6", "enzyme": "^3.11.0", "enzyme-to-json": "^3.4.1", - "jest-emotion": "^10.0.17", - "react": "^16.8.3" - }, - "peerDependencies": { - "react-dom": "*" + "jest-emotion": "^10.0.17" }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/storyshots/storyshots-core/src/api/index.ts b/addons/storyshots/storyshots-core/src/api/index.ts index b2f2a3f325a..b40273c8fa7 100644 --- a/addons/storyshots/storyshots-core/src/api/index.ts +++ b/addons/storyshots/storyshots-core/src/api/index.ts @@ -50,10 +50,16 @@ function testStorySnapshots(options: StoryshotsOptions = {}) { const data = storybook .raw() - .filter(({ name }) => (storyNameRegex ? name.match(storyNameRegex) : true)) - .filter(({ kind }) => (storyKindRegex ? kind.match(storyKindRegex) : true)) .reduce( (acc, item) => { + if (storyNameRegex && !item.name.match(storyNameRegex)) { + return acc; + } + + if (storyKindRegex && !item.kind.match(storyKindRegex)) { + return acc; + } + const { kind, storyFn: render, parameters } = item; const existing = acc.find((i: any) => i.kind === kind); const { fileName } = item.parameters; diff --git a/addons/storyshots/storyshots-core/src/frameworks/Loader.ts b/addons/storyshots/storyshots-core/src/frameworks/Loader.ts index 501f0fbac20..a257e0e0e8b 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/Loader.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/Loader.ts @@ -6,12 +6,13 @@ import { SupportedFramework } from './SupportedFramework'; export type RenderTree = (story: any, context?: any, options?: any) => any; export interface ClientApi extends ClientStoryApi { - configure(loader: Loadable, module: NodeModule | false): void; + configure(loader: Loadable, module: NodeModule | false, showDeprecationWarning?: boolean): void; forceReRender(): void; clearDecorators: ClientApiThing['clearDecorators']; getStorybook: ClientApiThing['getStorybook']; setAddon: ClientApiThing['setAddon']; raw: ClientApiThing['raw']; + addArgTypesEnhancer: ClientApiThing['addArgTypesEnhancer']; } export interface Loader { diff --git a/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts b/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts index 8e25cc610a2..8e0f6f6dafd 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/SupportedFramework.ts @@ -7,4 +7,5 @@ export type SupportedFramework = | 'react-native' | 'svelte' | 'vue' + | 'web-components' | 'rax'; diff --git a/addons/storyshots/storyshots-core/src/frameworks/configure.ts b/addons/storyshots/storyshots-core/src/frameworks/configure.ts index 3ba9c866346..6aba09d5415 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/configure.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/configure.ts @@ -3,6 +3,8 @@ import path from 'path'; import { toRequireContext } from '@storybook/core/server'; import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; import global from 'global'; +import { ArgTypesEnhancer, DecoratorFunction } from '@storybook/client-api'; + import { ClientApi } from './Loader'; import { StoryshotsOptions } from '../api/StoryshotsOptions'; @@ -17,8 +19,8 @@ const isFile = (file: string): boolean => { }; interface Output { - stories: string[]; - files: string[]; + preview?: string; + stories?: string[]; } const supportedExtensions = ['ts', 'tsx', 'js', 'jsx']; @@ -39,13 +41,13 @@ function getConfigPathParts(input: string): Output { const configDir = path.resolve(input); if (fs.lstatSync(configDir).isDirectory()) { - const output: Output = { files: [], stories: [] }; + const output: Output = {}; const preview = getPreviewFile(configDir); const main = getMainFile(configDir); if (preview) { - output.files.push(preview); + output.preview = preview; } if (main) { const { stories = [] } = jest.requireActual(main); @@ -64,7 +66,7 @@ function getConfigPathParts(input: string): Output { return output; } - return { files: [configDir], stories: [] }; + return { preview: configDir }; } function configure( @@ -79,14 +81,29 @@ function configure( return; } - const { files, stories } = getConfigPathParts(configPath); + const { preview, stories } = getConfigPathParts(configPath); - files.forEach((f) => { - jest.requireActual(f); - }); + if (preview) { + // This is essentially the same code as lib/core/src/server/preview/virtualModuleEntry.template + const { parameters, decorators, globals, globalTypes, argTypesEnhancers } = jest.requireActual( + preview + ); + + if (decorators) { + decorators.forEach((decorator: DecoratorFunction) => storybook.addDecorator(decorator)); + } + if (parameters || globals || globalTypes) { + storybook.addParameters({ ...parameters, globals, globalTypes }); + } + if (argTypesEnhancers) { + argTypesEnhancers.forEach((enhancer: ArgTypesEnhancer) => + storybook.addArgTypesEnhancer(enhancer) + ); + } + } if (stories && stories.length) { - storybook.configure(stories, false); + storybook.configure(stories, false, false); } } diff --git a/addons/storyshots/storyshots-core/src/frameworks/react/renderShallowTree.ts b/addons/storyshots/storyshots-core/src/frameworks/react/renderShallowTree.ts index ac534c1490b..d8ac7b865b4 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react/renderShallowTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react/renderShallowTree.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import shallow from 'react-test-renderer/shallow'; function getRenderedTree(story: any, context: any, { renderer, serializer }: any) { diff --git a/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts index e9302c054b7..d92a55710ab 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/react/renderTree.ts @@ -1,6 +1,4 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import React from 'react'; -// eslint-disable-next-line import/no-extraneous-dependencies import reactTestRenderer from 'react-test-renderer'; function getRenderedTree(story: any, context: any, { renderer, ...rendererOptions }: any) { diff --git a/addons/storyshots/storyshots-core/src/frameworks/vue/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/vue/renderTree.ts index 489ac266dd1..4dccbd478c9 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/vue/renderTree.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/vue/renderTree.ts @@ -1,6 +1,10 @@ // eslint-disable-next-line import/no-extraneous-dependencies import Vue from 'vue'; +// this is defined in @storybook/vue but not exported, +// and we need it to inject args into the story component's props +const VALUES = 'STORYBOOK_VALUES'; + function getRenderedTree(story: any) { const component = story.render(); @@ -10,6 +14,9 @@ function getRenderedTree(story: any) { }, }); + // @ts-ignore + vm[VALUES] = story.args; + return vm.$mount().$el; } diff --git a/addons/storyshots/storyshots-core/src/frameworks/web-components/loader.ts b/addons/storyshots/storyshots-core/src/frameworks/web-components/loader.ts new file mode 100644 index 00000000000..bfb760be008 --- /dev/null +++ b/addons/storyshots/storyshots-core/src/frameworks/web-components/loader.ts @@ -0,0 +1,32 @@ +import global from 'global'; +import configure from '../configure'; +import { Loader } from '../Loader'; +import { StoryshotsOptions } from '../../api/StoryshotsOptions'; + +function test(options: StoryshotsOptions): boolean { + return options.framework === 'web-components'; +} + +function load(options: StoryshotsOptions) { + global.STORYBOOK_ENV = 'web-components'; + + const storybook = jest.requireActual('@storybook/web-components'); + + configure({ ...options, storybook }); + + return { + framework: 'web-components' as const, + renderTree: jest.requireActual('./renderTree').default, + renderShallowTree: () => { + throw new Error('Shallow renderer is not supported for web-components'); + }, + storybook, + }; +} + +const webComponentsoader: Loader = { + load, + test, +}; + +export default webComponentsoader; diff --git a/addons/storyshots/storyshots-core/src/frameworks/web-components/renderTree.ts b/addons/storyshots/storyshots-core/src/frameworks/web-components/renderTree.ts new file mode 100644 index 00000000000..8c45ea52eac --- /dev/null +++ b/addons/storyshots/storyshots-core/src/frameworks/web-components/renderTree.ts @@ -0,0 +1,6 @@ +function getRenderedTree(story: { render: () => any }) { + const component = story.render(); + return component.getHTML ? component.getHTML() : component; +} + +export default getRenderedTree; diff --git a/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.metadata.test.js.snap b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.metadata.test.js.snap new file mode 100644 index 00000000000..c9965e394a4 --- /dev/null +++ b/addons/storyshots/storyshots-core/stories/__snapshots__/storyshot.metadata.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Text Simple 1`] = ` +
+ prefix + + contents + + suffix +
+`; diff --git a/addons/storyshots/storyshots-core/stories/exported_metadata/Text.stories.js b/addons/storyshots/storyshots-core/stories/exported_metadata/Text.stories.js new file mode 100644 index 00000000000..e87143f2c78 --- /dev/null +++ b/addons/storyshots/storyshots-core/stories/exported_metadata/Text.stories.js @@ -0,0 +1,5 @@ +export default { + title: 'Text', +}; + +export const Simple = () => 'contents'; diff --git a/addons/storyshots/storyshots-core/stories/exported_metadata/main.js b/addons/storyshots/storyshots-core/stories/exported_metadata/main.js new file mode 100644 index 00000000000..f6e10c62569 --- /dev/null +++ b/addons/storyshots/storyshots-core/stories/exported_metadata/main.js @@ -0,0 +1,3 @@ +module.exports = { + stories: ['./Text.stories.js'], +}; diff --git a/addons/storyshots/storyshots-core/stories/exported_metadata/preview.js b/addons/storyshots/storyshots-core/stories/exported_metadata/preview.js new file mode 100644 index 00000000000..724c84b9a43 --- /dev/null +++ b/addons/storyshots/storyshots-core/stories/exported_metadata/preview.js @@ -0,0 +1,12 @@ +import React from 'react'; + +export const decorators = [ + (StoryFn, { parameters, globals }) => ( +
+ {parameters.prefix} {globals.suffix} +
+ ), +]; + +export const parameters = { prefix: 'prefix' }; +export const globals = { suffix: 'suffix' }; diff --git a/addons/storyshots/storyshots-core/stories/storyshot.metadata.test.js b/addons/storyshots/storyshots-core/stories/storyshot.metadata.test.js new file mode 100644 index 00000000000..38d2d3c1d79 --- /dev/null +++ b/addons/storyshots/storyshots-core/stories/storyshot.metadata.test.js @@ -0,0 +1,9 @@ +import path from 'path'; +import initStoryshots from '../dist'; + +// jest.mock('@storybook/node-logger'); + +initStoryshots({ + framework: 'react', + configPath: path.join(__dirname, 'exported_metadata'), +}); diff --git a/addons/storyshots/storyshots-puppeteer/README.md b/addons/storyshots/storyshots-puppeteer/README.md index ce6919b987a..4776f0d23b0 100644 --- a/addons/storyshots/storyshots-puppeteer/README.md +++ b/addons/storyshots/storyshots-puppeteer/README.md @@ -10,7 +10,7 @@ npm install @storybook/addon-storyshots-puppeteer puppeteer --save-dev ⚠️ As of Storybook 5.3 `puppeteer` is no more included in addon dependencies and must be added to your project directly. -## Configure Storyshots for Puppeteeer tests +## Configure Storyshots for Puppeteer tests ⚠️ **React-native** is **not supported** by this test function. @@ -305,3 +305,16 @@ initStoryshots({ ``` `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. + +To create a screenshot of just a single element (with its children), rather than the page or current viewport, an ElementHandle can be returned from `beforeScreenshot`: +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; + +const beforeScreenshot = (page) => page.$('#root > *'); + +initStoryshots({ + suite: 'Image storyshots', + test: imageSnapshot({ storybookUrl: 'http://localhost:6006', beforeScreenshot }), +}); +``` \ No newline at end of file diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index 9772198cb3b..6a86b8d0428 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": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Image snapshots addition to StoryShots based on puppeteer", "keywords": [ "addon", @@ -23,18 +23,18 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../../scripts/prepare.js" }, "dependencies": { "@storybook/csf": "0.0.1", - "@storybook/node-logger": "6.0.0-beta.23", + "@storybook/node-logger": "6.1.0-alpha.14", "@types/jest-image-snapshot": "^2.8.0", "@wordpress/jest-puppeteer-axe": "^1.5.0", "core-js": "^3.0.1", - "jest-image-snapshot": "^3.0.1", + "jest-image-snapshot": "^4.0.2", "regenerator-runtime": "^0.13.3" }, "devDependencies": { @@ -42,7 +42,7 @@ "@types/puppeteer": "^2.0.0" }, "peerDependencies": { - "@storybook/addon-storyshots": "6.0.0-beta.23", + "@storybook/addon-storyshots": "6.1.0-alpha.14", "puppeteer": "^2.0.0 || ^3.0.0" }, "peerDependenciesMeta": { @@ -53,11 +53,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/storyshots/storyshots-puppeteer/src/config.ts b/addons/storyshots/storyshots-puppeteer/src/config.ts index a421ea02999..0f05f622f5b 100644 --- a/addons/storyshots/storyshots-puppeteer/src/config.ts +++ b/addons/storyshots/storyshots-puppeteer/src/config.ts @@ -1,5 +1,11 @@ import { MatchImageSnapshotOptions } from 'jest-image-snapshot'; -import { Base64ScreenShotOptions, Browser, DirectNavigationOptions, Page } from 'puppeteer'; +import { + Base64ScreenShotOptions, + Browser, + DirectNavigationOptions, + Page, + ElementHandle, +} from 'puppeteer'; export interface Context { kind: string; @@ -33,7 +39,7 @@ export interface PuppeteerTestConfig extends CommonConfig { export interface ImageSnapshotConfig extends CommonConfig { getMatchOptions: (options: Options) => MatchImageSnapshotOptions; getScreenshotOptions: (options: Options) => Base64ScreenShotOptions; - beforeScreenshot: (page: Page, options: Options) => void; + beforeScreenshot: (page: Page, options: Options) => void | ElementHandle; afterScreenshot: (options: { image: string; context: Context }) => void; } diff --git a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts index e8cceac6152..d2e950401f7 100644 --- a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts +++ b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts @@ -12,8 +12,8 @@ export const imageSnapshot = (customConfig: Partial = {}) = ...config, async testBody(page, options) { expect.assertions(1); - await beforeScreenshot(page, options); - const image = await page.screenshot(getScreenshotOptions(options)); + const element = await beforeScreenshot(page, options); + const image = await (element || page).screenshot(getScreenshotOptions(options)); await afterScreenshot({ image, context: options.context }); expect(image).toMatchImageSnapshot(getMatchOptions(options)); }, diff --git a/addons/storysource/README.md b/addons/storysource/README.md index 3b3c1ea1e69..464fd565b56 100644 --- a/addons/storysource/README.md +++ b/addons/storysource/README.md @@ -1,4 +1,4 @@ -# Storybook Storysource Addon +

Storybook Storysource Addon

This addon is used to show stories source in the addon panel. @@ -6,6 +6,11 @@ This addon is used to show stories source in the addon panel. ![Storysource Demo](./docs/demo.gif) +- [Getting Started](#getting-started) + - [Install using preset](#install-using-preset) +- [Theming](#theming) +- [Displaying full source](#displaying-full-source) + ## Getting Started First, install the addon @@ -47,138 +52,49 @@ module.exports = { }; ``` -## Loader Options - -The loader can be customized with the following options: - -### parser - -The parser that will be parsing your code to AST (based on [prettier](https://github.com/prettier/prettier/tree/master/src/language-js)) - -Allowed values: - -- `javascript` - default -- `typescript` -- `flow` - -Be sure to update the regex test for the webpack rule if utilizing Typescript files. - -Usage: - -```js -module.exports = function({ config }) { - config.module.rules.push({ - test: /\.stories\.tsx?$/, - loaders: [ - { - loader: require.resolve('@storybook/source-loader'), - options: { parser: 'typescript' }, - }, - ], - enforce: 'pre', - }); - - return config; -}; -``` - -### prettierConfig - -The prettier configuration that will be used to format the story source in the addon panel. - -Defaults: - -```js -{ - printWidth: 100, - tabWidth: 2, - bracketSpacing: true, - trailingComma: 'es5', - singleQuote: true, -} -``` - -Usage: - -```js -module.exports = function({ config }) { - config.module.rules.push({ - test: /\.stories\.jsx?$/, - loaders: [ - { - loader: require.resolve('@storybook/source-loader'), - options: { - prettierConfig: { - printWidth: 100, - singleQuote: false, - }, - }, - }, - ], - enforce: 'pre', - }); - - return config; -}; -``` - -### uglyCommentsRegex - -The array of regex that is used to remove "ugly" comments. - -Defaults: - -```js -[/^eslint-.*/, /^global.*/]; -``` - -Usage: - -```js -module.exports = function({ config }) { - config.module.rules.push({ - test: /\.stories\.jsx?$/, - loaders: [ - { - loader: require.resolve('@storybook/source-loader'), - options: { - uglyCommentsRegex: [/^eslint-.*/, /^global.*/], - }, - }, - ], - enforce: 'pre', - }); - - return config; -}; -``` - -### injectDecorator - -Tell storysource whether you need inject decorator. If false, you need to add the decorator by yourself; - -Defaults: true - -Usage: - -```js -module.exports = function({ config }) { - config.module.rules.push({ - test: /\.stories\.jsx?$/, - loaders: [ - { - loader: require.resolve('@storybook/source-loader'), - options: { injectDecorator: false }, - }, - ], - enforce: 'pre', - }); - - return config; -}; -``` +To customize the `source-loader`, pass `loaderOptions`. Valid configurations are documented in the [`source-loader` README](../../lib/source-loader/README.md#options). ## Theming -Storysource will automatically use the light or dark syntax theme based on your storybook theme. See [Theming Storybook](https://storybook.js.org/docs/configurations/theming/) for more information. +Storysource will automatically use the light or dark syntax theme based on your storybook theme. See [Theming Storybook](https://storybook.js.org/docs/react/configure/theming) for more information. ![Storysource Light/Dark Themes](./docs/theming-light-dark.png) + +## Displaying full source + +Storybook 6.0 introduced an unintentional change to `source-loader, in which only the source of the selected story is shown in the addon. To restore the old behavior, pass the`injectStoryParameters: false` option. + +If you're using `addon-docs`: + +```js +module.exports = { + addons: [ + { + name: '@storybook/addon-docs', + options: { + sourceLoaderOptions: { + injectStoryParameters: false, + }, + }, + }, + ], +}; +``` + +If not: + +```js +module.exports = { + addons: [ + { + name: '@storybook/addon-storysource', + options: { + loaderOptions: { + injectStoryParameters: false, + }, + }, + }, + ], +}; +``` + +This bug will be resolved in a future version of the addon. diff --git a/addons/storysource/package.json b/addons/storysource/package.json index 28c8d130010..ff1dfc7ec34 100644 --- a/addons/storysource/package.json +++ b/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Stories addon for storybook", "keywords": [ "addon", @@ -23,39 +23,42 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/router": "6.0.0-beta.23", - "@storybook/source-loader": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/router": "6.1.0-alpha.14", + "@storybook/source-loader": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "estraverse": "^4.2.0", "loader-utils": "^2.0.0", - "prettier": "^2.0.5", + "prettier": "~2.0.5", "prop-types": "^15.7.2", "react": "^16.9.17", - "react-syntax-highlighter": "^12.2.1", + "react-dom": "^16.8.3", + "react-syntax-highlighter": "^13.5.0", "regenerator-runtime": "^0.13.3" }, "devDependencies": { "@types/react": "^16.9.27", "@types/react-syntax-highlighter": "^11.0.4" }, - "peerDependencies": { - "@storybook/source-loader": "*", - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/addons/storysource/src/StoryPanel.tsx b/addons/storysource/src/StoryPanel.tsx index dd4c1c7b51a..49b5f475c9c 100644 --- a/addons/storysource/src/StoryPanel.tsx +++ b/addons/storysource/src/StoryPanel.tsx @@ -9,7 +9,7 @@ import { createSyntaxHighlighterElement, } from '@storybook/components'; -import { SourceBlock, LocationsMap } from '@storybook/source-loader'; +import { SourceBlock, LocationsMap } from '@storybook/source-loader/extract-source'; import { Story } from '@storybook/api/dist/lib/stories'; const StyledStoryLink = styled(Link)<{ to: string; key: string }>(({ theme }) => ({ @@ -155,7 +155,7 @@ export const StoryPanel: React.FC = ({ api }) => { }: SyntaxHighlighterRendererProps): React.ReactNode => { // because of the usage of lineRenderer, all lines will be wrapped in a span // these spans will receive all classes on them for some reason - // which makes colours casecade incorrectly + // which makes colours cascade incorrectly // this removed that list of classnames const myrows = rows.map(({ properties, ...rest }) => ({ ...rest, @@ -170,7 +170,7 @@ export const StoryPanel: React.FC = ({ api }) => { return {parts}; }; - return ( + return story ? ( = ({ api }) => { > {source} - ); + ) : null; }; diff --git a/addons/toolbars/README.md b/addons/toolbars/README.md index 4fc67c669df..49257cfe6aa 100644 --- a/addons/toolbars/README.md +++ b/addons/toolbars/README.md @@ -1,45 +1,24 @@ -
- -
+# Storybook Toolbars Addon -

Storybook Addon Toolbars

- -The Toolbars addon controls global story rendering options from Storybook's toolbar UI. It's a general purpose addon that can be used to: +The Toolbars addon controls global story rendering options from [Storybook's](https://storybook.js.org) toolbar UI. It's a general purpose addon that can be used to: - set a theme for your components - set your components' internationalization (i18n) locale - configure just about anything in Storybook that makes use of a global variable -Toolbars is built on top of [Storybook Args](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/formats/component-story-format/index.md#args-story-inputs) (SB6.0+): dynamic variables that trigger a story re-render when they are set. +[Framework Support](https://storybook.js.org/docs/react/api/frameworks-feature-support) -- [Get started](#get-started) - - [Installation](#installation) - - [Configure menu UI](#configure-menu-ui) - - [Create a decorator](#create-a-decorator) -- [Advanced usage](#advanced-usage) - - [Advanced menu configuration](#advanced-menu-configuration) - - [Consuming global args from within a story](#consuming-global-args-from-within-a-story) - - [Consuming global args from within an addon](#consuming-global-args-from-within-an-addon) -- [FAQs](#faqs) - - [How does this compare to `addon-contexts`?](#how-does-this-compare-to-addon-contexts) +![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/next/addons/toolbars/docs/hero.gif) -## Get started +## Installation -To get started with `addon-toolbars`: - -1. [install the addon](#installation), -2. [configure the menu UI](#configure-menu-ui) -3. [Create a decorator to implement custom logic](#create-a-decorator). - -### Installation - -First, install the package: +Toolbars is part of [essentials](https://storybook.js.org/docs/react/essentials/introduction) and so is installed in all new Storybooks by default. If you need to add it to your Storybook, you can run: ```sh -npm install @storybook/addon-toolbars -D # or yarn +npm i -D @storybook/addon-toolbars ``` -Then add it to your `.storybook/main.js` config: +Then, add following content to [`.storybook/main.js`](https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project): ```js module.exports = { @@ -47,169 +26,9 @@ module.exports = { }; ``` -### Configure menu UI +## Usage -Addon-toolbars has a simple, declarative syntax for configuring toolbar menus. You can add toolbars by adding `globalArgTypes` with a `toolbar` annotation, in `.storybook/preview.js`: - -```js -export const globalArgTypes = { - theme: { - name: 'Theme' - description: 'Global theme for components', - defaultValue: 'light', - toolbar: { - icon: 'circlehollow', - // array of plain string values or MenuItem shape (see below) - items: ['light', 'dark'], - }, - }, -}; -``` - -You should see a dropdown in your toolbar with options `light` and `dark`. - -### Create a decorator - -Now, let's wire it up! We can consume our new `theme` global arg in a decorator using the `context.globalArgs.theme` value. - -For example, suppose you are using `styled-components`. You can add a theme provider decorator to your `.storybook/preview.js` config: - -```ts -import { ThemeProvider } from 'styled-components'; -import { StoryContext, StoryGetter, StoryWrapper } from '@storybook/addons'; - -const withThemeProvider: StoryWrapper = (Story: StoryGetter, context: StoryContext) => { - // context.globalArgs.theme here will be either 'light' or 'dark' - // getTheme being a function retrieving the actual theme object from that value - const theme = getTheme(context.globalArgs.theme); - - return ( - - - - ); -}; - -export const decorators = [withThemeProvider]; -``` - -## Advanced usage - -The previous section shows the common case. There are two advanced use cases: (1) [advanced menu configurations](#advanced-menu-configuration), (2) [consuming global args inside a story](#consuming-global-args-from-within-a-story). - -### Advanced menu configuration - -The default menu configuration is simple: everything's a string! However, the Toolbars addon also support configuration options to tweak the appearance of the menu: - -```ts -type MenuItem { - /** - * The string value of the menu that gets set in the global args - */ - value: string, - /** - * The main text of the title - */ - title: string, - /** - * A string that gets shown in left side of the menu, if set - */ - left?: string, - /** - * A string that gets shown in right side of the menu, if set - */ - right?: string, - /** - * An icon that gets shown in the toolbar if this item is selected - */ - icon?: icon, -} -``` - -Thus if you want to show right-justified flags for an internationalization locale, you might set up the following configuration in `.storybook/preview.js`: - -```js -export const globalArgTypes = { - locale: { - name: 'Locale', - description: 'Internationalization locale', - defaultValue: 'en', - toolbar: { - icon: 'globe', - items: [ - { value: 'en', right: '🇺🇸', title: 'English' }, - { value: 'fr', right: '🇫🇷', title: 'Français' }, - { value: 'es', right: '🇪🇸', title: 'Español' }, - { value: 'zh', right: '🇨🇳', title: '中文' }, - { value: 'kr', right: '🇰🇷', title: '한국어' }, - ], - }, - }, -}; -``` - -### Consuming global args from within a story - -The recommended usage, as shown in the examples above, is to consume global args from within a decorator and implement a global setting that applies to all stories. But sometimes it's useful to use toolbar options inside individual stories. - -Storybook's `globalArgs` are available via the story context: - -```js -const getCaptionForLocale = (locale) => { - switch(locale) { - case 'es': return 'Hola!'; - case 'fr': return 'Bonjour!'; - case 'kr': return '안녕하세요!'; - case 'zh': return '你好!'; - default: - return 'Hello!', - } -} - -export const StoryWithLocale = (args, { globalArgs: { locale } }) => { - const caption = getCaptionForLocale(locale); - return <>{caption}; -}; -``` - -**NOTE:** In Storybook 6.0, if you set the global option `passArgsFirst: false` for backwards compatibility, the story context is passes as the second argument: - -```js -export const StoryWithLocale = ({ globalArgs: { locale } }) => { - const caption = getCaptionForLocale(locale); - return <>{caption}; -}; -``` - -### Consuming global args from within an addon - -There is a hook available in `@storybook/api` to retrieve the global args: `useGlobalArgs()` - -Following the previous example of the ThemeProvider, if you want for instance to display the current theme inside a Panel: - -```js -import { useGlobalArgs } from '@storybook/api'; -import { AddonPanel, Placeholder, Separator, Source, Spaced, Title } from '@storybook/components'; - -const ThemePanel = props => { - const [{ theme: themeName }] = useGlobalArgs(); - const theme = getTheme(themeName); - - return ( - - {theme ? ( - - {theme.name} -

The full theme object/p> - - - ) : ( - No theme selected - )} - - ); -}; -``` +The usage is documented in the [documentation](https://storybook.js.org/docs/react/essentials/toolbars-and-globals). ## FAQs diff --git a/addons/toolbars/package.json b/addons/toolbars/package.json index 0e21fb7a188..72def61388d 100644 --- a/addons/toolbars/package.json +++ b/addons/toolbars/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-toolbars", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook toolbars addon", "keywords": [ "addon", @@ -30,17 +30,16 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "core-js": "^3.0.1" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "core-js": "^3.0.1", + "react": "^16.8.3", + "react-dom": "^16.8.3" }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4571582a90b646e361cb37df525f62312486307a" } diff --git a/addons/toolbars/src/components/MenuToolbar.tsx b/addons/toolbars/src/components/MenuToolbar.tsx index 161108e36d5..b4094b21cba 100644 --- a/addons/toolbars/src/components/MenuToolbar.tsx +++ b/addons/toolbars/src/components/MenuToolbar.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; -import { useGlobalArgs } from '@storybook/api'; -import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; +import { useGlobals } from '@storybook/api'; +import { Icons, IconButton, WithTooltip, TooltipLinkList, TabButton } from '@storybook/components'; import { NormalizedToolbarArgType } from '../types'; export type MenuToolbarProps = NormalizedToolbarArgType & { id: string }; @@ -11,10 +11,11 @@ export const MenuToolbar: FC = ({ description, toolbar: { icon, items }, }) => { - const [globalArgs, updateGlobalArgs] = useGlobalArgs(); - const selectedValue = globalArgs[id]; + const [globals, updateGlobals] = useGlobals(); + const selectedValue = globals[id]; const active = selectedValue != null; const selectedItem = active && items.find((item) => item.value === selectedValue); + const selectedIcon = (selectedItem && selectedItem.icon) || icon; return ( = ({ right, active: selectedValue === value, onClick: () => { - updateGlobalArgs({ [id]: value }); + updateGlobals({ [id]: value }); onHide(); }, }; @@ -39,9 +40,13 @@ export const MenuToolbar: FC = ({ }} closeOnClick > - - - + {selectedIcon ? ( + + + + ) : ( + {name} + )} ); }; diff --git a/addons/toolbars/src/components/ToolbarManager.tsx b/addons/toolbars/src/components/ToolbarManager.tsx index 215ae20c908..ce88dc7ae03 100644 --- a/addons/toolbars/src/components/ToolbarManager.tsx +++ b/addons/toolbars/src/components/ToolbarManager.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import { useGlobalArgTypes } from '@storybook/api'; +import { useGlobalTypes } from '@storybook/api'; import { Separator } from '@storybook/components'; import { ToolbarArgType } from '../types'; @@ -21,15 +21,15 @@ const normalize = (key: string, argType: ToolbarArgType) => ({ * A smart component for handling manager-preview interactions. */ export const ToolbarManager: FC = () => { - const globalArgTypes = useGlobalArgTypes(); - const keys = Object.keys(globalArgTypes).filter((key) => !!globalArgTypes[key].toolbar); + const globalTypes = useGlobalTypes(); + const keys = Object.keys(globalTypes).filter((key) => !!globalTypes[key].toolbar); if (!keys.length) return null; return ( <> {keys.map((key) => { - const normalizedConfig = normalize(key, globalArgTypes[key] as ToolbarArgType); + const normalizedConfig = normalize(key, globalTypes[key] as ToolbarArgType); return ; })} diff --git a/addons/viewport/README.md b/addons/viewport/README.md index 9cbc5d51d78..1dc1240f2d5 100644 --- a/addons/viewport/README.md +++ b/addons/viewport/README.md @@ -2,25 +2,19 @@ Storybook Viewport Addon allows your stories to be displayed in different sizes and layouts in [Storybook](https://storybook.js.org). This helps build responsive components inside of Storybook. -[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) +[Framework Support](https://storybook.js.org/docs/react/api/frameworks-feature-support) ![Screenshot](https://github.com/storybookjs/storybook/blob/master/addons/viewport/docs/viewport.png) ## Installation -Install the following npm module: +Viewport is part of [essentials](https://storybook.js.org/docs/react/essentials/introduction) and so is installed in all new Storybooks by default. If you need to add it to your Storybook, you can run: ```sh -npm i --save-dev @storybook/addon-viewport +npm i -D @storybook/addon-viewport ``` -or with yarn: - -```sh -yarn add -D @storybook/addon-viewport -``` - -within `.storybook/main.js`: +Then, add following content to [`.storybook/main.js`](https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project): ```js module.exports = { @@ -30,175 +24,6 @@ module.exports = { You should now be able to see the viewport addon icon in the the toolbar at the top of the screen. -## Configuration +## Usage -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/client-api'; - -addParameters({ - viewport: { - viewports: newViewports, // newViewports would be an ViewportMap. (see below for examples) - defaultViewport: 'someDefault', - }, -}); -``` - -Options can take a object with the following keys: - -### defaultViewport : String - ---- - -Setting this property to, let say `iphone6`, will make `iPhone 6` the default device/viewport for all stories. Default is `'responsive'` which fills 100% of the preview area. - -### disable : Boolean - ---- - -Disable viewport addon per component, story or global. - -### viewports : Object - ---- - -A key-value pair of viewport's key and properties (see `Viewport` definition below) for all viewports to be displayed. Default is [`MINIMAL_VIEWPORTS`](src/defaults.ts) - -#### Viewport Model - -```js -{ - /** - * name to display in the dropdown - * @type {String} - */ - name: 'Responsive', - - /** - * Inline styles to be applied to the story (iframe). - * styles is an object whose key is the camelCased version of the style name, and whose - * value is the style’s value, usually a string - * @type {Object} - */ - styles: { - width: '100%', - height: '100%', - }, - - /** - * type of the device (e.g. desktop, mobile, or tablet) - * @type {String} - */ - type: 'desktop', -} -``` - -## Configuring per component or story - -Parameters can be configured for a whole set of stories or a single story via the standard parameter API: - -```js - -export default { - title: 'Stories', - parameters: { - viewport: { - viewports: INITIAL_VIEWPORTS, - defaultViewport: 'iphone6' - }, - }; -}; - -export const myStory = () =>

; -myStory.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 `.storybook/preview.js` file. - -```js -import { addParameters } from '@storybook/client-api'; -import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; - -addParameters({ - viewport: { - viewports: INITIAL_VIEWPORTS, - }, -}); -``` - -### 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 `.storybook/preview.js` file. - -```js -import { addParameters } from '@storybook/client-api'; - -const customViewports = { - kindleFire2: { - name: 'Kindle Fire 2', - styles: { - width: '600px', - height: '963px', - }, - }, - kindleFireHD: { - name: 'Kindle Fire HD', - styles: { - width: '533px', - height: '801px', - }, - }, -}; - -addParameters({ - 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) 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, - // or MINIMAL_VIEWPORTS, -} from '@storybook/addon-viewport'; - -const customViewports = { - kindleFire2: { - name: 'Kindle Fire 2', - styles: { - width: '600px', - height: '963px', - }, - }, - kindleFireHD: { - name: 'Kindle Fire HD', - styles: { - width: '533px', - height: '801px', - }, - }, -}; - -addParameters({ - viewport: { - viewports: { - ...INITIAL_VIEWPORTS, - // or ...MINIMAL_VIEWPORTS, - ...customViewports, - }, - }, -}); -``` +The usage is documented in the [documentation](https://storybook.js.org/docs/react/essentials/viewport). diff --git a/addons/viewport/package.json b/addons/viewport/package.json index 7b56e9a3ee5..39fdeefed51 100644 --- a/addons/viewport/package.json +++ b/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook addon to change the viewport size to mobile", "keywords": [ "addon", @@ -23,36 +23,34 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "memoizerific": "^1.11.3", "prop-types": "^15.7.2", + "react": "^16.8.3", + "react-dom": "^16.8.3", "regenerator-runtime": "^0.13.3" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/addons/viewport/preset.js b/addons/viewport/preset.js new file mode 100644 index 00000000000..a83f95279e7 --- /dev/null +++ b/addons/viewport/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/addons/viewport/register.js b/addons/viewport/register.js index fe7ac6e7368..cc38cb06f1f 100644 --- a/addons/viewport/register.js +++ b/addons/viewport/register.js @@ -1,3 +1 @@ -// NOTE: loading addons using this file is deprecated! -// please use manager.js and preview.js files instead require('./dist/register'); diff --git a/addons/viewport/src/preset/index.ts b/addons/viewport/src/preset/index.ts new file mode 100644 index 00000000000..0e20177db06 --- /dev/null +++ b/addons/viewport/src/preset/index.ts @@ -0,0 +1,3 @@ +export function managerEntries(entry: any[] = []) { + return [...entry, require.resolve('../register')]; +} diff --git a/app/angular/README.md b/app/angular/README.md index f4ffe852ed4..9a0ed4814e0 100644 --- a/app/angular/README.md +++ b/app/angular/README.md @@ -19,5 +19,5 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/angular/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/angular/workflows/publish-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/angular/package.json b/app/angular/package.json index acd085b70a6..8ca5bf8f117 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,23 +28,22 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", - "@storybook/node-logger": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", + "@storybook/node-logger": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "autoprefixer": "^9.7.6", "core-js": "^3.0.1", "fork-ts-checker-webpack-plugin": "^4.0.3", "global": "^4.3.2", "postcss-loader": "^3.0.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "raw-loader": "^4.0.1", "regenerator-runtime": "^0.13.3", "sass-loader": "^8.0.0", "strip-json-comments": "^3.0.1", @@ -75,11 +74,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/angular/src/client/index.ts b/app/angular/src/client/index.ts index 823d2918762..384e8f440cb 100644 --- a/app/angular/src/client/index.ts +++ b/app/angular/src/client/index.ts @@ -9,6 +9,8 @@ export { raw, } from './preview'; +export * from './preview/types-6-0'; + export { StoryFnAngularReturnType as IStory } from './preview/types'; export { moduleMetadata } from './preview/angular/decorators'; 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 f0a094788c9..81d2cada177 100644 --- a/app/angular/src/client/preview/angular/components/app.component.ts +++ b/app/angular/src/client/preview/angular/components/app.component.ts @@ -33,6 +33,8 @@ export class AppComponent implements OnInit, OnDestroy { subscription: Subscription; + propSubscriptions = new Map(); + constructor( private cfr: ComponentFactoryResolver, private changeDetectorRef: ChangeDetectorRef, @@ -64,6 +66,13 @@ export class AppComponent implements OnInit, OnDestroy { if (this.subscription) { this.subscription.unsubscribe(); } + + this.propSubscriptions.forEach(v => { + if (!v.sub.closed) { + v.sub.unsubscribe(); + } + }) + this.propSubscriptions.clear(); } /** @@ -93,7 +102,7 @@ export class AppComponent implements OnInit, OnDestroy { } } } else if (typeof value === 'function' && key !== 'ngModelChange') { - instanceProperty.subscribe(value); + this.setPropSubscription(key, instanceProperty, value); } }); @@ -123,4 +132,26 @@ export class AppComponent implements OnInit, OnDestroy { instance.registerOnChange(props.ngModelChange); } } + + /** + * Store ref to subscription for cleanup in 'ngOnDestroy' and check if + * observable needs to be resubscribed to, before creating a new subscription. + */ + private setPropSubscription(key: string, instanceProperty: Observable, value: any): void { + if (this.propSubscriptions.has(key)) { + const v = this.propSubscriptions.get(key); + if (v.prop === value) { + // Prop hasn't changed, so the existing subscription can stay. + return; + } + + // Now that the value has changed, unsubscribe from the previous value's subscription. + if (!v.sub.closed) { + v.sub.unsubscribe(); + } + } + + const sub = instanceProperty.subscribe(value); + this.propSubscriptions.set(key, { prop: value, sub }); + } } diff --git a/app/angular/src/client/preview/index.ts b/app/angular/src/client/preview/index.ts index 9f5b040238c..bf960f4d942 100644 --- a/app/angular/src/client/preview/index.ts +++ b/app/angular/src/client/preview/index.ts @@ -26,7 +26,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/angular/src/client/preview/types-6-0.ts b/app/angular/src/client/preview/types-6-0.ts new file mode 100644 index 00000000000..5f66f80955c --- /dev/null +++ b/app/angular/src/client/preview/types-6-0.ts @@ -0,0 +1,23 @@ +import { Args as DefaultArgs, Annotations, BaseMeta, BaseStory } from '@storybook/addons'; +import { StoryFnAngularReturnType } from './types'; + +export { Args, ArgTypes, Parameters, StoryContext } from '@storybook/addons'; + +type AngularComponent = any; +type AngularReturnType = StoryFnAngularReturnType; + +/** + * Metadata to configure the stories for a component. + * + * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) + */ +export type Meta = BaseMeta & + Annotations; + +/** + * Story function that represents a component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type Story = BaseStory & + Annotations; diff --git a/app/angular/src/server/angular-cli_utils.ts b/app/angular/src/server/angular-cli_utils.ts index 75d2d6763d3..8dccf86b1de 100644 --- a/app/angular/src/server/angular-cli_utils.ts +++ b/app/angular/src/server/angular-cli_utils.ts @@ -1,5 +1,13 @@ import fs from 'fs'; -import { basename, dirname, normalize, relative, resolve, Path } from '@angular-devkit/core'; +import { + basename, + dirname, + normalize, + relative, + resolve, + Path, + getSystemPath, +} from '@angular-devkit/core'; import { getCommonConfig, getStylesConfig, @@ -17,7 +25,7 @@ function isDirectory(assetPath: string) { } function getAssetsParts(resolvedAssetPath: Path, assetPath: Path) { - if (isDirectory(resolvedAssetPath)) { + if (isDirectory(getSystemPath(resolvedAssetPath))) { return { glob: '**/*', // Folders get a recursive star glob. input: assetPath, // Input directory is their original path. diff --git a/app/angular/src/server/framework-preset-angular.ts b/app/angular/src/server/framework-preset-angular.ts index 5b0239b58a7..3e18687952f 100644 --- a/app/angular/src/server/framework-preset-angular.ts +++ b/app/angular/src/server/framework-preset-angular.ts @@ -19,7 +19,7 @@ export function webpack( test: /\.tsx?$/, use: [ { - loader: 'ts-loader', + loader: require.resolve('ts-loader'), options: tsLoaderOptions, }, { loader: path.resolve(__dirname, 'ngx-template-loader') }, @@ -31,20 +31,20 @@ export function webpack( }, { test: /\.html$/, - loader: 'raw-loader', + loader: require.resolve('raw-loader'), exclude: /\.async\.html$/, }, { test: /\.s(c|a)ss$/, use: [ - { loader: 'raw-loader' }, + { loader: require.resolve('raw-loader') }, { loader: require.resolve('postcss-loader'), options: { plugins: [autoprefixer()], }, }, - { loader: 'sass-loader' }, + { loader: require.resolve('sass-loader') }, ], }, ], diff --git a/app/angular/types-6-0.d.ts b/app/angular/types-6-0.d.ts new file mode 100644 index 00000000000..6ed7da8e519 --- /dev/null +++ b/app/angular/types-6-0.d.ts @@ -0,0 +1 @@ +export * from './dist/client/preview/types-6-0.d'; diff --git a/app/aurelia/README.md b/app/aurelia/README.md index fb3507ebb76..95b664252cf 100644 --- a/app/aurelia/README.md +++ b/app/aurelia/README.md @@ -19,5 +19,5 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/aurelia/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/aurelia/workflows/publish-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/aurelia/demo.d.ts b/app/aurelia/demo.d.ts deleted file mode 100644 index 0183b7ef451..00000000000 --- a/app/aurelia/demo.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '@storybook/aurelia/demo' { - export const Button: any; - export const Welcome: any; -} diff --git a/app/aurelia/demo.js b/app/aurelia/demo.js deleted file mode 100644 index 64bcd548d7e..00000000000 --- a/app/aurelia/demo.js +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable global-require */ -module.exports = { - Welcome: require('./dist/demo/welcome').default, - Button: require('./dist/demo/button').default, -}; diff --git a/app/aurelia/package.json b/app/aurelia/package.json index 6325fe2a4ce..ac905bf5c57 100644 --- a/app/aurelia/package.json +++ b/app/aurelia/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/aurelia", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Aurelia: Develop Aurelia Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -26,13 +26,15 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addon-knobs": "6.0.0-beta.23", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", - "@storybook/node-logger": "6.0.0-beta.23", + "@aurelia/webpack-loader": "^0.7.0", + "@storybook/addon-knobs": "6.1.0-alpha.14", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", + "@storybook/node-logger": "6.1.0-alpha.14", "fork-ts-checker-webpack-plugin": "^4.0.3", "global": "^4.3.2", "ts-loader": "^6.0.1", + "url-loader": "^4.1.0", "webpack": "^4.33.0" }, "devDependencies": { @@ -46,7 +48,7 @@ "rimraf": "^3.0.2", "sass-loader": "^8.0.0", "style-loader": "^0.23.0", - "typescript": "latest", + "typescript": "^3.9.3", "webpack": "^4.33.0" }, "peerDependencies": { @@ -54,5 +56,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4571582a90b646e361cb37df525f62312486307a" } diff --git a/app/aurelia/src/client/preview/index.ts b/app/aurelia/src/client/preview/index.ts index 909bac8c3ac..aba12d87aa2 100644 --- a/app/aurelia/src/client/preview/index.ts +++ b/app/aurelia/src/client/preview/index.ts @@ -31,7 +31,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { export { StoryFnAureliaReturnType, addRegistries, addContainer, Component, addComponents }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/aurelia/src/demo/index.ts b/app/aurelia/src/demo/index.ts deleted file mode 100644 index 9463e32d9b5..00000000000 --- a/app/aurelia/src/demo/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as Button } from './button'; -export { default as Welcome } from './welcome'; diff --git a/app/aurelia/src/demo/welcome.ts b/app/aurelia/src/demo/welcome.ts deleted file mode 100644 index 109911b7de2..00000000000 --- a/app/aurelia/src/demo/welcome.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { bindable, customElement } from 'aurelia'; - -@customElement({ - name: 'storybook-welcome-component', - template: ` -
-

Welcome to storybook

-

This is a UI component dev environment for your app.

-

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

-

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

-

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

-

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

-

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

-
- - `, -}) -export default class Welcome { - @bindable() - showApp: MouseEvent; -} diff --git a/app/aurelia/src/server/framework-preset-aurelia.ts b/app/aurelia/src/server/framework-preset-aurelia.ts index 13b8f813948..08cf01f6680 100644 --- a/app/aurelia/src/server/framework-preset-aurelia.ts +++ b/app/aurelia/src/server/framework-preset-aurelia.ts @@ -18,31 +18,55 @@ export function webpack( ...config.module, rules: [ ...config.module.rules, - { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, + { + test: /\.(png|woff|woff2|eot|ttf|svg)$/, + loader: `${require.resolve('url-loader')}?limit=100000`, + }, { test: /\.css$/i, issuer: [{ not: [{ test: /\.html$/i }] }], - use: ['style-loader', 'css-loader'], + use: [ + { loader: require.resolve('style-loader') }, + { loader: require.resolve('css-loader') }, + ], }, { test: /\.css$/i, issuer: [{ test: /\.html$/i }], // CSS required in templates cannot be extracted safely // because Aurelia would try to require it again in runtime - use: 'css-loader', + use: require.resolve('css-loader'), }, { test: /\.scss$/, - use: ['style-loader', 'css-loader', 'sass-loader'], + use: [ + { loader: require.resolve('style-loader') }, + { loader: require.resolve('css-loader') }, + { loader: require.resolve('sass-loader') }, + ], issuer: /\.[tj]s$/i, }, { test: /\.scss$/, - use: ['css-loader', 'sass-loader'], + use: [ + { loader: require.resolve('css-loader') }, + { loader: require.resolve('sass-loader') }, + ], issuer: /\.html?$/i, }, - { test: /\.ts$/i, use: ['ts-loader', '@aurelia/webpack-loader'], exclude: /node_modules/ }, - { test: /\.html$/i, use: '@aurelia/webpack-loader', exclude: /node_modules/ }, + { + test: /\.ts$/i, + use: [ + { loader: require.resolve('ts-loader') }, + { loader: require.resolve('@aurelia/webpack-loader') }, + ], + exclude: /node_modules/, + }, + { + test: /\.html$/i, + use: require.resolve('@aurelia/webpack-loader'), + exclude: /node_modules/, + }, ], }, plugins: [...config.plugins, createForkTsCheckerInstance(tsLoaderOptions)], diff --git a/app/ember/README.md b/app/ember/README.md index f1c81cbb99a..635ead2f560 100644 --- a/app/ember/README.md +++ b/app/ember/README.md @@ -19,11 +19,30 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/ember/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/ember/workflows/publish-storybook) of your storybook and deploy it anywhere you want. ## Docs -- [Basics](https://storybook.js.org/basics/introduction) -- [Configurations](https://storybook.js.org/configurations/default-config) -- [Addons](https://storybook.js.org/addons/introduction) +- [Basics](https://storybook.js.org/docs/ember/get-started/introduction) +- [Configurations](https://storybook.js.org/docs/ember/configure/overview) +- [Addons](https://storybook.js.org/docs/ember/configure/storybook-addons) + +## Working with polyfills +If you need to use a polyfill that is already in use in our Ember application, +you will need to add some options to have Storybook working with them. + +In this example we'll use the [named-block-polyfill](https://github.com/ember-polyfills/ember-named-blocks-polyfill). +This example also assume that you already have the package in your `package.json`. + +In your `.storybook/main.js` you can add the following lines: +```javascript +const namedBlockPolyfill = require('ember-named-blocks-polyfill/lib/named-blocks-polyfill-plugin'); + +module.exports = { + emberOptions: { + polyfills: [namedBlockPolyfill], + }, + [...] +}; +``` diff --git a/app/ember/package.json b/app/ember/package.json index f0759e4c071..1d843d9a347 100644 --- a/app/ember/package.json +++ b/app/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/master/app/ember", "bugs": { @@ -25,14 +25,14 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { "@ember/test-helpers": "^1.7.0", - "@storybook/core": "6.0.0-beta.23", + "@storybook/core": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -42,9 +42,7 @@ "@babel/core": "*", "babel-plugin-ember-modules-api-polyfill": "^2.12.0", "babel-plugin-htmlbars-inline-precompile": "2 || 3", - "ember-source": "^3.16.0", - "react": "*", - "react-dom": "*" + "ember-source": "^3.16.0" }, "engines": { "node": ">=8.0.0" @@ -52,5 +50,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/app/ember/src/client/preview/index.ts b/app/ember/src/client/preview/index.ts index 1b159f531b6..60b1748e528 100644 --- a/app/ember/src/client/preview/index.ts +++ b/app/ember/src/client/preview/index.ts @@ -17,6 +17,6 @@ export const { const framework = 'ember'; export const storiesOf = (...args: any) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args: any) => coreConfigure(...args, framework); +export const configure = (...args: any) => coreConfigure(framework, ...args); export { forceReRender }; diff --git a/app/ember/src/server/framework-preset-babel-ember.ts b/app/ember/src/server/framework-preset-babel-ember.ts index 7ba0eea11ab..1e01c4eb9c4 100644 --- a/app/ember/src/server/framework-preset-babel-ember.ts +++ b/app/ember/src/server/framework-preset-babel-ember.ts @@ -1,14 +1,35 @@ import { precompile } from 'ember-source/dist/ember-template-compiler'; import { Configuration } from 'webpack'; // eslint-disable-line -export function babel(config: Configuration) { +let emberOptions: any; + +function precompileWithPlugins(string: string, options: any) { + const precompileOptions: any = options; + if (emberOptions && emberOptions.polyfills) { + precompileOptions.plugins = { ast: emberOptions.polyfills }; + } + + return precompile(string, precompileOptions); +} + +export function babel(config: Configuration, options: any) { + if (options && options.presetsList) { + options.presetsList.forEach((e: any, index: number) => { + if (e.preset && e.preset.emberOptions) { + emberOptions = e.preset.emberOptions; + // eslint-disable-next-line no-param-reassign + delete options.presetsList[index].preset.emberOptions; + } + }); + } + const babelConfigPlugins = config.plugins || []; const extraPlugins = [ [ require.resolve('babel-plugin-htmlbars-inline-precompile'), { - precompile, + precompile: precompileWithPlugins, modules: { 'ember-cli-htmlbars': 'hbs', 'ember-cli-htmlbars-inline-precompile': 'default', diff --git a/app/html/README.md b/app/html/README.md index ff667d03b18..8c3880dbfc6 100644 --- a/app/html/README.md +++ b/app/html/README.md @@ -21,5 +21,5 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/html/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/html/workflows/publish-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/html/package.json b/app/html/package.json index bea26341e7e..bb35a99bebb 100644 --- a/app/html/package.json +++ b/app/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,14 +28,15 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", @@ -44,9 +45,7 @@ "ts-dedent": "^1.1.1" }, "peerDependencies": { - "@babel/core": "*", - "react": "*", - "react-dom": "*" + "@babel/core": "*" }, "engines": { "node": ">=8.0.0" @@ -54,11 +53,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/html/src/client/preview/index.ts b/app/html/src/client/preview/index.ts index 6c33df55d28..cfd035cfcc0 100644 --- a/app/html/src/client/preview/index.ts +++ b/app/html/src/client/preview/index.ts @@ -25,7 +25,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/html/src/client/preview/render.ts b/app/html/src/client/preview/render.ts index a885d4d9ad9..bf1ce278270 100644 --- a/app/html/src/client/preview/render.ts +++ b/app/html/src/client/preview/render.ts @@ -1,5 +1,6 @@ import { document, Node } from 'global'; import dedent from 'ts-dedent'; +import { simulatePageLoad, simulateDOMContentLoaded } from '@storybook/client-api'; import { RenderContext } from './types'; const rootElement = document.getElementById('root'); @@ -17,6 +18,7 @@ export default function renderMain({ showMain(); if (typeof element === 'string') { rootElement.innerHTML = element; + simulatePageLoad(rootElement); } else if (element instanceof Node) { // Don't re-mount the element if it didn't change and neither did the story if (rootElement.firstChild === element && forceRender === true) { @@ -25,6 +27,7 @@ export default function renderMain({ rootElement.innerHTML = ''; rootElement.appendChild(element); + simulateDOMContentLoaded(); } else { showError({ title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, diff --git a/app/marionette/README.md b/app/marionette/README.md index fffc49bd35d..588aef1c049 100644 --- a/app/marionette/README.md +++ b/app/marionette/README.md @@ -21,5 +21,5 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. \ No newline at end of file +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/marionette/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/marionette/workflows/publish-storybook) of your storybook and deploy it anywhere you want. \ No newline at end of file diff --git a/app/marionette/package.json b/app/marionette/package.json index 986c61a64b1..3ebbc48d9fd 100644 --- a/app/marionette/package.json +++ b/app/marionette/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/marionette", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Marionette: Develop Marionette.js component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -25,7 +25,7 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "6.0.0-beta.23", + "@storybook/core": "6.1.0-alpha.14", "common-tags": "^1.8.0", "core-js": "^3.0.1", "global": "^4.3.2", @@ -39,8 +39,6 @@ "@babel/core": "*", "backbone": "*", "backbone.marionette": "*", - "react": "*", - "react-dom": "*", "underscore": "*" }, "engines": { @@ -48,5 +46,6 @@ }, "publishConfig": { "access": "public" - } + }, + "gitHead": "4571582a90b646e361cb37df525f62312486307a" } diff --git a/app/marko/README.md b/app/marko/README.md index 0da3d2eda4a..9e6f6a30afd 100644 --- a/app/marko/README.md +++ b/app/marko/README.md @@ -19,13 +19,13 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/marko/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/marko/workflows/publish-storybook) of your storybook and deploy it anywhere you want. Here are some featured storybooks that you can reference to see how Storybook works: ## Docs -- [Basics](https://storybook.js.org/basics/introduction) -- [Configurations](https://storybook.js.org/configurations/default-config) -- [Addons](https://storybook.js.org/addons/introduction) +- [Basics](https://storybook.js.org/docs/marko/get-started/introduction) +- [Configurations](https://storybook.js.org/docs/marko/configure/overview) +- [Addons](https://storybook.js.org/docs/marko/configure/storybook-addons) diff --git a/app/marko/package.json b/app/marko/package.json index 3a56eca487c..14319ea0838 100644 --- a/app/marko/package.json +++ b/app/marko/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/marko", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Marko: Develop Marko Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -27,15 +27,15 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { "@marko/webpack": "^2.1.0", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", @@ -44,8 +44,6 @@ "peerDependencies": { "@babel/core": "*", "marko": "^4.15.2", - "react": "*", - "react-dom": "*", "webpack": "^4" }, "engines": { @@ -54,5 +52,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/app/marko/src/client/preview/index.js b/app/marko/src/client/preview/index.js index b0f03db43bf..7635388ae4e 100644 --- a/app/marko/src/client/preview/index.js +++ b/app/marko/src/client/preview/index.js @@ -16,6 +16,6 @@ export const { const framework = 'marko'; export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); +export const configure = (...args) => coreConfigure(framework, ...args); export { forceReRender }; diff --git a/app/mithril/README.md b/app/mithril/README.md index 3570cd3675a..cd088b8ec8e 100644 --- a/app/mithril/README.md +++ b/app/mithril/README.md @@ -19,5 +19,5 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/mithril/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/mithril/workflows/publish-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/mithril/package.json b/app/mithril/package.json index 5a9d397388c..c1d30563f44 100644 --- a/app/mithril/package.json +++ b/app/mithril/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/mithril", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Mithril: Develop Mithril Component in isolation.", "keywords": [ "storybook" @@ -28,16 +28,16 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/core": "^7.9.6", - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@babel/core": "^7.11.5", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/mithril": "^2.0.0", "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", @@ -50,9 +50,7 @@ }, "peerDependencies": { "@babel/core": "*", - "mithril": "^1.1.6", - "react": "*", - "react-dom": "*" + "mithril": "^1.1.6 || ^2.0.0" }, "engines": { "node": ">=8.0.0" @@ -60,11 +58,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/mithril/src/client/preview/index.ts b/app/mithril/src/client/preview/index.ts index a68d76ad0b4..d78def9fc89 100644 --- a/app/mithril/src/client/preview/index.ts +++ b/app/mithril/src/client/preview/index.ts @@ -22,7 +22,7 @@ interface ClientApi extends ClientStoryApi { export const storiesOf: ClientApi['storiesOf'] = (kind, m) => (clientApi.storiesOf(kind, m) as ReturnType).addParameters({ framework }); -export const configure: ClientApi['configure'] = (...args) => coreConfigure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => coreConfigure(framework, ...args); export const { setAddon } = clientApi; export const { addDecorator } = clientApi; diff --git a/app/mithril/src/server/framework-preset-mithril.ts b/app/mithril/src/server/framework-preset-mithril.ts index c54576fb127..f42bf4c7477 100644 --- a/app/mithril/src/server/framework-preset-mithril.ts +++ b/app/mithril/src/server/framework-preset-mithril.ts @@ -3,6 +3,9 @@ import { TransformOptions } from '@babel/core'; export function babelDefault(config: TransformOptions) { return { ...config, - plugins: [...config.plugins, require.resolve('@babel/plugin-transform-react-jsx')], + plugins: [ + ...config.plugins, + [require.resolve('@babel/plugin-transform-react-jsx'), {}, 'preset'], + ], }; } diff --git a/app/preact/README.md b/app/preact/README.md index 61e791c27be..601a07a3fb5 100644 --- a/app/preact/README.md +++ b/app/preact/README.md @@ -19,11 +19,11 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/preact/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/preact/workflows/publish-storybook) of your storybook and deploy it anywhere you want. ## Docs -- [Basics](https://storybook.js.org/basics/introduction) -- [Configurations](https://storybook.js.org/configurations/default-config) -- [Addons](https://storybook.js.org/addons/introduction) +- [Basics](https://storybook.js.org/docs/preact/get-started/introduction) +- [Configurations](https://storybook.js.org/docs/preact/configure/overview) +- [Addons](https://storybook.js.org/docs/preact/configure/storybook-addons) diff --git a/app/preact/package.json b/app/preact/package.json index d481cf721d0..88a67d0e60e 100644 --- a/app/preact/package.json +++ b/app/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" @@ -28,15 +28,15 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.3.0", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", @@ -48,9 +48,7 @@ }, "peerDependencies": { "@babel/core": "*", - "preact": "^10.0.0", - "react": "*", - "react-dom": "*" + "preact": "^8.0.0|^10.0.0" }, "engines": { "node": ">=8.0.0" @@ -58,11 +56,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/preact/src/client/preview/index.ts b/app/preact/src/client/preview/index.ts index e5d03511e43..78e6485efc2 100644 --- a/app/preact/src/client/preview/index.ts +++ b/app/preact/src/client/preview/index.ts @@ -14,7 +14,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/preact/src/client/preview/render.tsx b/app/preact/src/client/preview/render.tsx index 693b5451270..cf4f855c1af 100644 --- a/app/preact/src/client/preview/render.tsx +++ b/app/preact/src/client/preview/render.tsx @@ -1,11 +1,31 @@ -import { h, render } from 'preact'; +import * as preact from 'preact'; import { document } from 'global'; import dedent from 'ts-dedent'; -import { RenderContext } from './types'; +import { RenderContext, StoryFnPreactReturnType } from './types'; const rootElement = document ? document.getElementById('root') : null; -export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) { +let renderedStory: Element; + +function preactRender(element: StoryFnPreactReturnType | null): void { + if ((preact as any).Fragment) { + // Preact 10 only: + preact.render(element, rootElement); + } else if (element) { + renderedStory = (preact.render(element, rootElement) as unknown) as Element; + } else { + preact.render(element, rootElement, renderedStory); + } +} + +export default function renderMain({ + storyFn, + kind, + name, + showMain, + showError, + forceRender, +}: RenderContext) { const element = storyFn(); if (!element) { @@ -19,9 +39,12 @@ export default function renderMain({ storyFn, kind, name, showMain, showError }: return; } - render(null, rootElement); + // But forceRender means that it's the same story, so we want to keep the state in that case. + if (!forceRender) { + preactRender(null); + } showMain(); - render(element, rootElement); + preactRender(element); } diff --git a/app/preact/src/server/framework-preset-preact.ts b/app/preact/src/server/framework-preset-preact.ts index 6153a8b25b9..b3eaabddeff 100644 --- a/app/preact/src/server/framework-preset-preact.ts +++ b/app/preact/src/server/framework-preset-preact.ts @@ -5,7 +5,7 @@ export function babelDefault(config: TransformOptions) { ...config, plugins: [ ...config.plugins, - [require.resolve('@babel/plugin-transform-react-jsx'), { pragma: 'h' }], + [require.resolve('@babel/plugin-transform-react-jsx'), { pragma: 'h' }, 'preset'], ], }; } diff --git a/app/rax/README.md b/app/rax/README.md index 4baa045a4d5..137faed65c7 100644 --- a/app/rax/README.md +++ b/app/rax/README.md @@ -19,11 +19,11 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/rax/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/rax/workflows/publish-storybook) of your storybook and deploy it anywhere you want. ## Docs -- [Basics](https://storybook.js.org/basics/introduction) -- [Configurations](https://storybook.js.org/configurations/default-config) -- [Addons](https://storybook.js.org/addons/introduction) +- [Basics](https://storybook.js.org/docs/rax/get-started/introduction) +- [Configurations](https://storybook.js.org/docs/rax/configure/overview) +- [Addons](https://storybook.js.org/docs/rax/configure/storybook-addons) diff --git a/app/rax/package.json b/app/rax/package.json index 4417ac5a599..ec80307ea39 100644 --- a/app/rax/package.json +++ b/app/rax/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/rax", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Rax: Develop Rax Component in isolation.", "keywords": [ "rax", @@ -28,13 +28,13 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "6.0.0-beta.23", + "@storybook/core": "6.1.0-alpha.14", "babel-preset-rax": "^1.0.0-beta.0", "core-js": "^3.0.1", "driver-dom": "^2.0.0", @@ -47,12 +47,17 @@ }, "peerDependencies": { "@babel/core": "*", - "rax": "^0.4.0 || ^1.0.0", - "react": "*", - "react-dom": "*" + "rax": "^0.4.0 || ^1.0.0" }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/app/rax/src/client/preview/index.js b/app/rax/src/client/preview/index.js index 3f83dedd56a..109004a46b5 100644 --- a/app/rax/src/client/preview/index.js +++ b/app/rax/src/client/preview/index.js @@ -16,6 +16,6 @@ export const { const framework = 'rax'; export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); +export const configure = (...args) => coreConfigure(framework, ...args); export { forceReRender }; diff --git a/app/react/README.md b/app/react/README.md index c3958dd0032..0a8e9e74e00 100644 --- a/app/react/README.md +++ b/app/react/README.md @@ -19,8 +19,8 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/react/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/react/workflows/publish-storybook) of your storybook and deploy it anywhere you want. Here are some featured storybooks that you can reference to see how Storybook works: @@ -41,6 +41,6 @@ But you probably also need to use types from `@types/node @types/react`. ## Docs -- [Basics](https://storybook.js.org/basics/introduction) -- [Configurations](https://storybook.js.org/configurations/default-config) -- [Addons](https://storybook.js.org/addons/introduction) +- [Basics](https://storybook.js.org/docs/react/get-started/introduction) +- [Configurations](https://storybook.js.org/docs/react/configure/overview) +- [Addons](https://storybook.js.org/docs/react/configure/storybook-addons) diff --git a/app/react/package.json b/app/react/package.json index 691854510c0..45228a3fd8f 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,17 +28,18 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.0.0", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", - "@storybook/node-logger": "6.0.0-beta.23", + "@babel/preset-flow": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@pmmmwh/react-refresh-webpack-plugin": "^0.4.2", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", + "@storybook/node-logger": "6.1.0-alpha.14", "@storybook/semver": "^7.3.2", "@svgr/webpack": "^5.4.0", "@types/webpack-env": "^1.15.2", @@ -49,21 +50,22 @@ "global": "^4.3.2", "lodash": "^4.17.15", "prop-types": "^15.7.2", + "react": "^16.8.3", "react-dev-utils": "^10.0.0", - "react-docgen-typescript-loader": "^3.7.2", + "react-docgen-typescript-plugin": "^0.5.2", + "react-dom": "^16.8.3", + "react-refresh": "^0.8.3", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1", "webpack": "^4.43.0" }, "devDependencies": { - "@storybook/client-api": "6.0.0-beta.23", + "@storybook/client-api": "6.1.0-alpha.14", "@types/node": "^14.0.10", "@types/webpack": "^4.41.12" }, "peerDependencies": { - "@babel/core": "^7.0.1", - "react": "*", - "react-dom": "*" + "@babel/core": "^7.11.5" }, "engines": { "node": ">=8.0.0" @@ -71,11 +73,14 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { + "types-6-0": [ + "ts3.4/dist/client/preview/types-6-0.d.ts" + ], "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/react/src/client/index.ts b/app/react/src/client/index.ts index 2d1362e9aa1..16f0714357f 100644 --- a/app/react/src/client/index.ts +++ b/app/react/src/client/index.ts @@ -10,6 +10,8 @@ export { forceReRender, } from './preview'; +export * from './preview/types-6-0'; + if (module && module.hot && module.hot.decline) { module.hot.decline(); } diff --git a/app/react/src/client/preview/index.ts b/app/react/src/client/preview/index.ts index 193aad79925..057878a0a6b 100644 --- a/app/react/src/client/preview/index.ts +++ b/app/react/src/client/preview/index.ts @@ -25,7 +25,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export type DecoratorFn = Parameters[0]; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; diff --git a/app/react/src/client/preview/types-6-0.ts b/app/react/src/client/preview/types-6-0.ts new file mode 100644 index 00000000000..20d38b8bec8 --- /dev/null +++ b/app/react/src/client/preview/types-6-0.ts @@ -0,0 +1,24 @@ +import { ComponentType } from 'react'; +import { Args as DefaultArgs, Annotations, BaseMeta, BaseStory } from '@storybook/addons'; +import { StoryFnReactReturnType } from './types'; + +export { Args, ArgTypes, Parameters, StoryContext } from '@storybook/addons'; + +type ReactComponent = ComponentType; +type ReactReturnType = StoryFnReactReturnType; + +/** + * Metadata to configure the stories for a component. + * + * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) + */ +export type Meta = BaseMeta & + Annotations; + +/** + * Story function that represents a component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type Story = BaseStory & + Annotations; diff --git a/app/react/src/server/framework-preset-cra.ts b/app/react/src/server/framework-preset-cra.ts index 8239290acc5..f0ce252f582 100644 --- a/app/react/src/server/framework-preset-cra.ts +++ b/app/react/src/server/framework-preset-cra.ts @@ -1,13 +1,14 @@ import { Configuration } from 'webpack'; import { logger } from '@storybook/node-logger'; import { isReactScriptsInstalled } from './cra-config'; +import type { StorybookOptions } from './types'; type Preset = string | { name: string }; 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'; + return /@storybook(\/|\\)preset-create-react-app/.test(presetName); }); if (!hasNewPreset) { @@ -19,10 +20,7 @@ const checkForNewPreset = (presetsList: Preset[]) => { } }; -export function webpackFinal( - config: Configuration, - { presetsList, configDir }: { presetsList: Preset[]; configDir: string } -) { +export function webpackFinal(config: Configuration, { presetsList }: StorybookOptions) { if (isReactScriptsInstalled()) { checkForNewPreset(presetsList); } diff --git a/app/react/src/server/framework-preset-react-docgen.test.ts b/app/react/src/server/framework-preset-react-docgen.test.ts index 33bde8f2862..fa36bd07371 100644 --- a/app/react/src/server/framework-preset-react-docgen.test.ts +++ b/app/react/src/server/framework-preset-react-docgen.test.ts @@ -1,9 +1,11 @@ +import ReactDocgenTypescriptPlugin from 'react-docgen-typescript-plugin'; import * as preset from './framework-preset-react-docgen'; +import type { StorybookOptions } from './types'; describe('framework-preset-react-docgen', () => { const babelPluginReactDocgenPath = require.resolve('babel-plugin-react-docgen'); - it('should return the config with the extra plugin', () => { + it('should return the babel config with the extra plugin', () => { const babelConfig = { babelrc: false, presets: ['env', 'foo-preset'], @@ -12,7 +14,7 @@ describe('framework-preset-react-docgen', () => { const config = preset.babel(babelConfig, { typescriptOptions: { check: false, reactDocgen: 'react-docgen' }, - }); + } as StorybookOptions); expect(config).toEqual({ babelrc: false, @@ -33,4 +35,46 @@ describe('framework-preset-react-docgen', () => { ], }); }); + + it('should return the webpack config with the extra plugin', () => { + const webpackConfig = { + plugins: [], + }; + + const config = preset.webpackFinal(webpackConfig, { + typescriptOptions: { check: false, reactDocgen: 'react-docgen-typescript' }, + }); + + expect(config).toEqual({ + plugins: [expect.any(ReactDocgenTypescriptPlugin)], + }); + }); + + it('should not add any extra plugins', () => { + const babelConfig = { + babelrc: false, + presets: ['env', 'foo-preset'], + plugins: ['foo-plugin'], + }; + + const webpackConfig = { + plugins: [], + }; + + const outputBabelconfig = preset.babel(babelConfig, { + typescriptOptions: { check: false, reactDocgen: false }, + }); + const outputWebpackconfig = preset.webpackFinal(webpackConfig, { + typescriptOptions: { check: false, reactDocgen: false }, + }); + + expect(outputBabelconfig).toEqual({ + babelrc: false, + presets: ['env', 'foo-preset'], + plugins: ['foo-plugin'], + }); + expect(outputWebpackconfig).toEqual({ + plugins: [], + }); + }); }); diff --git a/app/react/src/server/framework-preset-react-docgen.ts b/app/react/src/server/framework-preset-react-docgen.ts index a525c8d03f9..6f6cc2370a4 100644 --- a/app/react/src/server/framework-preset-react-docgen.ts +++ b/app/react/src/server/framework-preset-react-docgen.ts @@ -1,12 +1,15 @@ import type { TransformOptions } from '@babel/core'; import type { Configuration } from 'webpack'; -import type { StorybookOptions } from '@storybook/core/types'; +import ReactDocgenTypescriptPlugin from 'react-docgen-typescript-plugin'; +import type { StorybookOptions } from './types'; export function babel(config: TransformOptions, { typescriptOptions }: StorybookOptions) { const { reactDocgen } = typescriptOptions; - if (!reactDocgen) { + + if (reactDocgen === false) { return config; } + return { ...config, overrides: [ @@ -27,24 +30,13 @@ export function babel(config: TransformOptions, { typescriptOptions }: Storybook export function webpackFinal(config: Configuration, { typescriptOptions }: StorybookOptions) { const { reactDocgen, reactDocgenTypescriptOptions } = typescriptOptions; - if (reactDocgen !== 'react-docgen-typescript') return config; + + if (reactDocgen !== 'react-docgen-typescript') { + return config; + } + return { ...config, - module: { - ...config.module, - rules: [ - ...config.module.rules, - { - test: /\.tsx?$/, - // include: path.resolve(__dirname, "../src"), - use: [ - { - loader: require.resolve('react-docgen-typescript-loader'), - options: reactDocgenTypescriptOptions, - }, - ], - }, - ], - }, + plugins: [...config.plugins, new ReactDocgenTypescriptPlugin(reactDocgenTypescriptOptions)], }; } diff --git a/app/react/src/server/framework-preset-react.test.ts b/app/react/src/server/framework-preset-react.test.ts new file mode 100644 index 00000000000..653fd063b3f --- /dev/null +++ b/app/react/src/server/framework-preset-react.test.ts @@ -0,0 +1,96 @@ +import webpack from 'webpack'; +import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; +import * as preset from './framework-preset-react'; +import type { StorybookOptions } from './types'; + +const mockApply = jest.fn(); +jest.mock('@pmmmwh/react-refresh-webpack-plugin', () => { + return jest.fn().mockImplementation(() => { + return { apply: mockApply }; + }); +}); + +describe('framework-preset-react', () => { + const reactRefreshPath = require.resolve('react-refresh/babel'); + const webpackConfigMock: webpack.Configuration = { + plugins: [], + module: { + rules: [], + }, + }; + const babelConfigMock = {}; + + const storybookOptions: Partial = { + configType: 'DEVELOPMENT', + presets: { + apply: async () => ({ + fastRefresh: true, + }), + }, + presetsList: [], + }; + + const storybookOptionsDisabledRefresh: Partial = { + configType: 'DEVELOPMENT', + presets: { + apply: async () => ({ + fastRefresh: false, + }), + }, + }; + + describe('babel', () => { + it('should return a config with fast refresh plugin when fast refresh is enabled', async () => { + const config = await preset.babel(babelConfigMock, storybookOptions as StorybookOptions); + + expect(config.plugins).toEqual([reactRefreshPath]); + }); + + it('should return unchanged config without fast refresh plugin when fast refresh is disabled', async () => { + const config = await preset.babel( + babelConfigMock, + storybookOptionsDisabledRefresh as StorybookOptions + ); + + expect(config).toEqual(babelConfigMock); + }); + + it('should return unchanged config without fast refresh plugin when mode is not development', async () => { + const config = await preset.babel(babelConfigMock, { + ...storybookOptions, + configType: 'PRODUCTION', + } as StorybookOptions); + + expect(config).toEqual(babelConfigMock); + }); + }); + + describe('webpackFinal', () => { + it('should return a config with fast refresh plugin when fast refresh is enabled', async () => { + const config = await preset.webpackFinal( + webpackConfigMock, + storybookOptions as StorybookOptions + ); + + expect(config.plugins).toEqual([new ReactRefreshWebpackPlugin()]); + }); + + it('should return unchanged config without fast refresh plugin when fast refresh is disabled', async () => { + const config = await preset.webpackFinal( + webpackConfigMock, + storybookOptionsDisabledRefresh as StorybookOptions + ); + + expect(config).toEqual(webpackConfigMock); + }); + + it('should return unchanged config without fast refresh plugin when mode is not development', async () => { + const config = await preset.webpackFinal(webpackConfigMock, { + ...storybookOptions, + configType: 'PRODUCTION', + } as StorybookOptions); + + expect(config).toEqual(webpackConfigMock); + }); + }); +}); diff --git a/app/react/src/server/framework-preset-react.ts b/app/react/src/server/framework-preset-react.ts index 1c4f453df0f..c2180d367dd 100644 --- a/app/react/src/server/framework-preset-react.ts +++ b/app/react/src/server/framework-preset-react.ts @@ -1,6 +1,27 @@ import { TransformOptions } from '@babel/core'; +import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; +import type { Configuration } from 'webpack'; -export function babelDefault(config: TransformOptions) { +import { logger } from '@storybook/node-logger'; +import type { StorybookOptions } from '@storybook/core/types'; + +export async function babel(config: TransformOptions, options: StorybookOptions) { + const isDevelopment = options.configType === 'DEVELOPMENT'; + const reactOptions = await options.presets.apply('reactOptions', {}, options); + const fastRefreshEnabled = + isDevelopment && (reactOptions.fastRefresh || process.env.FAST_REFRESH === 'true'); + + if (!fastRefreshEnabled) { + return config; + } + + return { + ...config, + plugins: [require.resolve('react-refresh/babel'), ...(config.plugins || [])], + }; +} + +export async function babelDefault(config: TransformOptions) { return { ...config, presets: [ @@ -11,3 +32,20 @@ export function babelDefault(config: TransformOptions) { plugins: [...(config.plugins || []), require.resolve('babel-plugin-add-react-displayname')], }; } + +export async function webpackFinal(config: Configuration, options: StorybookOptions) { + const isDevelopment = options.configType === 'DEVELOPMENT'; + const reactOptions = await options.presets.apply('reactOptions', {}, options); + const fastRefreshEnabled = + isDevelopment && (reactOptions.fastRefresh || process.env.FAST_REFRESH === 'true'); + + if (!fastRefreshEnabled) { + return config; + } + + logger.info('=> Using React fast refresh feature.'); + return { + ...config, + plugins: [...(config.plugins || []), new ReactRefreshWebpackPlugin()], + }; +} diff --git a/app/react/src/server/types.ts b/app/react/src/server/types.ts new file mode 100644 index 00000000000..c7f47f08507 --- /dev/null +++ b/app/react/src/server/types.ts @@ -0,0 +1,10 @@ +import { StorybookOptions as BaseOptions } from '@storybook/core/types'; + +/** + * The internal options object, used by Storybook frameworks and addons. + */ +export interface StorybookOptions extends BaseOptions { + reactOptions?: { + fastRefresh?: boolean; + }; +} diff --git a/app/react/types-6-0.d.ts b/app/react/types-6-0.d.ts new file mode 100644 index 00000000000..6ed7da8e519 --- /dev/null +++ b/app/react/types-6-0.d.ts @@ -0,0 +1 @@ +export * from './dist/client/preview/types-6-0.d'; diff --git a/app/react/types/index.ts b/app/react/types/index.ts new file mode 100644 index 00000000000..537581de852 --- /dev/null +++ b/app/react/types/index.ts @@ -0,0 +1,10 @@ +import { StorybookConfig as BaseConfig } from '@storybook/core/types'; + +/** + * The interface for Storybook configuration in `main.ts` files. + */ +export interface StorybookConfig extends BaseConfig { + reactOptions?: { + fastRefresh?: boolean; + }; +} diff --git a/app/riot/README.md b/app/riot/README.md index 0cf65383a1e..3dbc9197222 100644 --- a/app/riot/README.md +++ b/app/riot/README.md @@ -1,25 +1,25 @@ -# Storybook for Riot - ---- - -Storybook for Riot is a UI development environment for the components written with riot.js. -With it, you can visualize different states of your UI components and develop them interactively. - -![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) - -Storybook runs outside of your app. -So you can develop UI components in isolation without worrying about app specific dependencies and requirements. - -## Getting Started - -```sh -cd my-riot-app -npx -p @storybook/cli sb init -``` - -For more information visit: [storybook.js.org](https://storybook.js.org) - ---- - -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +# Storybook for Riot + +--- + +Storybook for Riot is a UI development environment for the components written with riot.js. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-riot-app +npx -p @storybook/cli sb init +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/riot/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/riot/workflows/publish-storybook) of your storybook and deploy it anywhere you want. diff --git a/app/riot/package.json b/app/riot/package.json index ad3804a1b63..92b6878df80 100644 --- a/app/riot/package.json +++ b/app/riot/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/riot", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for riot.js: View riot snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,13 +28,13 @@ "standalone.js", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/core": "6.0.0-beta.23", + "@storybook/core": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "raw-loader": "^4.0.1", @@ -42,15 +42,13 @@ "ts-dedent": "^1.1.1" }, "devDependencies": { - "@babel/plugin-transform-modules-commonjs": "^7.9.6", - "@babel/preset-env": "^7.9.6", - "@babel/preset-flow": "^7.0.0", - "@babel/preset-react": "^7.7.0" + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/preset-env": "^7.11.5", + "@babel/preset-flow": "^7.10.4", + "@babel/preset-react": "^7.10.4" }, "peerDependencies": { "@babel/core": "*", - "react": "*", - "react-dom": "*", "riot": "^3.0.0 || ^4.0.0", "riot-compiler": "^3.5.1 || ^4.0.0", "riot-hot-reload": "^1.0.0", @@ -63,5 +61,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/app/riot/src/client/preview/index.js b/app/riot/src/client/preview/index.js index fac63097df0..4ecddb28577 100644 --- a/app/riot/src/client/preview/index.js +++ b/app/riot/src/client/preview/index.js @@ -18,7 +18,7 @@ export const { const framework = 'riot'; export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args) => coreConfigure(...args, framework); +export const configure = (...args) => coreConfigure(framework, ...args); const mount = vendorMount.bind(riot, '#root'); const compileNow = unboundCompileNow.bind(null, tag2); diff --git a/app/server/README.md b/app/server/README.md index 37bc6240d2e..d557c4efd4a 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -17,9 +17,295 @@ cd my-app npx -p @storybook/cli sb init -t server ``` +To configure the server that Storybook will connect to, export a global parameter `parameters.server.url` in `.storybook/preview.js`: + +```js +export const parameters = { + server: { + url: `http://localhost:${port}/storybook_preview`, + }, +}; +``` + +The URL you connect to should have the ability to render a story, see [server rendering](#server-rendering) below. + For more information visit: [storybook.js.org](https://storybook.js.org) ---- +## Writing Stories -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +To write a story, use whatever API is natural for your server-side rendering framework to generate set of JSON files of stories analogous to CSF files (see the [`server-kitchen-sink`](../../examples/server-kitchen-sink/stories) example for ideas). + +```json +{ + "title": "Component", + "parameters": { + "options": { "component": "my_widget" } + }, + "stories": [ + { + "name": "Default", + "parameters": { + "server": { "id": "path/of/your/story" } + } + } + ] +} +``` + +In your `.storybook/main.js` you simply provide a glob specifying the location of those JSON files, e.g. + +```js +module.exports = { + stories: ['../stories/**/*.stories.json'], +}; +``` + +Notice that the JSON does not specify a rendering function -- `@storybook/server` will instead call your `parameters.server.url` with the story's server id appended. + +For example the JSON story above is requivalent to the CSF definition: + +```javascript +export default { + title: 'Component', + parameters: { + options: { + component: 'my_widget', + } + } +}; + +export const Default = (args) => {}; +Default.storyName = 'Default'; +Default.parameters = { + server: { + id: 'path/of/your/story"' + } +}; +``` + +With the story HTML will be fetched from the server by making a GET request to http://localhost/storybook_preview/path/of/your/story` + +### Ruby/Rails support + +In particular the [View Component::Storybook](https://github.com/jonspalmer/view_component_storybook) gem provides a Ruby API for easily creating the above with a Ruby/Rails DSL (as well as providing a server rendering endpoint). + +## Server rendering + +The server rendering side of things is relatively straightfoward. When you browse to a story in the sidebar, Storybook will make a `fetch` request to `${parameters.server.url}/{parameters.server.id}` and display the HTML that is returned. + +You need to ensure the route in your server app renders the appropriate HTML when called in that fashion. + +### Passing parameters to the server + +Many components are likely to be dynamic - responding to parameters that change their content or appearance. `@storybook\server` has two mechanisms for passing those parameters to the server - `params` and `args`. Parameters defined in this way are appended to the fetch url as query string parameters. The server endpoing is responsible for interpreting those parameters and vary the returned html appropriately + +#### Constant parameters with `params` + +Static parameters can be defined using the `params` story parameter. For example suppose you have a Button component that has a label and color options: + +```json +{ + "title": "Buttons", + "stories": [ + { + "name": "Red", + "parameters": { + "server": { + "id": "button", + "params": { "color": "red", "label": "Stop" } + } + } + }, + { + "name": "Green", + "parameters": { + "server": { + "id": "button", + "params": { "color": "green", "label": "OK" } + } + } + } + ] +} +``` + +The Red and Green story HTML will be fetched from the urls `server.url/controls/button?color=red&label=Stopr` and `server.url/controls/button?color=green&label=OK` + +Like all story parameters server params can be defined in the default export and overriden in stories. + +```json +{ + "title": "Buttons", + "parameters": { + "server": { + "params": { "color": "red" } + } + }, + "stories": [ + { + "name": "Default", + "parameters": { + "server": { + "id": "button", + "params": { "label": "Stop" } + } + } + }, + { + "name": "Green", + "parameters": { + "server": { + "id": "button", + "params": { "color": "green", "label": "OK" } + } + } + } + ] +} +``` + +#### Dynamic parameters with `args` and Controls + +Dynamic parameters can be defined using args and the Controls addon + +```json +{ + "title": "Buttons", + "stories": [ + { + "name": "Red", + "parameters": { + "server": { + "id": "button" + } + }, + "args": { "color": "red", "label": "Stop" } + }, + { + "name": "Green", + "parameters": { + "server": { + "id": "button" + } + }, + "args": { "color": "green", "label": "Go" } + } + ] +} +``` + +Story args are passed to the server as url query parameters just like `params` except now they can be varied on the Controls addon panel. + +Just like CSF stories we can define `argTypes` to specify the controls used in the controls panel. `argTypes` can be defined at the default or story level. + +```json +{ + "title": "Buttons", + "argTypess": { + "color": { "control": { "type": "color" } } + }, + "stories": [ + { + "name": "Red", + "parameters": { + "server": { + "id": "button" + } + }, + "args": { "color": "red", "label": "Stop" } + }, + { + "name": "Green", + "parameters": { + "server": { + "id": "button" + } + }, + "args": { "color": "green", "label": "Go" } + } + ] +} +``` + +## Addon compatibility + +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/react/configure/storybook-addons) and a great API to customize as you wish. As some addons assume the story is rendered in JS, they may not work with `@storybook/server` (yet!). + +Many addons that act on the manager side (such as `backgrounds` and `viewport`) will work out of the box with `@storybook/server` -- you can configure them with parameters written on the server as usual. + +### Controls + +To configure controls, simple add `args` and `argTypes` keys to the story JSON much like you would CSF: + +```json +{ + "title": "Controls", + "stories": [ + { + "name": "Button", + "parameters": { + "server": { "id": "controls/button" } + }, + "args": { "button_text": "Push Me", "color": "red" }, + "argsTypes": { "button_text": { "control": { "type": "color" } } } + }, + ] +} +``` + +The controls values will be added to your story URL as query parameters. + +### Actions + +To use actions, use the `parameters.actions.handles` parameter: + +```json +{ + "title": "Actions", + "stories": [ + { + "name": "Button", + "parameters": { + "server": { "id": "actions/button" }, + "actions": { + "handles": ["mouseover", "click .btn"] + } + } + } + ] +} +``` + +## Advanced Configuraiton + +### fetchStoryHtml + +For control over how `@storybook/server` fetches Html from the server you can provide a `fetchStoryHtml` function as a parameter. You would typically set this in `.storybook/preview.js` but it's just a regular Storybook parameter so could be overriden at the stories or story level. + + +```javascript +// .storybook/preview.js + +const fetchStoryHtml = async (url, path, params) => { + // Custom fetch impelentation + // .... + return html; +}; + +export const parameters = { + server: { + url: `http://localhost:${port}/storybook_preview`, + fetchStoryHtml + }, +}; +``` + +`fetchStoryHtml` should be an async function with the following signature + +```javascript +type FetchStoryHtmlType = (url: string, id: string, params: any) => Promise; +``` + + * url: Server url configured by the `parameters.server.url` + * id: Id of the story being rendered given by `parameters.server.id` + * params: Mereged story params `parameters.server.params`and story args diff --git a/app/server/package.json b/app/server/package.json index 69bba972332..c997151b9eb 100644 --- a/app/server/package.json +++ b/app/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,15 +28,17 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", - "@storybook/node-logger": "^5.2.8", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", + "@storybook/node-logger": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", @@ -53,5 +55,12 @@ "publishConfig": { "access": "public" }, - "gitHead": "6ad2664adf18b50ea3ce015cbae2ff3e9a60cc4a" + "gitHead": "4571582a90b646e361cb37df525f62312486307a", + "typesVersions": { + "<3.8": { + "*": [ + "ts3.4/*" + ] + } + } } diff --git a/app/server/src/client/preview/index.ts b/app/server/src/client/preview/index.ts index bccce6b8ac2..9a5ac6ebbda 100644 --- a/app/server/src/client/preview/index.ts +++ b/app/server/src/client/preview/index.ts @@ -2,14 +2,14 @@ import { start } from '@storybook/core/client'; import { ClientStoryApi, Loadable } from '@storybook/addons'; import './globals'; -import { renderMain as render, setFetchStoryHtml } from './render'; -import { StoryFnServerReturnType, IStorybookSection, ConfigureOptionsArgs } from './types'; +import { renderMain as render } from './render'; +import { StoryFnServerReturnType, IStorybookSection } from './types'; const framework = 'server'; interface ClientApi extends ClientStoryApi { setAddon(addon: any): void; - configure(loader: Loadable, module: NodeModule, options?: ConfigureOptionsArgs): void; + configure(loader: Loadable, module: NodeModule): void; getStorybook(): IStorybookSection[]; clearDecorators(): void; forceReRender(): void; @@ -24,14 +24,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -const setRenderFetchAndConfigure: ClientApi['configure'] = (loader, module, options) => { - if (options && options.fetchStoryHtml) { - setFetchStoryHtml(options.fetchStoryHtml); - } - api.configure(loader, module, framework); -}; - -export const configure: ClientApi['configure'] = setRenderFetchAndConfigure; +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const { addDecorator, addParameters, diff --git a/app/server/src/client/preview/render.ts b/app/server/src/client/preview/render.ts index 904ac552a39..3d512592035 100644 --- a/app/server/src/client/preview/render.ts +++ b/app/server/src/client/preview/render.ts @@ -1,10 +1,12 @@ import { document, fetch, Node } from 'global'; import dedent from 'ts-dedent'; +import { Args, ArgTypes } from '@storybook/api'; +import { simulatePageLoad, simulateDOMContentLoaded } from '@storybook/client-api'; import { RenderContext, FetchStoryHtmlType } from './types'; const rootElement = document.getElementById('root'); -let fetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => { +const defaultFetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => { const fetchUrl = new URL(`${url}/${path}`); fetchUrl.search = new URLSearchParams(params).toString(); @@ -12,8 +14,37 @@ let fetchStoryHtml: FetchStoryHtmlType = async (url, path, params) => { return response.text(); }; +const buildStoryArgs = (args: Args, argTypes: ArgTypes) => { + const storyArgs = { ...args }; + + Object.keys(argTypes).forEach((key: string) => { + const argType = argTypes[key]; + const { control } = argType; + const controlType = control && control.type.toLowerCase(); + const argValue = storyArgs[key]; + switch (controlType) { + case 'date': + // For cross framework & language support we pick a consistent representation of Dates as strings + storyArgs[key] = new Date(argValue).toISOString(); + break; + case 'array': { + // use the supplied separator when seriazlizing an array as a string + const separator = control.separator || ','; + storyArgs[key] = argValue.join(separator); + break; + } + case 'object': + // send objects as JSON strings + storyArgs[key] = JSON.stringify(argValue); + break; + default: + } + }); + + return storyArgs; +}; + export async function renderMain({ - storyFn, id, kind, name, @@ -21,20 +52,26 @@ export async function renderMain({ showError, forceRender, parameters, + storyFn, + args, + argTypes, }: RenderContext) { - const storyParams = storyFn(); + // Some addons wrap the storyFn so we need to call it even though Server doesn't need the answer + storyFn(); + const storyArgs = buildStoryArgs(args, argTypes); const { - server: { url, id: storyId, params }, + server: { url, id: storyId, fetchStoryHtml = defaultFetchStoryHtml, params }, } = parameters; const fetchId = storyId || id; - const fetchParams = { ...params, ...storyParams }; + const fetchParams = { ...params, ...storyArgs }; const element = await fetchStoryHtml(url, fetchId, fetchParams); showMain(); if (typeof element === 'string') { rootElement.innerHTML = element; + simulatePageLoad(rootElement); } else if (element instanceof Node) { // Don't re-mount the element if it didn't change and neither did the story if (rootElement.firstChild === element && forceRender === true) { @@ -43,6 +80,7 @@ export async function renderMain({ rootElement.innerHTML = ''; rootElement.appendChild(element); + simulateDOMContentLoaded(); } else { showError({ title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, @@ -53,9 +91,3 @@ export async function renderMain({ }); } } - -export const setFetchStoryHtml: any = (fetchHtml: FetchStoryHtmlType) => { - if (fetchHtml !== undefined) { - fetchStoryHtml = fetchHtml; - } -}; diff --git a/app/server/src/client/preview/types.ts b/app/server/src/client/preview/types.ts index 23bdf5b3a12..8238b46d042 100644 --- a/app/server/src/client/preview/types.ts +++ b/app/server/src/client/preview/types.ts @@ -18,7 +18,3 @@ export interface ShowErrorArgs { title: string; description: string; } - -export interface ConfigureOptionsArgs { - fetchStoryHtml: FetchStoryHtmlType; -} diff --git a/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot index 204ba40358b..07c3e8a03cb 100644 --- a/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/a11y.snapshot @@ -11,13 +11,11 @@ export default { } }; -export const Label = () => {}; -Label.story = { - name: 'Label', - parameters: { - server: { - id: 'addons/a11y/label' - } +export const Label = (args) => {}; +Label.storyName = 'Label'; +Label.parameters = { + server: { + id: 'addons/a11y/label' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/actions.snapshot b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot index 0df7e9216ed..f6631a09b33 100644 --- a/app/server/src/lib/compiler/__testfixtures__/actions.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/actions.snapshot @@ -11,20 +11,18 @@ export default { } }; -export const Multiple_actions_config = () => {}; -Multiple_actions_config.story = { - name: 'Multiple actions + config', - parameters: { - actions: [ - 'click', - 'contextmenu', - { - clearOnStoryChange: false - } - ], - server: { - id: 'addons/actions/story3' +export const Multiple_actions_config = (args) => {}; +Multiple_actions_config.storyName = 'Multiple actions + config'; +Multiple_actions_config.parameters = { + actions: [ + 'click', + 'contextmenu', + { + clearOnStoryChange: false } + ], + server: { + id: 'addons/actions/story3' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot index c1408fd5dbc..93da5a33dd0 100644 --- a/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/backgrounds.snapshot @@ -19,13 +19,11 @@ export default { } }; -export const Story_1 = () => {}; -Story_1.story = { - name: 'Story 1', - parameters: { - server: { - id: 'addons/backgrounds/story1' - } +export const Story_1 = (args) => {}; +Story_1.storyName = 'Story 1'; +Story_1.parameters = { + server: { + id: 'addons/backgrounds/story1' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/controls.json b/app/server/src/lib/compiler/__testfixtures__/controls.json new file mode 100644 index 00000000000..6d33cba3316 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/controls.json @@ -0,0 +1,39 @@ +{ + "title": "Addons/Controls", + "parameters": { + "options": { "selectedPanel": "storybook/controls/panel" } + }, + "stories": [ + { + "name": "Simple", + "parameters": { + "server": { "id": "addons/controls/simple" } + }, + "args": { + "name": "John Doe", + "birthday": "1960-12-25T00:42:03.600Z", + "favorite_color": "red", + "active": true, + "pets": 2, + "sports": ["football", "baseball"], + "favorite_food": "Ice Cream", + "other_thinkgs": {"hair": "Brown", "eyes": "Blue"} + }, + "argTypes": { + "birthday": { "control": { "type": "date" } }, + "favorite_color": { "control": { "type": "color" } }, + "favorite_food": { + "control": { + "type": "select", + "options":{ + "hot_dog": "Hot Dog", + "pizza": "Pizza", + "burgers": "Burgers", + "ice_cream": "Ice Cream" + } + } + } + } + } + ] +} diff --git a/app/server/src/lib/compiler/__testfixtures__/controls.snapshot b/app/server/src/lib/compiler/__testfixtures__/controls.snapshot new file mode 100644 index 00000000000..cdeb4fa5b87 --- /dev/null +++ b/app/server/src/lib/compiler/__testfixtures__/controls.snapshot @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`json-to-csf-compiler controls.json 1`] = ` +" +export default { + title: 'Addons/Controls', + parameters: { + options: { + selectedPanel: 'storybook/controls/panel' + } + } +}; + +export const Simple = (args) => {}; +Simple.storyName = 'Simple'; +Simple.parameters = { + server: { + id: 'addons/controls/simple' + } +}; +Simple.args = { + name: 'John Doe', + birthday: '1960-12-25T00:42:03.600Z', + favorite_color: 'red', + active: true, + pets: 2, + sports: [ + 'football', + 'baseball' + ], + favorite_food: 'Ice Cream', + other_thinkgs: { + hair: 'Brown', + eyes: 'Blue' + } +}; +Simple.argTypes = { + birthday: { + control: { + type: 'date' + } + }, + favorite_color: { + control: { + type: 'color' + } + }, + favorite_food: { + control: { + type: 'select', + options: { + hot_dog: 'Hot Dog', + pizza: 'Pizza', + burgers: 'Burgers', + ice_cream: 'Ice Cream' + } + } + } +}; +" +`; diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json index 63909b27c77..c7e719c6f11 100644 --- a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.json @@ -1,6 +1,5 @@ { "title": "Kitchen Sink", - "addons": ["knobs"], "parameters": { "backgrounds": [ { "name": "light", "value": "#eeeeee" }, @@ -16,7 +15,6 @@ "name": "Heading", "parameters": { "actions": ["click", "contextmenu", { "clearOnStoryChange": false }], - "notes": "My notes on some bold text", "server": { "id": "demo/heading", "params": { @@ -24,15 +22,11 @@ } } }, - "knobs": [ - { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, - { "type": "number", "name": "Age", "value": 44, "param": "age"} - ] + "args": { "name": "John Doe", "age": 44 } }, { "name": "Button", "parameters": { - "notes": "My notes on a button", "server": { "id": "demo/button" } } } diff --git a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot index e3648ad5eec..ccf29b3f440 100644 --- a/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot @@ -1,13 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`json-to-csf-compiler kitchen_sink.json 1`] = ` -"import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; - +" export default { title: 'Kitchen Sink', - decorators: [ - withKnobs - ], parameters: { backgrounds: [ { @@ -31,40 +27,33 @@ export default { } }; -export const Heading = () => { - return { - name: text('Name', 'John Doe'), - age: number('Age', 44, {}), - }; -}; -Heading.story = { - name: 'Heading', - parameters: { - actions: [ - 'click', - 'contextmenu', - { - clearOnStoryChange: false - } - ], - notes: 'My notes on some bold text', - server: { - id: 'demo/heading', - params: { - color: 'orange' - } +export const Heading = (args) => {}; +Heading.storyName = 'Heading'; +Heading.parameters = { + actions: [ + 'click', + 'contextmenu', + { + clearOnStoryChange: false + } + ], + server: { + id: 'demo/heading', + params: { + color: 'orange' } } }; +Heading.args = { + name: 'John Doe', + age: 44 +}; -export const Button = () => {}; -Button.story = { - name: 'Button', - parameters: { - notes: 'My notes on a button', - server: { - id: 'demo/button' - } +export const Button = (args) => {}; +Button.storyName = 'Button'; +Button.parameters = { + server: { + id: 'demo/button' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.json b/app/server/src/lib/compiler/__testfixtures__/knobs.json deleted file mode 100644 index fc10ce3062e..00000000000 --- a/app/server/src/lib/compiler/__testfixtures__/knobs.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "title": "Addons/Knobs", - "addons": ["knobs"], - "parameters": { - "options": { "selectedPanel": "storybook/knobs/panel" } - }, - "stories": [ - { - "name": "Simple", - "parameters": { - "server": { "id": "addons/knobs/simple" } - }, - "knobs": [ - { "type": "text", "name": "Name", "value": "John Doe", "param": "name"}, - { "type": "date", "name": "Birthday", "value": "1960-12-25T00:42:03.600Z", "param": "birthday"}, - { "type": "color", "name": "Favorite Color", "value": "red", "param": "favorite_color"}, - { "type": "boolean", "name": "Active", "value": true, "param": "active"}, - { "type": "number", "name": "Pets", "value": 2, "param": "pets"}, - { "type": "array", "name": "Sports", "value": ["football", "baseball"], "param": "sports"}, - { - "type": "select", - "name": "Favorite Food", - "value": "Ice Cream", - "options": { - "hot_dog": "Hot Dog", - "pizza": "Pizza", - "burgers": "Burgers", - "ice_cream": "Ice Cream" - }, - "param": "favorite_food" - }, - { "type": "object", "name": "Other Things", "value": {"hair": "Brown", "eyes": "Blue"}, "param": "other_thinkgs"} - ] - } - ] -} diff --git a/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot b/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot deleted file mode 100644 index e6d54bc510d..00000000000 --- a/app/server/src/lib/compiler/__testfixtures__/knobs.snapshot +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`json-to-csf-compiler knobs.json 1`] = ` -"import { array, boolean, color, date, number, object, select, text, withKnobs } from '@storybook/addon-knobs'; - -export default { - title: 'Addons/Knobs', - decorators: [ - withKnobs - ], - parameters: { - options: { - selectedPanel: 'storybook/knobs/panel' - } - } -}; - -export const Simple = () => { - return { - name: text('Name', 'John Doe'), - birthday: new Date(date('Birthday', new Date('1960-12-25T00:42:03.600Z'))).toISOString(), - favorite_color: color('Favorite Color', 'red'), - active: boolean('Active', true), - pets: number('Pets', 2, {}), - sports: array('Sports', [ - 'football', - 'baseball' - ], ',').join(','), - favorite_food: select('Favorite Food', { - hot_dog: 'Hot Dog', - pizza: 'Pizza', - burgers: 'Burgers', - ice_cream: 'Ice Cream' - }, 'Ice Cream'), - other_thinkgs: JSON.stringify(object('Other Things', { - hair: 'Brown', - eyes: 'Blue' - })), - }; -}; -Simple.story = { - name: 'Simple', - parameters: { - server: { - id: 'addons/knobs/simple' - } - } -}; -" -`; diff --git a/app/server/src/lib/compiler/__testfixtures__/links.snapshot b/app/server/src/lib/compiler/__testfixtures__/links.snapshot index 70dda862c9a..6ad29b4fbc4 100644 --- a/app/server/src/lib/compiler/__testfixtures__/links.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/links.snapshot @@ -6,13 +6,11 @@ export default { title: 'Welcome', }; -export const Welcome = () => {}; -Welcome.story = { - name: 'Welcome', - parameters: { - server: { - id: 'welcome/welcome' - } +export const Welcome = (args) => {}; +Welcome.storyName = 'Welcome'; +Welcome.parameters = { + server: { + id: 'welcome/welcome' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot index 4e1bc924e2f..ff2cb558fb5 100644 --- a/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/multiple_stories.snapshot @@ -6,33 +6,27 @@ export default { title: 'Demo', }; -export const Heading = () => {}; -Heading.story = { - name: 'Heading', - parameters: { - server: { - id: 'demo/heading' - } +export const Heading = (args) => {}; +Heading.storyName = 'Heading'; +Heading.parameters = { + server: { + id: 'demo/heading' } }; -export const Headings = () => {}; -Headings.story = { - name: 'Headings', - parameters: { - server: { - id: 'demo/headings' - } +export const Headings = (args) => {}; +Headings.storyName = 'Headings'; +Headings.parameters = { + server: { + id: 'demo/headings' } }; -export const Button = () => {}; -Button.story = { - name: 'Button', - parameters: { - server: { - id: 'demo/button' - } +export const Button = (args) => {}; +Button.storyName = 'Button'; +Button.parameters = { + server: { + id: 'demo/button' } }; " diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.json b/app/server/src/lib/compiler/__testfixtures__/notes.json deleted file mode 100644 index 9dc4e21c17d..00000000000 --- a/app/server/src/lib/compiler/__testfixtures__/notes.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Addons/Notes", - "stories": [ - { - "name": "Simple note", - "parameters": { - "notes": "My notes on some bold text", - "server": { "id": "addons/notes/story1" } - } - } - ] -} diff --git a/app/server/src/lib/compiler/__testfixtures__/notes.snapshot b/app/server/src/lib/compiler/__testfixtures__/notes.snapshot deleted file mode 100644 index 95760047ea4..00000000000 --- a/app/server/src/lib/compiler/__testfixtures__/notes.snapshot +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`json-to-csf-compiler notes.json 1`] = ` -" -export default { - title: 'Addons/Notes', -}; - -export const Simple_note = () => {}; -Simple_note.story = { - name: 'Simple note', - parameters: { - notes: 'My notes on some bold text', - server: { - id: 'addons/notes/story1' - } - } -}; -" -`; diff --git a/app/server/src/lib/compiler/__testfixtures__/params.snapshot b/app/server/src/lib/compiler/__testfixtures__/params.snapshot index c0517efed52..5d6fb7dc390 100644 --- a/app/server/src/lib/compiler/__testfixtures__/params.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/params.snapshot @@ -13,15 +13,13 @@ export default { } }; -export const Story = () => {}; -Story.story = { - name: 'Story', - parameters: { - server: { - id: 'params/story', - params: { - message: 'Hello World' - } +export const Story = (args) => {}; +Story.storyName = 'Story'; +Story.parameters = { + server: { + id: 'params/story', + params: { + message: 'Hello World' } } }; diff --git a/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot index 897da9eaf04..81f7ea25808 100644 --- a/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot +++ b/app/server/src/lib/compiler/__testfixtures__/params_override.snapshot @@ -13,16 +13,14 @@ export default { } }; -export const Override = () => {}; -Override.story = { - name: 'Override', - parameters: { - server: { - id: 'params/override', - params: { - message: 'Hello World', - color: 'green' - } +export const Override = (args) => {}; +Override.storyName = 'Override'; +Override.parameters = { + server: { + id: 'params/override', + params: { + message: 'Hello World', + color: 'green' } } }; diff --git a/app/server/src/lib/compiler/decorators/index.ts b/app/server/src/lib/compiler/decorators/index.ts deleted file mode 100644 index 77d988f10d0..00000000000 --- a/app/server/src/lib/compiler/decorators/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StorybookSection, Decorator } from '../types'; -import { knobsDecorator } from './knobs'; - -const allDecorators: Record = { - knobs: knobsDecorator, -}; - -export function decorateSection(section: StorybookSection, addons: string[]): StorybookSection { - const decorators = Object.keys(allDecorators) - .filter((addon) => addons.includes(addon)) - .map((addon) => allDecorators[addon]); - - return decorators.reduce((sec, decorator) => decorator(sec), section); -} diff --git a/app/server/src/lib/compiler/decorators/knobs.ts b/app/server/src/lib/compiler/decorators/knobs.ts deleted file mode 100644 index ac93b1842cc..00000000000 --- a/app/server/src/lib/compiler/decorators/knobs.ts +++ /dev/null @@ -1,95 +0,0 @@ -import dedent from 'ts-dedent'; -import { StorybookSection, StorybookStory } from '../types'; -import { decorateSimpleAddon, importMeta } from './utils'; -import { stringifyObject } from '../stringifier'; - -type KnobType = 'text' | 'number' | 'color' | 'array' | 'boolean' | 'object' | 'date' | 'select'; - -interface StoryKnob { - param: string; - type: KnobType; - name: string; - value: any; - [x: string]: any; -} - -function stringifyKnob(knob: StoryKnob) { - const { param, type, name, value, ...opts } = knob; - const knobParam = param || name; // Todo: can we do away with this? - const level = 2; - const stringifiedValue = stringifyObject(value, level); - // TODO: Add group - const knobFunction = ((t) => { - switch (t) { - case 'text': - return `text('${name}', ${stringifiedValue})`; - case 'number': - return `number('${name}', ${stringifiedValue}, ${stringifyObject(opts, level)})`; - case 'color': - return `color('${name}', ${stringifiedValue})`; - case 'array': { - const separator = opts.separator || ','; - return `array('${name}', ${stringifiedValue}, '${separator}').join('${separator}')`; - } - case 'boolean': - return `boolean('${name}', ${stringifiedValue})`; - case 'object': - return `JSON.stringify(object('${name}', ${stringifiedValue}))`; - case 'date': - return `new Date(date('${name}', new Date(${stringifiedValue}))).toISOString()`; - case 'select': - return `select('${name}', ${stringifyObject(opts.options, level)}, ${stringifiedValue})`; - default: - return ''; - } - })(type); - - return `${knobParam}: ${knobFunction}`; -} - -function stringifyStoryFunction(knobs: StoryKnob[], storyFn: string) { - if (!knobs || knobs.length === 0) return storyFn; - - return dedent` - () => { - return { - ${knobs.map((knob: any) => `${stringifyKnob(knob)},`).join('\n ')} - }; - } - `; -} - -function knobsStoryDecorator(story: StorybookStory): StorybookStory { - const { name, storyFn, knobs, ...options } = story; - - return { - name, - storyFn: stringifyStoryFunction(knobs, storyFn), - ...options, - }; -} - -export function knobsDecorator(section: StorybookSection): StorybookSection { - const { title, imports, decorators, stories, ...options } = decorateSimpleAddon(section, 'knobs'); - const { importName, moduleName } = importMeta('knobs'); - - const knobImports = [ - importName, - 'array', - 'boolean', - 'color', - 'date', - 'text', - 'number', - 'object', - 'select', - ]; - - return { - title, - imports: { ...imports, ...{ [moduleName]: knobImports } }, - decorators, - stories: stories.map((story) => knobsStoryDecorator(story)), - ...options, - }; -} diff --git a/app/server/src/lib/compiler/decorators/utils.ts b/app/server/src/lib/compiler/decorators/utils.ts deleted file mode 100644 index 838df57a9d7..00000000000 --- a/app/server/src/lib/compiler/decorators/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { StorybookSection } from '../types'; - -export function importMeta(addon: string) { - return { - importName: `with${addon.charAt(0).toUpperCase() + addon.slice(1)}`, - moduleName: `@storybook/addon-${addon}`, - }; -} - -export function decorateSimpleAddon(section: StorybookSection, addon: string) { - const { title, imports, decorators, stories, ...options } = section; - const { importName, moduleName } = importMeta(addon); - - return { - title, - imports: { ...imports, ...{ [moduleName]: [importName] } }, - decorators: [...decorators, importName], - stories, - ...options, - }; -} diff --git a/app/server/src/lib/compiler/index.ts b/app/server/src/lib/compiler/index.ts index 8b329fb991e..c2afad7b8b8 100644 --- a/app/server/src/lib/compiler/index.ts +++ b/app/server/src/lib/compiler/index.ts @@ -7,14 +7,13 @@ import { } from './types'; import { stringifySection } from './stringifier'; -import { decorateSection } from './decorators'; function createStory(storyArgs: CompileStorybookStoryArgs): StorybookStory { const { name, ...options } = storyArgs; return { name, - storyFn: '() => {}', + storyFn: '(args) => {}', ...options, }; } @@ -31,8 +30,5 @@ function createSection(args: CompileStorybookSectionArgs): StorybookSection { } export function compileCsfModule(args: CompileCsfModuleArgs): string { - const { addons = [], ...compileSectionArgs } = args; - const storybookSection = createSection(compileSectionArgs); - const decoratedSection = decorateSection(storybookSection, addons); - return stringifySection(decoratedSection); + return stringifySection(createSection(args)); } diff --git a/app/server/src/lib/compiler/stringifier.ts b/app/server/src/lib/compiler/stringifier.ts index 7dff01084c5..5b4ada166b2 100644 --- a/app/server/src/lib/compiler/stringifier.ts +++ b/app/server/src/lib/compiler/stringifier.ts @@ -60,17 +60,20 @@ export function stringifyDefault(section: StorybookSection): string { } export function stringifyStory(story: StorybookStory): string { - const { name, storyFn, decorators, ...options } = story; + const { name, storyFn, ...options } = story; const storyId = identifier(name); - const decoratorsString = stringifyDecorators(decorators); - const optionsString = stringifyObject({ name, ...options }, 0, true); + const storyStrings = [ + `export const ${storyId} = ${storyFn};`, + `${storyId}.storyName = '${name}';`, + ]; - let storyString = ''; - if (decoratorsString.length > 0 || optionsString.length > 0) { - storyString = `${storyId}.story = {${decoratorsString}${optionsString}\n};\n`; - } - return `export const ${storyId} = ${storyFn};\n${storyString}`; + Object.keys(options).forEach((key) => { + storyStrings.push(`${storyId}.${key} = ${stringifyObject(options[key])};`); + }); + storyStrings.push(''); + + return storyStrings.join('\n'); } export function stringifySection(section: StorybookSection): string { @@ -80,6 +83,5 @@ export function stringifySection(section: StorybookSection): string { ...section.stories.map((story) => stringifyStory(story)), ].join('\n'); - // console.log('sectionString:\n', sectionString); return sectionString; } diff --git a/app/server/src/lib/compiler/types.ts b/app/server/src/lib/compiler/types.ts index 0de8dbd14f2..7b08af446bc 100644 --- a/app/server/src/lib/compiler/types.ts +++ b/app/server/src/lib/compiler/types.ts @@ -27,5 +27,3 @@ export interface StorybookSection { stories: StorybookStory[]; [x: string]: any; } - -export type Decorator = (section: StorybookSection) => StorybookSection; diff --git a/app/svelte/README.md b/app/svelte/README.md index 630698b3991..f77c0356f43 100644 --- a/app/svelte/README.md +++ b/app/svelte/README.md @@ -19,8 +19,8 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/svelte/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/svelte/workflows/publish-storybook) of your storybook and deploy it anywhere you want. ## TODOs diff --git a/app/svelte/package.json b/app/svelte/package.json index 290d73c8906..88adec92a21 100644 --- a/app/svelte/package.json +++ b/app/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,17 +28,18 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", + "sveltedoc-parser": "^3.0.4", "ts-dedent": "^1.1.1" }, "devDependencies": { @@ -48,8 +49,6 @@ }, "peerDependencies": { "@babel/core": "*", - "react": "*", - "react-dom": "*", "svelte": "^3.1.0", "svelte-loader": "^2.9.1" }, @@ -59,11 +58,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/svelte/src/client/preview/index.ts b/app/svelte/src/client/preview/index.ts index 177d45653da..abc4d66ddb3 100644 --- a/app/svelte/src/client/preview/index.ts +++ b/app/svelte/src/client/preview/index.ts @@ -17,6 +17,6 @@ export const { const framework = 'svelte'; export const storiesOf = (...args: any) => clientApi.storiesOf(...args).addParameters({ framework }); -export const configure = (...args: any) => coreConfigure(...args, framework); +export const configure = (...args: any) => coreConfigure(framework, ...args); export { forceReRender }; diff --git a/app/vue/README.md b/app/vue/README.md index 57f4901cc32..f3b3af4d1a3 100644 --- a/app/vue/README.md +++ b/app/vue/README.md @@ -23,8 +23,8 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/vue/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/vue/workflows/publish-storybook) of your storybook and deploy it anywhere you want. ## Vue Notes diff --git a/app/vue/package.json b/app/vue/package.json index 58dde077d9c..b22fd61182b 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -28,20 +28,22 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "core-js": "^3.0.1", "global": "^4.3.2", "regenerator-runtime": "^0.13.3", "ts-dedent": "^1.1.1", "ts-loader": "^6.2.2", + "vue-docgen-api": "^4.29.1", + "vue-docgen-loader": "^1.5.0", "webpack": "^4.43.0" }, "devDependencies": { @@ -55,8 +57,6 @@ "@babel/core": "*", "babel-loader": "^7.0.0 || ^8.0.0", "css-loader": "*", - "react": "*", - "react-dom": "*", "ts-loader": "^6.2.2", "vue": "^2.6.8", "vue-loader": "^15.7.0", @@ -68,11 +68,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/vue/src/client/index.ts b/app/vue/src/client/index.ts index 8034a9d6433..620f2fae5cd 100644 --- a/app/vue/src/client/index.ts +++ b/app/vue/src/client/index.ts @@ -9,6 +9,8 @@ export { raw, } from './preview'; +export * from './preview/types-6-0'; + if (module && module.hot && module.hot.decline) { module.hot.decline(); } diff --git a/app/vue/src/client/preview/index.ts b/app/vue/src/client/preview/index.ts index eeb0edf3464..b00eb4385d1 100644 --- a/app/vue/src/client/preview/index.ts +++ b/app/vue/src/client/preview/index.ts @@ -69,7 +69,8 @@ const defaultContext: StoryContext = { kind: 'unspecified', parameters: {}, args: {}, - globalArgs: {}, + argTypes: {}, + globals: {}, }; function decorateStory( @@ -121,7 +122,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/vue/src/client/preview/render.ts b/app/vue/src/client/preview/render.ts index 3500a087494..29c54bf00a2 100644 --- a/app/vue/src/client/preview/render.ts +++ b/app/vue/src/client/preview/render.ts @@ -22,6 +22,7 @@ export default function render({ storyFn, kind, name, + args, showMain, showError, showException, @@ -29,6 +30,9 @@ export default function render({ }: RenderContext) { Vue.config.errorHandler = showException; + // FIXME: move this into root[COMPONENT] = element + // once we get rid of knobs so we don't have to re-create + // a new component each time const element = storyFn(); if (!element) { @@ -44,11 +48,13 @@ export default function render({ showMain(); - // always refresh the component - root[COMPONENT] = element; + // at component creation || refresh by HMR or switching stories + if (!root[COMPONENT] || !forceRender) { + root[COMPONENT] = element; + } - // @ts-ignore https://github.com/storybookjs/storybook/pull/7578#discussion_r307986139 - root[VALUES] = element.options[VALUES]; + // @ts-ignore https://github.com/storybookjs/storrybook/pull/7578#discussion_r307986139 + root[VALUES] = { ...element.options[VALUES], ...args }; if (!root.$el) { root.$mount('#root'); diff --git a/app/vue/src/client/preview/types-6-0.ts b/app/vue/src/client/preview/types-6-0.ts new file mode 100644 index 00000000000..3807ea00e78 --- /dev/null +++ b/app/vue/src/client/preview/types-6-0.ts @@ -0,0 +1,23 @@ +import { Component, AsyncComponent } from 'vue'; +import { Args as DefaultArgs, Annotations, BaseMeta, BaseStory } from '@storybook/addons'; +import { StoryFnVueReturnType } from './types'; + +export { Args, ArgTypes, Parameters, StoryContext } from '@storybook/addons'; + +type VueComponent = Component | AsyncComponent; +type VueReturnType = StoryFnVueReturnType; + +/** + * Metadata to configure the stories for a component. + * + * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) + */ +export type Meta = BaseMeta & Annotations; + +/** + * Story function that represents a component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type Story = BaseStory & + Annotations; diff --git a/app/vue/types-6-0.d.ts b/app/vue/types-6-0.d.ts new file mode 100644 index 00000000000..6ed7da8e519 --- /dev/null +++ b/app/vue/types-6-0.d.ts @@ -0,0 +1 @@ +export * from './dist/client/preview/types-6-0.d'; diff --git a/app/web-components/README.md b/app/web-components/README.md index b8f57d205f5..842a6d988af 100644 --- a/app/web-components/README.md +++ b/app/web-components/README.md @@ -21,8 +21,8 @@ For more information visit: [storybook.js.org](https://storybook.js.org) --- -Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/docs/web-components/configure/storybook-addons) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/docs/web-components/workflows/publish-storybook) of your storybook and deploy it anywhere you want. # Setup page reload via HMR diff --git a/app/web-components/package.json b/app/web-components/package.json index f54271e05d6..6ab69fe0905 100644 --- a/app/web-components/package.json +++ b/app/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit-html", @@ -30,16 +30,18 @@ "README.md", "*.js", "*.d.ts", - "ts3.5/**/*" + "ts3.4/**/*" ], "scripts": { "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-import-meta": "^7.2.0", - "@storybook/addons": "6.0.0-beta.23", - "@storybook/core": "6.0.0-beta.23", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/preset-env": "^7.11.5", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/core": "6.1.0-alpha.14", "@types/webpack-env": "^1.15.2", "babel-plugin-bundled-import-meta": "^0.3.1", "core-js": "^3.0.1", @@ -53,9 +55,7 @@ "peerDependencies": { "@babel/core": "*", "babel-loader": "^7.0.0 || ^8.0.0", - "lit-html": "^1.0.0", - "react": "*", - "react-dom": "*" + "lit-html": "^1.0.0" }, "engines": { "node": ">=8.0.0" @@ -63,11 +63,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/app/web-components/src/client/preview/index.ts b/app/web-components/src/client/preview/index.ts index 2ec3d47cf69..b62b7446ab1 100644 --- a/app/web-components/src/client/preview/index.ts +++ b/app/web-components/src/client/preview/index.ts @@ -25,7 +25,7 @@ export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { }); }; -export const configure: ClientApi['configure'] = (...args) => api.configure(...args, framework); +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const addDecorator: ClientApi['addDecorator'] = api.clientApi.addDecorator; export const addParameters: ClientApi['addParameters'] = api.clientApi.addParameters; export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; diff --git a/app/web-components/src/client/preview/render.ts b/app/web-components/src/client/preview/render.ts index 77c5b87e36b..5970b4d3e70 100644 --- a/app/web-components/src/client/preview/render.ts +++ b/app/web-components/src/client/preview/render.ts @@ -1,6 +1,7 @@ import { document, Node } from 'global'; import dedent from 'ts-dedent'; import { render, TemplateResult } from 'lit-html'; +import { simulatePageLoad, simulateDOMContentLoaded } from '@storybook/client-api'; import { RenderContext } from './types'; const rootElement = document.getElementById('root'); @@ -26,8 +27,10 @@ export default function renderMain({ const renderTo = rootElement.querySelector('[id="root-inner"]'); render(element, renderTo); + simulatePageLoad(rootElement); } else if (typeof element === 'string') { rootElement.innerHTML = element; + simulatePageLoad(rootElement); } else if (element instanceof Node) { // Don't re-mount the element if it didn't change and neither did the story if (rootElement.firstChild === element && forceRender === true) { @@ -36,6 +39,7 @@ export default function renderMain({ rootElement.innerHTML = ''; rootElement.appendChild(element); + simulateDOMContentLoaded(); } else { showError({ title: `Expecting an HTML snippet or DOM node from the story: "${name}" of "${kind}".`, diff --git a/app/web-components/src/server/framework-preset-web-components.ts b/app/web-components/src/server/framework-preset-web-components.ts index 996e444a4bc..a63675e4650 100644 --- a/app/web-components/src/server/framework-preset-web-components.ts +++ b/app/web-components/src/server/framework-preset-web-components.ts @@ -19,17 +19,17 @@ export function webpack(config: Configuration) { new RegExp(`node_modules(\\/|\\\\)@vaadin(.*)\\.js$`), ], use: { - loader: 'babel-loader', + loader: require.resolve('babel-loader'), options: { plugins: [ - '@babel/plugin-syntax-dynamic-import', - '@babel/plugin-syntax-import-meta', + require.resolve('@babel/plugin-syntax-dynamic-import'), + require.resolve('@babel/plugin-syntax-import-meta'), // webpack does not support import.meta.url yet, so we rewrite them in babel - ['bundled-import-meta', { importStyle: 'baseURI' }], + [require.resolve('babel-plugin-bundled-import-meta'), { importStyle: 'baseURI' }], ], presets: [ [ - '@babel/preset-env', + require.resolve('@babel/preset-env'), { useBuiltIns: 'entry', corejs: 3, diff --git a/cypress/generated/addon-action.spec.ts b/cypress/generated/addon-action.spec.ts index 8f7a2dc858b..16cd34e68d6 100644 --- a/cypress/generated/addon-action.spec.ts +++ b/cypress/generated/addon-action.spec.ts @@ -1,31 +1,19 @@ -import { visit, clickAddon } from '../helper'; - describe('addon-action', () => { before(() => { - visit(); - cy.get('#button').click(); + cy.visitStorybook(); }); it('should trigger an action', () => { // click on the button - cy.get('#button--text-with-action').click(); + cy.navigateToStory('example-button', 'primary'); - // assert url changes - cy.url().should('include', 'path=/story/button--text-with-action'); - - // check for selected element - cy.get('#button--text-with-action').should('have.class', 'selected'); - - // check for content - cy.getStoryElement().contains('Trigger Action').click(); - - // click on addon - clickAddon('Actions'); + cy.getStoryElement().contains('Button').click(); + cy.viewAddonPanel('Actions'); // TODO @yannbf improve tab identifier on addons // get the logs cy.get('#storybook-panel-root') - .contains(/This was clicked/) + .contains(/onClick/) .should('be.visible'); }); }); diff --git a/cypress/generated/addon-link.spec.ts b/cypress/generated/addon-link.spec.ts deleted file mode 100644 index d94420c496c..00000000000 --- a/cypress/generated/addon-link.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { visit, clickAddon } from '../helper'; - -describe('addon-link', () => { - before(() => { - visit(); - cy.get('#button').click(); - }); - - it('should redirect to another story', () => { - // click on the button - cy.get('#button--button-with-link-to-another-story').click(); - - // assert url changes - cy.url().should('include', 'path=/story/button--button-with-link-to-another-story'); - - // check for selected element - cy.get('#button--button-with-link-to-another-story').should('have.class', 'selected'); - - // check for content - cy.getStoryElement().contains('Go to Welcome Story').click(); - - // assert url changes - cy.url().should('include', 'path=/story/welcome--to-storybook'); - }); -}); diff --git a/cypress/generated/addon-viewport.spec.ts b/cypress/generated/addon-viewport.spec.ts new file mode 100644 index 00000000000..9f99e37ded0 --- /dev/null +++ b/cypress/generated/addon-viewport.spec.ts @@ -0,0 +1,16 @@ +describe('addon-viewport', () => { + before(() => { + cy.visitStorybook(); + }); + + it('should have viewport button in the toolbar', () => { + cy.navigateToStory('example-button', 'Primary'); + + // Click on viewport button and select small mobile + cy.get('[title="Change the size of the preview"]').click(); + cy.get('#mobile1').click(); + + // Check that Welcome story is still displayed + cy.getStoryElement().should('contain.text', 'Button'); + }); +}); diff --git a/cypress/generated/basic.spec.ts b/cypress/generated/basic.spec.ts index f141a86ccad..efb67e23f8a 100644 --- a/cypress/generated/basic.spec.ts +++ b/cypress/generated/basic.spec.ts @@ -1,35 +1,76 @@ -import { visit } from '../helper'; - -describe('Basic Flow', () => { +describe('Basic CLI', () => { before(() => { - visit(); + cy.visitStorybook(); }); - it('should load welcome flow', () => { - // assert url changes - cy.url().should('include', 'path=/story/welcome--to-storybook'); - - // check for selected element - cy.get('#welcome--to-storybook').should('have.class', 'selected'); - - // check for content - cy.getStoryElement().should('contain.text', 'Welcome to storybook'); + describe('Welcome story (MDX)', () => { + it('should load and display', () => { + cy.navigateToStory('example-introduction', 'page'); + cy.getDocsElement().should('contain.text', 'Welcome to Storybook'); + }); }); describe('Button story', () => { - before(() => { - cy.get('#button').click(); + it('should load primary story', () => { + cy.navigateToStory('example-button', 'primary'); + cy.getStoryElement() + .find('button') + .should('have.class', 'storybook-button') + .and('have.class', 'storybook-button--primary'); }); - it('should be visited succesfully', () => { - // assert url changes - cy.url().should('include', 'path=/story/button--text'); + it('should load secondary story', () => { + cy.navigateToStory('example-button', 'secondary'); + cy.getStoryElement() + .find('button') + .should('have.class', 'storybook-button') + .and('have.class', 'storybook-button--secondary'); + }); + it('should load small story', () => { + cy.navigateToStory('example-button', 'small'); + cy.getStoryElement() + .find('button') + .should('have.class', 'storybook-button') + .and('have.class', 'storybook-button--small'); + }); + it('should load large story', () => { + cy.navigateToStory('example-button', 'large'); + cy.getStoryElement() + .find('button') + .should('have.class', 'storybook-button') + .and('have.class', 'storybook-button--large'); + }); + }); - // check for selected element - cy.get('#button--text').should('have.class', 'selected'); + describe('Header story', () => { + it('should load and display logged in', () => { + cy.navigateToStory('example-header', 'logged-in'); + cy.getStoryElement().find('header').should('contain.text', 'Acme'); + cy.getStoryElement().find('button').should('contain.text', 'Log out'); + }); - // check for content - cy.getStoryElement().find('button').should('contain.text', 'Hello Button'); + it('should load and display logged out', () => { + cy.navigateToStory('example-header', 'logged-out'); + cy.getStoryElement().find('header').should('contain.text', 'Acme'); + cy.getStoryElement().find('button').first().should('contain.text', 'Log in'); + cy.getStoryElement().find('button').last().should('contain.text', 'Sign up'); + }); + }); + + describe('Page story', () => { + it('should load and display logged in', () => { + cy.navigateToStory('example-page', 'logged-in'); + cy.getStoryElement().find('header').should('contain.text', 'Acme'); + cy.getStoryElement().find('button').should('contain.text', 'Log out'); + cy.getStoryElement().should('contain.text', 'Pages in Storybook'); + }); + + it('should load and display logged out', () => { + cy.navigateToStory('example-page', 'logged-out'); + cy.getStoryElement().should('contain.text', 'Acme'); + cy.getStoryElement().find('button').first().should('contain.text', 'Log in'); + cy.getStoryElement().find('button').last().should('contain.text', 'Sign up'); + cy.getStoryElement().should('contain.text', 'Pages in Storybook'); }); }); }); diff --git a/cypress/integration/knobs.spec.ts b/cypress/integration/knobs.spec.ts index c5f65dca534..d0f73265633 100644 --- a/cypress/integration/knobs.spec.ts +++ b/cypress/integration/knobs.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable cypress/no-unnecessary-waiting */ import { clickAddon, visit } from '../helper'; describe('Knobs', () => { @@ -12,6 +13,7 @@ describe('Knobs', () => { cy.getStoryElement() .console('info') + .wait(3000) .find('p') .eq(0) .should('contain.text', 'My name is John Doe'); diff --git a/cypress/plugins/webpack.config.js b/cypress/plugins/webpack.config.js index 01774bb3d14..e26dabb4657 100644 --- a/cypress/plugins/webpack.config.js +++ b/cypress/plugins/webpack.config.js @@ -9,7 +9,7 @@ module.exports = { exclude: [/node_modules/], use: [ { - loader: 'ts-loader', + loader: require.resolve('ts-loader'), options: { transpileOnly: true, }, diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 11ee583da25..045b5f6aa1f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -36,6 +36,20 @@ Cypress.Commands.add( } ); +Cypress.Commands.add('visitStorybook', () => { + cy.log('visitStorybook'); + const host = Cypress.env('location') || 'http://localhost:8001'; + return cy + .clearLocalStorage() + .visit(`${host}/?path=/story/example-introduction--page`) + .get(`#storybook-preview-iframe`, { log: false }) + .its('0.contentDocument.body', { log: false }) + .should('not.be.empty') + .then((body) => cy.wrap(body, { log: false })) + .find('#docs-root', { log: false }) + .should('not.be.empty'); +}); + Cypress.Commands.add('getStoryElement', {}, () => { cy.log('getStoryElement'); return cy @@ -47,3 +61,41 @@ Cypress.Commands.add('getStoryElement', {}, () => { .should('not.be.empty') .then((storyRoot) => cy.wrap(storyRoot, { log: false })); }); + +Cypress.Commands.add('getDocsElement', {}, () => { + cy.log('getDocsElement'); + return cy + .get(`#storybook-preview-iframe`, { log: false }) + .its('0.contentDocument.body', { log: false }) + .should('not.be.empty') + .then((body) => cy.wrap(body, { log: false })) + .find('#docs-root', { log: false }) + .should('not.be.empty') + .then((storyRoot) => cy.wrap(storyRoot, { log: false })); +}); + +Cypress.Commands.add('navigateToStory', (kind, name) => { + const kindId = kind.replace(/ /g, '-').toLowerCase(); + const storyId = name.replace(/ /g, '-').toLowerCase(); + + const storyLinkId = `#${kindId}--${storyId}`; + cy.log(`navigateToStory ${kind} ${name}`); + + if (name !== 'page') { + // Section can be collapsed, click twice ensure expansion + cy.get(`#${kindId}`).click(); + } + cy.get(storyLinkId).click(); + + // assert url changes + cy.url().should('include', `path=/story/${kindId}--${storyId}`); + cy.get(storyLinkId).should('have.class', 'selected'); + + // A pause is good when switching stories + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(50); +}); + +Cypress.Commands.add('viewAddonPanel', (name) => { + cy.get(`[role=tablist] button[role=tab]`).contains(name).click(); +}); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 133f544ad62..2c303f0eb2a 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -4,10 +4,35 @@ type LoggerMethod = 'log' | 'info' | 'debug'; declare namespace Cypress { interface Chainable { + /** + * Visit storybook's introduction page + */ + visitStorybook(): Chainable; + /** * Custom command to select the DOM element of a story in the canvas tab. */ getStoryElement(): Chainable; + /** + * Custom command to select the DOM element of a docs story in the canvas tab. + */ + getDocsElement(): Chainable; + + /** + * Navigate to a story. + * 'Storybook Example/Button' + * - kind: `Storybook Example` + * - name: `Button` + * @param kind Story kind + * @param name name of the story + */ + navigateToStory(kind: string, name: string): Chainable; + + /** + * Display addon panel + * @param name of the addon + */ + viewAddonPanel(name: string): Chainable; /** * Returns the element while logging it. diff --git a/dev-kits/addon-decorator/package.json b/dev-kits/addon-decorator/package.json index 87b80b3d93e..23a90ac5de2 100644 --- a/dev-kits/addon-decorator/package.json +++ b/dev-kits/addon-decorator/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-decorator", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "decorator addon for storybook", "keywords": [ "addon", @@ -24,19 +24,19 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.4.0" }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/dev-kits/addon-parameter/package.json b/dev-kits/addon-parameter/package.json index aff0f2ff045..7f5d9157110 100644 --- a/dev-kits/addon-parameter/package.json +++ b/dev-kits/addon-parameter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-parameter", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "parameter addon for storybook", "keywords": [ "addon", @@ -24,12 +24,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -38,11 +38,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/dev-kits/addon-preview-wrapper/package.json b/dev-kits/addon-preview-wrapper/package.json index 6f8390e1312..e205f9a2c36 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": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "preview wrapper addon for storybook", "keywords": [ "addon", @@ -24,17 +24,17 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", "react": "^16.8.3" }, "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/dev-kits/addon-roundtrip/package.json b/dev-kits/addon-roundtrip/package.json index ca5ef0962ef..82581b1e080 100644 --- a/dev-kits/addon-roundtrip/package.json +++ b/dev-kits/addon-roundtrip/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-roundtrip", - "version": "6.0.0-beta.23", + "version": "6.1.0-alpha.14", "description": "roundtrip addon for storybook", "keywords": [ "addon", @@ -24,13 +24,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.0.0-beta.23", - "@storybook/api": "6.0.0-beta.23", - "@storybook/client-api": "6.0.0-beta.23", - "@storybook/client-logger": "6.0.0-beta.23", - "@storybook/components": "6.0.0-beta.23", - "@storybook/core-events": "6.0.0-beta.23", - "@storybook/theming": "6.0.0-beta.23", + "@storybook/addons": "6.1.0-alpha.14", + "@storybook/api": "6.1.0-alpha.14", + "@storybook/client-api": "6.1.0-alpha.14", + "@storybook/client-logger": "6.1.0-alpha.14", + "@storybook/components": "6.1.0-alpha.14", + "@storybook/core-events": "6.1.0-alpha.14", + "@storybook/theming": "6.1.0-alpha.14", "core-js": "^3.0.1", "global": "^4.3.2", "react": "^16.8.3", @@ -39,11 +39,11 @@ "publishConfig": { "access": "public" }, - "gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff", + "gitHead": "4571582a90b646e361cb37df525f62312486307a", "typesVersions": { - "<=3.5": { + "<3.8": { "*": [ - "ts3.5/*" + "ts3.4/*" ] } } diff --git a/docs/.eslintrc.js b/docs/.eslintrc.js deleted file mode 100644 index 6ab0b6d43ec..00000000000 --- a/docs/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -const warn = 1; - -module.exports = { - globals: { - graphql: false, - }, - rules: { - 'import/no-unresolved': [warn, { commonjs: true, caseSensitive: true }], - 'import/extensions': [ - // because of highlight.js - warn, - 'always', - { - js: 'never', - }, - ], - }, -}; diff --git a/docs/.gatsby-context.js b/docs/.gatsby-context.js deleted file mode 100644 index df29ee95276..00000000000 --- a/docs/.gatsby-context.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -/* weak */ -// This file is auto-written and used by Gatsby to require -// files from your pages directory. -module.exports = function (callback) { - var context = require.context('./src/pages', true, /(coffee|cjsx|ts|tsx|jsx|js|md|rmd|mkdn?|mdwn|mdown|markdown|litcoffee|ipynb|html|json|yaml|toml)$/ // eslint-disable-line - );if (module.hot) { - module.hot.accept(context.id, function () { - context = require.context('./src/pages', true, /(coffee|cjsx|ts|tsx|jsx|js|md|rmd|mkdn?|mdwn|mdown|markdown|litcoffee|ipynb|html|json|yaml|toml)$/ // eslint-disable-line - );return callback(context); - }); - } - return callback(context); -}; \ No newline at end of file diff --git a/docs/.snyk b/docs/.snyk deleted file mode 100644 index 09f63ccce2c..00000000000 --- a/docs/.snyk +++ /dev/null @@ -1,72 +0,0 @@ -# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.12.0 -ignore: {} -# patches apply the minimum changes required to fix a vulnerability -patch: - 'npm:hoek:20180212': - - gatsby-source-filesystem > babel-cli > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > chokidar > fsevents > node-pre-gyp > hawk > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > babel-cli > chokidar > fsevents > node-pre-gyp > hawk > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > gatsby-plugin-page-creator > chokidar > fsevents > node-pre-gyp > hawk > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > hoek': - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > gatsby-plugin-page-creator > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > babel-cli > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > gatsby-plugin-page-creator > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > chokidar > fsevents > node-pre-gyp > hawk > hoek: - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > @storybook/core > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > hoek': - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek': - patched: '2018-10-12T13:09:42.105Z' - - gatsby > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek': - patched: '2018-10-12T13:09:42.105Z' - - gatsby-source-filesystem > babel-cli > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek: - patched: '2018-10-12T13:09:42.105Z' - - gatsby > gatsby-plugin-page-creator > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek': - patched: '2018-10-12T13:09:42.105Z' - - gatsby > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek: - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > @storybook/core > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > boom > hoek': - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > @storybook/core > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > sntp > hoek': - patched: '2018-10-12T13:09:42.105Z' - - '@storybook/react > @storybook/core > webpack > watchpack > chokidar > fsevents > node-pre-gyp > hawk > cryptiles > boom > hoek': - patched: '2018-10-12T13:09:42.105Z' - - gatsby > webpack-validator > joi > hoek: - patched: '2018-10-12T13:09:42.105Z' - 'npm:moment:20170905': - - gatsby > webpack-validator > joi > moment: - patched: '2018-10-12T13:09:42.105Z' - 'npm:tunnel-agent:20170305': - - gatsby-plugin-sharp > imagemin-mozjpeg > mozjpeg > bin-build > download > caw > tunnel-agent: - patched: '2018-10-16T09:11:57.220Z' - - gatsby-plugin-sharp > imagemin-mozjpeg > mozjpeg > bin-wrapper > download > caw > tunnel-agent: - patched: '2018-10-16T09:11:57.220Z' diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 6f6e3d6bfbc..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Website for [storybook.js.org](https://storybook.js.org) - -This is the source for [storybook.js.org](https://storybook.js.org). It documents [Storybook](https://github.com/storybookjs/storybook), an amazing UI component development environment for React and React Native. The site is built with [Gatsby](https://github.com/gatsbyjs/gatsby). - -### Usage - -```sh -yarn -yarn dev -``` - -### Edit Documentation - -Documentation is written in Markdown and located inside the [`docs/src/pages`](https://github.com/storybookjs/storybook/tree/master/docs/src/pages) directory. - -### Guidelines for Writing Good Documentation - -0. Explain **why** in addition to **how**. If something is designed a certain way for a reason, provide that reason. -1. Provide examples of code snippets whenever possible - showing is always better than telling. -2. Avoid simplifying problems - this frustrates users even more when they don't understand something "simple". -* Bad examples: - - `All you need to do is apply...` - - `Simply add...` - - `The object is just...` -3. Use concise language - The less time users spend on reading and understanding docs, the better. -* Avoid using passive voice. - - Passive (bad): `It is believed by Storybook that empowering component builders is important.` - - Active (good): `Storybook believes in empowering component builders.` -* Place action in the verb. - - Indirect action (bad): `A refactor of this code is necessary`. - - Direct action (good): `This code needs to be refactored`. -4. Avoid the use of pronouns - documentation should not address the reader because not everything applies to the person reading our docs. -* Don't use `you` to refer to the user or a third party. - - Pronoun (bad): `You can also...` - - Without pronoun (good): `Users can also...` -* Don't use `we` to refer to Storybook, contributors, or Storybook users. - - Pronoun (bad): `We can create this component...` - - Without pronoun (good): `The component can be created...` -* Don't use `he`, `she`, `him`, `her`, etc. to refer to a third party unless referring to a specific person. -* Refer to contributors and the product as `Storybook`. -* Refer to users as `users`. diff --git a/docs/src/pages/addons/api/index.md b/docs/api/addons-api.md similarity index 56% rename from docs/src/pages/addons/api/index.md rename to docs/api/addons-api.md index f41611d09d7..26b927881e6 100644 --- a/docs/src/pages/addons/api/index.md +++ b/docs/api/addons-api.md @@ -1,17 +1,20 @@ --- -id: 'api' -title: 'API' +title: 'Addons API' --- ## Core Addon API This is the core addon API. This is how to get the addon API: -```jsx -import { addons } from '@storybook/addons'; -``` + -Have a look at the API methods for more details: + + + ### addons.getChannel() @@ -24,37 +27,32 @@ It has a NodeJS [EventEmitter](https://nodejs.org/api/events.html) compatible AP This method allows you to register an addon and get the storybook API. You can do this only in the Manager App. See how we can use this: -```jsx -import { addons } from '@storybook/addons'; + -// Register the addon with a unique name. -addons.register('my-organisation/my-addon', api => {}); -``` + -Now you'll get an instance to our StorybookAPI. See the [api docs](/addons/api#storybook-api) for Storybook API regarding using that. + + +Now you'll get an instance to our StorybookAPI. See the [api docs](#storybook-api) for Storybook API regarding using that. ### addons.add() This method allows you to add a panel to Storybook. (Storybook's Action Logger is a panel). You can do this only in the Manager App. See how you can use this method: -```jsx -import { addons, types } from '@storybook/addons'; -import { AddonPanel } from '@storybook/components'; + -const MyPanel = () =>
This is a panel.
; + -// give a unique name for the panel -addons.add('my-organisation/my-addon/panel', { - title: 'My Addon', - type: types.PANEL, - render: ({ active, key }) => ( - - - - ), -}); -``` + The render function is called with `active` and `key`. @@ -66,21 +64,15 @@ As you can see, you can set any React Component as the panel. Currently, it's on The `makeDecorator` API can be used to create decorators in the style of the official addons. Use it like so: -```jsx -import { makeDecorator } from '@storybook/addons'; + -export makeDecorator({ - name: 'withSomething', - parameterName: 'something', - wrapper: (storyFn, context, { parameters }) => { - // Do something with `parameters`, which are set via { something: ... } + - // Note you may alter the story output if you like, although generally that's - // not advised - return storyFn(context); - } -}) -``` + The options to `makeDecorator` are: @@ -90,8 +82,12 @@ The options to `makeDecorator` are: - `allowDeprecatedUsage`: support the deprecated "wrapper" usage (`.add('story', () => withFoo(options)(() => ))`). - `wrapper`: your decorator function. Takes the `storyFn`, `context`, and both the `options` and `parameters` (as defined in `skipIfNoParametersOrOptions` above). +
+ Note if the parameters to a story include `{ foo: { disable: true } }` (where `foo` is the `parameterName` of your addon), your decorator will not be called. +
+ --- ## Storybook hooks @@ -100,48 +96,48 @@ Writing addons can be simplified a lot by using these Storybook hooks: ### useStorybookState -```js -export const Panel = () => { - const state = useStorybookState(); + - return
do something with storybook's state
; -} -``` + + + Allows full access to the entire storybook state. Your component will re-render whenever the storybook state changes. -If you use this, remember your component wil be re-rendered a lot, and you may need to optimize for that using `React.memo` or `useMemo` or `PureComponent`. +If you use this, remember your component wil be re-rendered a lot, and you may need to optimize for that using [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo) or [`useMemo`](https://reactjs.org/docs/hooks-reference.html#usememo) or [`PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent). ### useStorybookApi -```js -export const Panel = () => { - const state = useStorybookApi(); + - return
do something with storybook's api
; -} -``` + + + Allows full access to the storybook API. Detail on the storybook api are further down. ### useChannel -```js -import { STORY_CHANGED } from '@storybook/core-events'; -export const Panel = () => { - const emit = useChannel({ - STORY_CHANGED: (...args) => console.log(...args), - }); - return ( - - ); -} -``` + + + + + Allows for both setting subscriptions to events and getting the emitter for emitting custom event unto the channel. @@ -149,26 +145,15 @@ The messages can be listened for on both the iframe and the manager side. ### useAddonState -```js -export const Panel = () => { - const [state, setState] = useAddonState('my/addon-id', 'initial state'); + - return ( - - ); -} -export const Tool = () => { - const [state, setState] = useAddonState('my/addon-id', 'initial state'); + - return ( - - ); -} -``` + Extremely useful for addons that need to persist some state. @@ -180,18 +165,15 @@ With this hook they can all get access to the same bit of state which is persist ### useParameter -```js -export const Panel = () => { - const value = useParameter('parameter-key', 'default value'); + - return ( -
- for the currently selected story, the parameter for "parameter-key" is: - {value} -
- ); -} -``` + + + This hook gets you the current story's parameter. @@ -214,84 +196,110 @@ 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 -export default { - title: 'heading', -}; + -export const withText = () =>

Hello world

; -``` + + + This is how you can select the above story: -```jsx -addons.register('my-organisation/my-addon', api => { - api.selectStory('heading', 'withText'); -}); -``` + + + + + ### api.selectInCurrentKind() Same as `selectStory`, but accepts a story inside current kind as the only parameter: -```jsx -addons.register('my-organisation/my-addon', api => { - api.selectInCurrentKind('withText'); -}); -``` + + + + + ### api.setQueryParams() This method allows you to set query string parameters. You can use that as temporary storage for addons. Here's how you set query params. -```jsx -addons.register('my-organisation/my-addon', api => { - api.setQueryParams({ - abc: 'this is abc', - bbc: 'this is bbc', - }); -}); -``` + -> If you need to remove a query param, use `null` for that. For an example, let's say we need to remove bbc query param. This is how we do it: + -```jsx -addons.register('my-organisation/my-addon', api => { - api.setQueryParams({ - bbc: null, - }); -}); -``` + + +
+ +If you need to remove a query param, use `null` for that. For an example, let's say we need to remove bbc query param. This is how we do it: + +
+ + + + + + ### api.getQueryParam() This method allows you to get a query param set by above API `setQueryParams`. For example, let's say we need to get the bbc query param. Then this how we do it: -```jsx -addons.register('my-organisation/my-addon', api => { - api.getQueryParam('bbc'); -}); -``` + + + + + ### api.getUrlState(overrideParams) This method allows you to get application url state with some changed params. For example, if you want to get a link to a particular story: -```jsx -addons.register('my-organisation/my-addon', api => { - const href = api.getUrlState({ - selectedKind: 'kind', - selectedStory: 'story', - }).url; -}); -``` + + + + + ### 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.on('some-event', (eventData) => console.log(eventData)); -}); -``` + + + + + diff --git a/docs/api/addons.md b/docs/api/addons.md new file mode 100644 index 00000000000..db799afbd13 --- /dev/null +++ b/docs/api/addons.md @@ -0,0 +1,237 @@ +--- +title: 'Addons' +--- + +Addons extend Storybook with features and integrations that are not built into the core. Most Storybook features are implemented as addons. For instance: documentation, accessibility testing, interactive controls, and design previews. +The addon API makes it easy for you to configure and customize Storybook in new ways. There are countless addons made by the community that unlock time-saving workflows. What addons can do: + +- [Add a panel to Storybook (like Action Logger).](../essentials/actions.md) +- [Add a tool to Storybook’s toolbar (like zoom or grid).](../essentials/toolbars-and-globals.md) +- [Add a tab to Storybook (like SB Docs).](../writing-docs/introduction.md) + +Browse the [addon gallery](/addons) to install an existing addon or as inspiration for your own addon. Read on to learn how to make an addon yourself. + +## Storybook basics + +Before writing your first addon, let’s take a look at the basics of Storybook’s architecture. While Storybook presents a unified user interface, under the hood it’s divided down the middle into **Manager** and **Preview**. + +The Manager is the UI where Storybook’s search, navigation, toolbars, and addons are rendered. The Preview area is an iframe where stories are rendered. + +![Storybook detailed window](./manager-preview.jpg) + +Because Manager and Preview run in separate iframes, they communicate across a communication channel. When you select a story within the Manager an event is sent across the channel and the selected story is rendered inside the Preview. + +Many of the addon APIs you’ll read about below are abstractions to help make this communication transparent. + +## Getting started + +Let’s write a simple addon for Storybook which: + +- Adds a new “My Addon” panel +- Retrieves a custom “myAddon” parameter from stories +- Displays the parameter data in the panel + +### Add story parameters + +Let’s start by writing a story for our addon that exposes a custom parameter. The idea is that our addon will show this parameter in the addon panel. + + + + + + + +Because we added the story at the component level, the `myAddon` parameter is associated with all stories defined in the file. + +### Add a panel + +Now let’s add a panel to Storybook in a file called `register.js`, which is the entry point for addons to register themselves. + + + + + + + +This is boilerplate code for any addon that adds a panel to Storybook, and there’s really not much going on here. In this case, we’re just adding a static div that renders when the panel is selected in Storybook’s UI. + +### Display story parameter + +Next, let’s replace the `MyPanel` component from above to show the parameter. + + + + + + + +The new version is made smarter by `useParameter`, which is a [React hook](https://reactjs.org/docs/hooks-intro.html) that updates the parameter value and re-renders the panel every time the story changes. + +The addon API provides hooks like this so all of that communication can happen behind the scenes. That means you can focus on your addon's functionality. + +### Register the addon + +Finally, let’s hook it all up. Addons are typically published as standalone packages, but they can also be written locally in an existing Storybook project. We’ll make our addon a local addon. + +Update your [`.storybook/main.js`](../configure/overview.md#configure-story-rendering): + + + + + + + +The path can be an absolute location on your file system, or a path relative to your `.storybook` directory (e.g. `./my-addon/register.js` if you defined the addon inside your `.storybook` folder). + +If you get an error similar to: + +```sh +ModuleParseError: Module parse failed: Unexpected token (92:22) + +You may need an appropriate loader to handle this file type. + var value = this.state.value; + var active = this.props.active; + return active ?
{value}
: null; + } + }]); +``` + +It is likely because you do not have a `.babelrc` file or do not have it configured with the correct presets: + +```json +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} +``` + +Now restart/rebuild Storybook and your addon should appear in the addons panel. Furthermore, as you navigate between stories, the parameter displayed should update accordingly. + +### Next steps + +In the previous example, we introduced the structure of an addon, but barely scratched the surface of what addons can do. + +To dive deeper we recommend [Learn Storybook’s “creating addons”](https://www.learnstorybook.com/intro-to-storybook/react/en/creating-addons/) tutorial. It’s an excellent walkthrough that covers the same ground as the above introduction, but goes further and leads you through the full process of creating a realistic addon. + +## Addon recipes + +Once you understand the basics of writing an addon, there are a variety of common enhancements to make your addon better. + +### Disabling the addon panel + +It’s possible to disable the addon panel for a particular story. + +To make that possible, you need to pass the `paramKey` element when you register the panel: + + + + + + + +Then when adding a story, you can pass a disabled parameter. + + + + + + + +### Styling your addon + +Storybook uses [Emotion](https://emotion.sh/docs/introduction) for styling, AND we provide a theme which can be set by the user! + +We recommend you also to use Emotion to style your addon’s UI components. That allows you to use the active Storybook theme to deliver a seamless developer experience. +If you don’t want to use Emotion, you can use inline styles or another css-in-js lib. You can receive the theme as a prop by using the `withTheme` hoc from Emotion. [Read more about theming](../configure/theming.md). + +### Storybook components + +Addon authors can develop their UIs using any React library. But we recommend using Storybook’s own UI components in `@storybook/components` to build addons faster. When you use Storybook components you get: + +- Battle-tested off-the-shelf components +- Storybook native look and feel +- Built-in support for Storybook theming + +You can check them out in [Storybook’s own storybook](https://storybookjs.netlify.app/) + +### Packaging + +In the example above, we showed how to write a local addon inside an existing Storybook project. To distribute your addon for others, package the addon into a standalone NPM module. + +For a good template of an addon packaged as an NPM module, check out [@storybook/addon-controls](https://npmjs.com/package/@storybook/addon-controls). + +It contains addon code similar to what we’ve written above. It also contains: + +- A package.json file that declares the module +- Peer dependencies of `react` and `@storybook/addons` + -A `register.js` file at the root level written as an ES5 module +- A `src` directory containing the ES6 addon code +- A `dist` directory containing transpiled ES5 code on publish + +Your packaged Storybook addon needs to be written in ES5. If you are using ES6, then you need to transpile it. + +When you are developing your addon as a package, you can’t use `npm link` to add it to your project. Instead add your package as a local dependency into your package.json: + +```json +{ + "dependencies": { + "@storybook/addon-controls": "file:///home/username/myrepo" + } +} +``` + +## Addon presets + +Storybook presets are collections of Storybook configurations that get applied automatically when you create a `/preset.js` entry point in your addon and then list that addon in your project’s [`.storybook/main.js`](../configure/overview.md#configure-story-rendering) addons field. + +Common uses for presets include: + +- Register an addon’s `register.js`. +- Set global parameters for the addon (e.g. [addon-backgrounds](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds)). +- Add global decorators for the addon (e.g. [addon-a11y](https://github.com/storybookjs/storybook/tree/next/addons/a11y)). +- Set up webpack customizations for the addon (e.g. [addon-docs](../writing-docs/introduction.md)). + +Here’s an example of typical preset file: + + + + + + + +For more information on presets, see the [presets docs](./presets.md). + +## Writing presets + +If you want to learn more about how you can write your own presets, read the [documentation](./writing-presets.md) + +## Addons API + +If you want to expand your knowledge on the Addons API, read the [documentation](./addons-api.md) diff --git a/docs/api/argstable.png b/docs/api/argstable.png new file mode 100644 index 00000000000..323f3d0295a Binary files /dev/null and b/docs/api/argstable.png differ diff --git a/docs/api/argtypes.md b/docs/api/argtypes.md new file mode 100644 index 00000000000..2b227b8318f --- /dev/null +++ b/docs/api/argtypes.md @@ -0,0 +1,100 @@ +--- +title: 'ArgTypes' +--- + +
+ +NOTE: This API is experimental and may change outside of the typical semver release cycle + +
+ +ArgTypes are a first-class feature in Storybook for specifying the behaviour of [Args](../writing-stories/args.md). By specifying the type of an arg you constrain the values that it can take and can also provide information about args that are not explicitly set (i.e. not required). + +You can also use argTypes to “annotate” args with information that is used by addons that make use of those args, for instance to instruct the controls addons to render a color choose for a string-valued arg. + +The most concrete realization of argTypes is the [Args Table](../writing-docs/doc-blocks.md#argstable) doc block. Each row in the table corresponds to a single argType, as well as the current value of that arg. + +![Storybook infering automatically the argType](./argstable.png) + +## Automatic argType inference + +If you are using the Storybook [docs](../writing-docs/introduction.md) addon (installed by default as part of [essentials](../essentials/introduction.md)), then Storybook will infer a set of argTypes for each story based on the `component` specified in the [default export](./csf.md#default-export) of the CSF file. + +To do so, Storybook uses various static analysis tools depending on your framework. + +- React + - [react-docgen](https://github.com/reactjs/react-docgen) + - [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) +- Vue + - [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) +- Angular + - [compodoc](https://compodoc.app/) +- WebComponents + - [custom-element.json](https://github.com/webcomponents/custom-elements-json) +- Ember + - [YUI doc](https://github.com/ember-learn/ember-cli-addon-docs-yuidoc#documenting-components) + +The format of the generated argType will look something like: + + + + + + + +In this ArgTypes data structure, name, type, defaultValue, and description are standard fields in all ArgTypes (analogous to PropTypes in React). The table and control fields are addon-specific annotations. So, for example, the table annotation provides extra information to customize how label gets rendered, and the control annotation provides extra information for the control for editing the property. + +
+ +`@storybook/addon-docs` provide shorthand for common tasks: + +- `type: 'number'` is shorthand for type: { name: 'number' } +- `control: 'radio'` is shorthand for control: { type: 'radio' } + +
+ +#### Manual specification + +If you want more control over the args table or any other aspect of using argTypes, you can overwrite the generated argTypes for you component on a per-arg basis. For instance, with the above inferred argTypes and the following default export: + + + + + + + +These values--description, table.type, and controls.type--get merged over the defaults that are extracted by Storybook. The final merged values would be: + + + + + + + +In particular, this would render a row with a modified description, a type display with a dropdown that shows the detail, and no control. + +#### Using argTypes in addons + +If you want to access the argTypes of the current component inside an addon, you can use the `useArgTypes` hook from the `@storybook/api` package: + + + + + + diff --git a/docs/api/cli-options.md b/docs/api/cli-options.md new file mode 100644 index 00000000000..fbd8eb97923 --- /dev/null +++ b/docs/api/cli-options.md @@ -0,0 +1,52 @@ +--- +title: 'CLI options' +--- + +Storybook comes with two CLI utilities: `start-storybook` and `build-storybook`. + +Pass these commands the following options to alter Storybook's behavior. + +## start-storybook + +```plaintext +Usage: start-storybook [options] +``` + +| Options | Description | Example | +| ------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | +| --help | Output usage information | `start-storybook --help` | +| -V, --version | Output the version number | `start-storybook -V` | +| -p, --port [number] | Port to run Storybook | `start-storybook -p 9009` | +| -h, --host [string] | Host to run Storybook | `start-storybook -h http://my-host.com` | +| -s, --static-dir `` | Directory where to load static files from, comma-separated list | `start-storybook -s public` | +| -c, --config-dir [dir-name] | Directory where to load Storybook configurations from | `start-storybook -c .storybook` | +| --https | Serve Storybook over HTTPS. Note: You must provide your own certificate information. | `start-storybook --https` | +| --ssl-ca `` | Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate) | `start-storybook --ssl-ca my-certificate` | +| --ssl-cert `` | Provide an SSL certificate. (Required with --https) | `start-storybook --ssl-cert my-ssl-certificate` | +| --ssl-key `` | Provide an SSL key. (Required with --https) | `start-storybook --ssl-key my-ssl-key` | +| --smoke-test | Exit after successful start | `start-storybook --smoke-test` | +| --ci | CI mode (skip interactive prompts, don't open browser) | `start-storybook --ci` | +| --quiet | Suppress verbose build output | `start-storybook --quiet` | +| --no-dll | Do not use dll reference | `start-storybook --no-dll` | +| --debug-webpack | Display final webpack configurations for debugging purposes | `start-storybook --debug-webpack` | +| --docs | Starts Storybook in documentation mode. Learn more about it in [here](../writing-docs/build-documentation.md#preview-storybooks-documentation) | `start-storybook --docs` | + +## build-storybook + +```plaintext +Usage: build-storybook [options] +``` + +| Options | Description | Example | +| ------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ | +| -h, --help | Output usage information | `build-storybook --help` | +| -V, --version | Output the version number | `build-storybook -V` | +| -s, --static-dir `` | Directory where to load static files from, comma-separated list | `build-storybook -s public` | +| -o, --output-dir [dir-name] | Directory where to store built files | `build-storybook -o /my-deployed-storybook` | +| -c, --config-dir [dir-name] | Directory where to load Storybook configurations from | `build-storybook -c .storybook` | +| -w, --watch | Enables watch mode | `build-storybook -w` | +| --loglevel [level] | Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent] | `build-storybook --loglevel warn` | +| --quiet | Suppress verbose build output | `build-storybook --quiet` | +| --no-dll | Do not use dll reference | `build-storybook --no-dll` | +| --debug-webpack | Display final webpack configurations for debugging purposes | `build-storybook --debug-webpack` | +| --docs | Builds Storybook in documentation mode. Learn more about it in [here](../writing-docs/build-documentation.md#publish-storybooks-documentation)) | `build-storybook --docs` | diff --git a/docs/api/csf.md b/docs/api/csf.md new file mode 100644 index 00000000000..6a99e369ecf --- /dev/null +++ b/docs/api/csf.md @@ -0,0 +1,187 @@ +--- +title: 'Component Story Format (CSF)' +--- + +Component Story Format (CSF) is the recommended way to [write stories](../writing-stories/introduction.md). It's an [open standard](https://github.com/ComponentDriven/csf) based on ES6 modules that is portable beyond Storybook. + +
+ +If you are writing stories in the older `storiesOf()` syntax, you can find documentation in an [advanced README](../../lib/core/docs/storiesOf.md). + +
+ +In CSF, stories and component metadata are defined as ES Modules. Every component story file consists of a required [default export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#Using_the_default_export) and one or more [named exports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export). + +CSF is supported in all frameworks except React Native, where you should use the [storiesOf API](../../lib/core/docs/storiesOf.md) instead. + +## Default export + +The default export defines metadata about your component, including the `component` itself, its `title` (where it will show up in the [navigation UI story hierarchy](../writing-stories/naming-components-and-hierarchy.md#sorting-stories)), [decorators](../writing-stories/decorators.md), and [parameters](../writing-stories/parameters.md). + +The `component` field is optional (but encouraged!), and is used by addons for automatic prop table generation and display of other component metadata. `title` should be unique, i.e. not re-used across files. + + + + + + + +For more examples, see [writing stories](../writing-stories/introduction.md). + +## Named story exports + +With CSF, every named export in the file represents a story function by default. + + + + + + + +The exported identifiers will be converted to "start case" using Lodash's [startCase](https://lodash.com/docs/#startCase) function. For example: + +| Identifier | Transformation | +| ---------------- | :---------------: | +| name | Name | +| someName | Some Name | +| someNAME | Some NAME | +| some_custom_NAME | Some Custom NAME | +| someName1234 | Some Name 1 2 3 4 | + +It's recommended to start export names with a capital letter. + +Story functions can be annotated with a few different fields to define story-level [decorators](../writing-stories/decorators.md) and [parameters](../writing-stories/parameters.md), and also to define the `storyName` of the story. + +The `storyName` is useful if you want to use names with special characters, names that correspond to restricted keywords in Javascript, or names that collide with other variables in the file. If it's not specified, the export name will be used instead. + + + + + + + +## Args story inputs + +Starting in SB 6.0, stories accept named inputs called Args. Args are dynamic data that are provided (and possibly updated by) Storybook and its addons. + +Consider Storybook’s ["Button" example](../writing-stories/introduction.md#defining-stories) of a text button that logs its click events: + + + + + + + +Now consider the same example, re-written with args: + + + + + + + +At first blush this might seem no better than the original example. However, if we add the [Docs addon](https://github.com/storybookjs/storybook/tree/master/addons/docs) and configure the [Actions addon](https://github.com/storybookjs/storybook/tree/master/addons/actions) appropriately, we can write: + + + + + + + +Or even more simply: + + + + + + + +Not only are these versions shorter and easier to write than their no-args counterparts, but they are also more portable since the code doesn't depend on the actions addon specifically. + +For more information on setting up [Docs](../writing-docs/introduction.md) and [Actions](../essentials/actions.md), see their respective documentation. + +## Storybook export vs name handling + +Storybook handles named exports and `story.name` slightly differently. When should you use one vs. the other? + +The named export is always used to determine the story ID / URL. + +If you specify `story.name`, it will be used as the story display name in the UI. + +If you don't specify `story.name`, the named export will be used to generate the display name. Storybook passes the named export through a `storyNameFromExport` function, which is implemented with `lodash.startCase`: + + + + + + + +When you want to change the name of your story, just rename the CSF export. This will change the name of the story and also change the story's ID and URL. + +You should use the `story.name` option in the following cases: + +1. You want the name to show up in the Storybook UI in a way that's not possible with a named export, e.g. reserved keywords like "default", special characters like emoji, spacing/capitalization other than what's provided by `storyNameFromExport`. +2. You want to preserve the Story ID independently from changing how it's displayed. Having stable Story ID's is useful for integration with third party tools. + +## Non-story exports + +In some cases, you may want to export a mixture of story and non-stories. For example, it can be useful to export data that's used in your stories. + +To make this possible, you can use optional `includeStories` and `excludeStories` configuration fields in the default export, which can be set to either an array of strings, or a regular expression. + +Consider the following story file: + + + + + + + +When Storybook loads this file, it will see all the exports, but it will ignore the data exports and only treat `SimpleStory` and `ComplexStory` as stories. + +For this specific example the equivalent result can be achieved in a few ways depending on what's convenient: + +- `includeStories: /^[A-Z]/` +- `includeStories: /.*Story$/` +- `includeStories: ['SimpleStory', 'ComplexStory']` +- `excludeStories: /^[a-z]/` +- `excludeStories: /.*Data$/` +- `excludeStories: ['simpleData', 'complexData']` + +If you follow the best practice of starting story exports with an uppercase letter (i.e. use UpperCamelCase), the first option is the recommended solution. diff --git a/docs/api/frameworks-feature-support.md b/docs/api/frameworks-feature-support.md new file mode 100644 index 00000000000..8b1ae823ed3 --- /dev/null +++ b/docs/api/frameworks-feature-support.md @@ -0,0 +1,19 @@ +--- +title: 'Feature support for frameworks' +--- + +Storybook integrates with many popular frontend frameworks. We do our best to keep feature parity amongst frameworks, but it’s tricky for our modest team to support every framework. + +Below is a comprehensive table of what’s supported in which framework integration. If you’d like a certain feature supported in your framework, we welcome pull requests. + +## Core frameworks + +Core frameworks have dedicated maintainers or contributors who are responsible for maintaining the integration. As such, you can use most Storybook features in these frameworks. + + + +## Community frameworks + +Community frameworks have fewer contributors which means they may not be as up to date as core frameworks. If you use one of these frameworks for your job, please consider contributing to its integration with Storybook. + + diff --git a/docs/api/manager-preview.jpg b/docs/api/manager-preview.jpg new file mode 100644 index 00000000000..4931645cc3d Binary files /dev/null and b/docs/api/manager-preview.jpg differ diff --git a/docs/api/mdx-documentation-only.png b/docs/api/mdx-documentation-only.png new file mode 100644 index 00000000000..0ed39ccb9a8 Binary files /dev/null and b/docs/api/mdx-documentation-only.png differ diff --git a/docs/api/mdx-page.png b/docs/api/mdx-page.png new file mode 100644 index 00000000000..195b39eefda Binary files /dev/null and b/docs/api/mdx-page.png differ diff --git a/docs/api/mdx-simple.png b/docs/api/mdx-simple.png new file mode 100644 index 00000000000..2dfaf66b4ed Binary files /dev/null and b/docs/api/mdx-simple.png differ diff --git a/docs/api/mdx.md b/docs/api/mdx.md new file mode 100644 index 00000000000..927bf750c11 --- /dev/null +++ b/docs/api/mdx.md @@ -0,0 +1,125 @@ +--- +title: 'MDX Format' +--- + +`MDX` is the syntax [Storybook Docs](../writing-docs/introduction.md) uses to capture long-form Markdown documentation and stories in one file. You can also write pure documentation pages in `MDX` and add them to Storybook alongside your stories. [Read the announcement](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) to learn more about how and why it came to be. + +## Basic example + +Let's get started with an example that combines Markdown with a single story: + + + + + + + +And here's how that's rendered in Storybook: + +![Show a simple mdx example](./mdx-simple.png) + +As you can see there's a lot going on here. We're writing Markdown, we're writing JSX, and somehow we're also defining Storybook stories that are drop-in compatible with the entire Storybook ecosystem. + +Let's break it down. + +## MDX-Flavored CSF + +[MDX](https://mdxjs.com/) is a standard file format that combines Markdown with JSX. This means you can use Markdown’s terse syntax (such as `# heading`) for your documentation, and freely embed JSX component blocks at any point in the file. + +MDX-flavored [Component Story Format (CSF)](https://medium.com/storybookjs/component-story-format-66f4c32366df) includes a collection of components called **"Doc Blocks"**, that allow Storybook to translate MDX files into storybook stories. MDX-defined stories are identical to regular Storybook stories, so they can be used with Storybook's entire ecosystem of addons and view layers. + +For example, here's the story from `Checkbox` example above, rewritten in CSF: + + + + + + + +There's a one-to-one mapping from the code in `MDX` to `CSF`, which in turn directly corresponds to Storybook's internal `storiesOf` API. As a user, this means your existing Storybook knowledge should translate between the three. And technically, this means that the transformations that happen under the hood are simple and predictable. + +## Writing stories + +Now let's look at a more realistic example to see a few more things we can do: + + + + + + + +And here's how that gets rendered in Storybook: + +![Display mdx page](./mdx-page.png) + +### Embedding stories + +Suppose you have an existing story and want to embed it into your docs. Here's how to show a story with ID `some--id` (check the browser URL in Storybook v5+ to see a story's ID): + + + + + + + +You can also use the rest of the MDX features in conjunction with embedding. That includes source, preview, and prop tables. + +### Decorators and parameters + +To add [decorators](../writing-docs/mdx.md#decorators-and-parameters) and [parameters](../writing-docs/mdx.md#decorators-and-parameters) in MDX: + + + + + + + +In addition, global decorators work just like before, e.g. adding the following to your [`.storybook/preview.js`](../configure/overview.md#configure-story-rendering): + + + + + + + +## Documentation-only MDX + +Typically, when you use Storybook MDX, you define stories in the MDX documentation is automatically associated with those stories. But what if you want to write Markdown-style documentation and have it show up in your Storybook? + +If you don't define stories in your MDX, you can write MDX documentation and associate it with an existing story, or embed that MDX as its own documentation node in your Storybook's navigation. + +If you don't define a `Meta`, you can write Markdown and associate with an existing story. See ["CSF Stories with MDX Docs"](../writing-docs/mdx.md). + +To get a "documentation-only story", in your UI, define a `` as you normally would, but don't define any stories. It will show up in your UI as a documentation node: + +![Show documentation](./mdx-documentation-only.png) + +## MDX file names + +Unless you use a custom webpack configuration, all of your `MDX` files should have the suffix `*.stories.mdx`. This tells Storybook to apply its special processing to the `` and `` elements in the file. + +Be sure to update your Storybook config file to load `.stories.mdx` stories, as per the [`addon-docs` installation instructions](https://github.com/storybookjs/storybook/tree/master/addons/docs#installation). diff --git a/docs/api/new-frameworks.md b/docs/api/new-frameworks.md new file mode 100644 index 00000000000..650d4e6ed13 --- /dev/null +++ b/docs/api/new-frameworks.md @@ -0,0 +1,176 @@ +--- +title: 'Frameworks' +--- + +Storybook is architected to support diverse web frameworks including React, Vue, Angular, Web Components, Svelte and over a dozen others. This guide helps you get started on adding new framework support for Storybook. + +## Scaffolding a new framework + +The first thing to do is scaffold your framework support in its own repo. + +We recommend adopting the same project structure as the Storybook monorepo. That structure contains the framework package (`app/`) and an example app (`examples/-kitchen-sink`) as well as other associated documentation and configuration as needed. + +This may seem like a little more hierarchy than what’s necessary. But because the structure mirrors the way Storybook’s own monorepo is structured, you can reuse Storybook’s tooling and it also makes it easier to move the framework into the Storybook into the monorepo at a later point if that is desirable. + +We recommend using `@storybook/html` as a starter framework since it’s the simplest one and doesn’t contain any framework-specific oddities. There is a boilerplate to get you started [here](https://github.com/CodeByAlex/storybook-framework-boilerplate). + +## Framework architecture + +Supporting a new framework in Storybook typically consists of two main aspects: + +1. Configuring the server. In Storybook, the server is the node process that runs when you `start-storybook` or `build-storybook`. Configuring the server typically means configuring babel and webpack in framework-specific ways. + +2. Configuring the client. The client is the code that runs in the browser. Configuring the client means providing a framework-specific story rendering function. + +## Configuring the server + +Storybook has the concept of [presets](./addons.md#addon-presets), which are typically babel/webpack configurations for file loading. If your framework has its own file format, e.g. “.vue,” you might need to transform these files into JS files at load time. If you expect every user of your framework to need this, you should add it to the framework. So far every framework added to Storybook has done this, because Storybook’s core configuration is very minimal. + +### Package structure + +To add a framework preset, it’s useful to understand the package structure. Each framework typically exposes two executables in its `package.json`: + +```json +{ + "bin": { + "start-storybook": "./bin/index.js", + "build-storybook": "./bin/build.js" + } +} +``` + +These scripts pass an `options` object to `@storybook/core/server`, a library that abstracts all of Storybook’s framework-independent code. + +For example, here’s the boilerplate to start the dev server in `start-storybook`: + + + + + + + + +Thus the meat of adding framework presets is filling in that options object. + +### Server options + +As described above, the server `options` object does the heavy lifting of configuring the server. + +Let’s look at the `@storybook/vue`’s options definition: + + + + + + + +The value of the `framework` option (in this case ‘vue’) is something that gets passed to addons and allows them to do special case things for your framework. + +The real meat of this file is the framework presets, and these are standard [Storybook presets](./addons.md#addon-presets) -- you can look at framework packages in the Storybook monorepo (e.g. [React](https://github.com/storybookjs/storybook/blob/next/app/react/src/server/options.ts), [Vue](https://github.com/storybookjs/storybook/blob/next/app/vue/src/server/options.ts), [Web Components](https://github.com/storybookjs/storybook/blob/next/app/web-components/src/server/options.ts)) to see examples of framework-specific customizations. + +When developing your own framework that is not published by storybook, you can pass the path to the framework location with the `frameworkPath` key: + +```ts +// my-framework/src/server/options.ts + +const packageJson = require('../../package.json'); + +export default { + packageJson, + framework: 'my-framework', + frameworkPath: '@my-framework/storybook', + frameworkPresets: [require.resolve('./framework-preset-my-framework.js')], +}; +``` + +Passing a relative path to `frameworkPath` is also possible, just keep in mind that these are resolved from the storybook config directory (`.storybook` by default). + +Make sure the `frameworkPath` ends up at the `dist/client/index.js` file within your framework app. + +## Configuring the client + +To configure the client, you must provide a framework specific render function. Before diving into the details, it’s important to understand how user-written stories relate to what is finally rendered on the screen. + +### Renderable objects + +Storybook stories are ES6 functions that return a “renderable object.” + +Consider the following React story: + + + + + + + +In this case, the renderable object is the React element, ``, + styleUrls: ['./button.css'], +}) +export default class ButtonComponent { + /** + * Checks if the button should be disabled + */ + @Input() + isDisabled: boolean; + + /** + The display content of the button + */ + @Input() + content: string; +} +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-group-story.ts.mdx b/docs/snippets/angular/button-group-story.ts.mdx new file mode 100644 index 00000000000..b5360659a71 --- /dev/null +++ b/docs/snippets/angular/button-group-story.ts.mdx @@ -0,0 +1,32 @@ +```ts +// ButtonGroup.stories.ts + +import { moduleMetadata } from '@storybook/angular'; +import { CommonModule } from '@angular/common'; + +import ButtonGroup from './ButtonGroup.component'; +import Button from './button.component'; +import * as ButtonStories from './Button.stories'; + +export default { + title: 'ButtonGroup', + component: ButtonGroup, + decorators: [ + moduleMetadata({ + declarations: [Button], + imports: [CommonModule], + }), + ], +}; + +const Template = (args: ButtonGroup) => ({ + component :ButtonGroup, + props: args, +}); + +export const Pair = Template.bind({}); +Pair.args = { + buttons: [ ...ButtonStories.Primary.args, ...ButtonStories.Secondary.args ], + orientation: 'horizontal', +}; +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story-component-args-primary.ts.mdx b/docs/snippets/angular/button-story-component-args-primary.ts.mdx new file mode 100644 index 00000000000..27a98546c53 --- /dev/null +++ b/docs/snippets/angular/button-story-component-args-primary.ts.mdx @@ -0,0 +1,18 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export default { + title: "Button", + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + args: { + // Now all Button stories will be primary. + primary: true, + }, +} + +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story-default-export-with-component.ts.mdx b/docs/snippets/angular/button-story-default-export-with-component.ts.mdx new file mode 100644 index 00000000000..4de7c48f9a7 --- /dev/null +++ b/docs/snippets/angular/button-story-default-export-with-component.ts.mdx @@ -0,0 +1,10 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export default { + title: 'Components/Button', + component: Button, +} +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story-rename-story.ts.mdx b/docs/snippets/angular/button-story-rename-story.ts.mdx new file mode 100644 index 00000000000..7cf5bdbb7ff --- /dev/null +++ b/docs/snippets/angular/button-story-rename-story.ts.mdx @@ -0,0 +1,14 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export const Primary = () => ({ + component: Button, + props: { + label: 'Button', + }, +}); + +Primary.storyName='I am the primary'; +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story-using-args.ts.mdx b/docs/snippets/angular/button-story-using-args.ts.mdx new file mode 100644 index 00000000000..ffe70619637 --- /dev/null +++ b/docs/snippets/angular/button-story-using-args.ts.mdx @@ -0,0 +1,18 @@ +```ts +// Button.stories.ts + +// We create a “template” of how args map to rendering +const Template = (args: Button) => ({ + component: Button, + props: args, +}); + +export const Primary = Template.bind({}); +Primary.args = { background: '#ff0', label: 'Button' }; + +export const Secondary = Template.bind({}); +Secondary.args = { ...Primary.args, label: '😄👍😍💯' }; + +export const Tertiary = Template.bind({}); +Tertiary.args = { ...Primary.args, label: '📚📕📈🤓' }; +``` diff --git a/docs/snippets/angular/button-story-with-args.ts.mdx b/docs/snippets/angular/button-story-with-args.ts.mdx new file mode 100644 index 00000000000..071eb8d804c --- /dev/null +++ b/docs/snippets/angular/button-story-with-args.ts.mdx @@ -0,0 +1,15 @@ +```ts +//Button.stories.ts + +const Template = (args: Button) => ({ + component: Button, + props: args, +}); + +export const Primary = Template.bind({}); + +Primary.args = { + primary: true, + label: 'Primary', +}; +``` diff --git a/docs/snippets/angular/button-story-with-blue-args.ts.mdx b/docs/snippets/angular/button-story-with-blue-args.ts.mdx new file mode 100644 index 00000000000..5fc2dae5956 --- /dev/null +++ b/docs/snippets/angular/button-story-with-blue-args.ts.mdx @@ -0,0 +1,19 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export default { + title: 'Button', + component: Button, + parameters: { + backgrounds: { + values: [ + { name: 'red', value: '#f00', }, + { name: 'green', value: '#0f0', }, + { name: 'blue', value: '#00f', }, + ] + } + } +} +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story-with-emojis.ts.mdx b/docs/snippets/angular/button-story-with-emojis.ts.mdx new file mode 100644 index 00000000000..fbc50f3f667 --- /dev/null +++ b/docs/snippets/angular/button-story-with-emojis.ts.mdx @@ -0,0 +1,29 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export const Primary = () => ({ + component: Button, + props: { + label: 'Button', + background: '#ff0', + }, +}); + +export const Secondary = () => ({ + component: Button, + props: { + label: '😄👍😍💯', + background: '#ff0', + }, +}); + +export const Tertiary = () => ({ + component: Button, + props: { + label: '📚📕📈🤓', + background: '#ff0', + }, +}); +``` diff --git a/docs/snippets/angular/button-story-with-parameters.ts.mdx b/docs/snippets/angular/button-story-with-parameters.ts.mdx new file mode 100644 index 00000000000..8394dedda8d --- /dev/null +++ b/docs/snippets/angular/button-story-with-parameters.ts.mdx @@ -0,0 +1,18 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export default { + title: 'Button', + component: Button, + parameters: { + backgrounds: { + values: [ + { name: 'red', value: '#f00', }, + { name: 'green', value: '#0f0', }, + ], + } + } +}; +``` \ No newline at end of file diff --git a/docs/snippets/angular/button-story.ts.mdx b/docs/snippets/angular/button-story.ts.mdx new file mode 100644 index 00000000000..ec48eb0926e --- /dev/null +++ b/docs/snippets/angular/button-story.ts.mdx @@ -0,0 +1,12 @@ +```ts +// Button.stories.ts + +import Button from './button.component'; + +export const Primary = () => ({ + component: Button, + props: { + label: 'Button', + }, +}); +``` \ No newline at end of file diff --git a/docs/snippets/angular/list-story-expanded.ts.mdx b/docs/snippets/angular/list-story-expanded.ts.mdx new file mode 100644 index 00000000000..bf138922109 --- /dev/null +++ b/docs/snippets/angular/list-story-expanded.ts.mdx @@ -0,0 +1,38 @@ +```ts +// List.stories.ts + +import { moduleMetadata } from '@storybook/angular'; +import { CommonModule } from '@angular/common'; + +import List from './list.component'; +import ListItem from './list-item.component' + + +export default { + title: 'List', + component: List, + decorators: [ + moduleMetadata({ + declarations: [ListItem], + imports: [CommonModule], + }), + ], +}; + +// Always an empty list, not super interesting +const Template = (args: List) => ({ + component: List, + props: args, +}); + +export const OneItem = Template.bind({}); +OneItem.args = { + items:[ListItem] +}; + +export const ManyItems = Template.bind({}); +ManyItems.args = { + items:[ListItem,ListItem,ListItem] +}; + +``` \ No newline at end of file diff --git a/docs/snippets/angular/list-story-reuse-data.ts.mdx b/docs/snippets/angular/list-story-reuse-data.ts.mdx new file mode 100644 index 00000000000..880d429dced --- /dev/null +++ b/docs/snippets/angular/list-story-reuse-data.ts.mdx @@ -0,0 +1,14 @@ +```ts +// List.stories.ts + +import * as ListItemStories from './ListItem.stories'; + +export const ManyItems = Template.bind({}); +ManyItems.args = { + items:[ + ...ListItemStories.Selected.args, + ...ListItemStories.Unselected.args, + ...ListItemStoriesUnselected.args + ] +}; +``` \ No newline at end of file diff --git a/docs/snippets/angular/list-story-starter.ts.mdx b/docs/snippets/angular/list-story-starter.ts.mdx new file mode 100644 index 00000000000..045eedd99b9 --- /dev/null +++ b/docs/snippets/angular/list-story-starter.ts.mdx @@ -0,0 +1,17 @@ +```ts +// List.stories.ts + +import List from './list.component'; + +export default { + title: 'List', + component: List, +}; + +// Always an empty list, not super interesting +const Template = (args: List) => ({ + component: List, + props: args, +}); + +``` \ No newline at end of file diff --git a/docs/snippets/angular/page-story.ts.mdx b/docs/snippets/angular/page-story.ts.mdx new file mode 100644 index 00000000000..20f0ba43ad0 --- /dev/null +++ b/docs/snippets/angular/page-story.ts.mdx @@ -0,0 +1,33 @@ +```ts +// Page.stories.ts + +import { moduleMetadata } from '@storybook/angular'; +import { CommonModule } from '@angular/common'; + +import Button from './button.component'; +import Header from './header.component'; +import Page from './page.component'; + +import * as HeaderStories from './Header.stories'; + +export default { + title: 'Example/Page', + component: Header, + decorators: [ + moduleMetadata({ + declarations: [Button, Header], + imports: [CommonModule], + }), + ], +}; + +const Template = (args: Page) => ({ + component: Page, + props: args, +}); + +export const LoggedIn = Template.bind({}); +LoggedIn.args = { + ...HeaderStories.LoggedIn.args, +}; +``` \ No newline at end of file diff --git a/docs/snippets/angular/storybook-angular-inline-css-loader.js.mdx b/docs/snippets/angular/storybook-angular-inline-css-loader.js.mdx new file mode 100644 index 00000000000..2d966e113f0 --- /dev/null +++ b/docs/snippets/angular/storybook-angular-inline-css-loader.js.mdx @@ -0,0 +1,3 @@ +```js +import '!style-loader!css-loader!./styles.css'; +``` \ No newline at end of file diff --git a/docs/snippets/common/addon-consume-globaltype.js.mdx b/docs/snippets/common/addon-consume-globaltype.js.mdx new file mode 100644 index 00000000000..b46acf0f4d0 --- /dev/null +++ b/docs/snippets/common/addon-consume-globaltype.js.mdx @@ -0,0 +1,25 @@ +```js +// your-addon-register-file.js + +import { useGlobals } from '@storybook/api'; +import { AddonPanel, Placeholder, Separator, Source, Spaced, Title } from '@storybook/components'; + +const ThemePanel = props => { + const [{ theme: themeName }] = useGlobals(); + const theme = getTheme(themeName); + + return ( + + {theme ? ( + + {theme.name} +

The full theme object/p> + + + ) : ( + No theme selected + )} + + ); +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/args-usage-with-addons.js.mdx b/docs/snippets/common/args-usage-with-addons.js.mdx new file mode 100644 index 00000000000..6c6f5fb6db7 --- /dev/null +++ b/docs/snippets/common/args-usage-with-addons.js.mdx @@ -0,0 +1,16 @@ +```js +// your-addon/register.js + +import { useArgs } from '@storybook/api'; + +const [args, updateArgs,resetArgs] = useArgs(); + +// To update one or more args: +updateArgs({ key: 'value' }); + +// To reset one (or more) args: +resetArgs(argNames:['key']); + +// To reset all args +resetArgs(); +``` \ No newline at end of file diff --git a/docs/snippets/common/badge-story-starter-example.mdx.mdx b/docs/snippets/common/badge-story-starter-example.mdx.mdx new file mode 100644 index 00000000000..4f24652e16b --- /dev/null +++ b/docs/snippets/common/badge-story-starter-example.mdx.mdx @@ -0,0 +1,49 @@ +```md + + + +import { Meta, Story, Preview } from '@storybook/addon-docs/blocks'; + +import { Badge } from './Badge'; +import { Icon } from './Icon'; + + + +# Badge + +Let's define a story for our `Badge` component: + + + Positive + + +We can drop it in a `Canvas` to get a code snippet: + + + + Negative + + + +We can even preview multiple stories in a block. This +gets rendered as a group, but defines individual stories +with unique URLs and isolated snapshot tests. + + + + Warning + + + Neutral + + + Error + + + + + with icon + + + +``` \ No newline at end of file diff --git a/docs/snippets/common/badge-story.mdx.mdx b/docs/snippets/common/badge-story.mdx.mdx new file mode 100644 index 00000000000..9399ee66bfd --- /dev/null +++ b/docs/snippets/common/badge-story.mdx.mdx @@ -0,0 +1,69 @@ +```md + + + +import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks'; + +import { Badge } from './Badge'; +import { Icon } from './Icon'; + + + + + +export const Template = (args) => + +# Badge + +Let's define a story for our `Badge` component: + + + {Template.bind({})} + + +We can drop it in a `Canvas` to get a code snippet: + + + + {Template.bind({})} + + + +We can even preview multiple Stories in a block. This +gets rendered as a group, but defines individual stories +with unique URLs which is great for review and testing. + + + + {Template.bind({})} + + + {Template.bind({})} + + + {Template.bind({})} + + with icon) + )}}> + {Template.bind({})} + + + +``` \ No newline at end of file diff --git a/docs/snippets/common/button-group-story-subcomponents.js.mdx b/docs/snippets/common/button-group-story-subcomponents.js.mdx new file mode 100644 index 00000000000..f96e2b5032b --- /dev/null +++ b/docs/snippets/common/button-group-story-subcomponents.js.mdx @@ -0,0 +1,11 @@ +```js +// ButtonGroup.stories.js + +import { Button, ButtonGroup } from '../ButtonGroup'; + +export default { + title: 'Path/to/ButtonGroup', + component: ButtonGroup, + subcomponents: { Button }, +}; +``` diff --git a/docs/snippets/common/button-group-story-subcomponents.ts.mdx b/docs/snippets/common/button-group-story-subcomponents.ts.mdx new file mode 100644 index 00000000000..ca5231fed93 --- /dev/null +++ b/docs/snippets/common/button-group-story-subcomponents.ts.mdx @@ -0,0 +1,11 @@ +```ts +// ButtonGroup.stories.ts + +import { ButtonGroup, ButtonGroupProps } from '../ButtonGroup'; + +export default { + title: 'Path/to/ButtonGroup', + component: ButtonGroup, + subcomponents: { Button }, +} as Meta; +``` diff --git a/docs/snippets/common/button-story-action-event-handle.js.mdx b/docs/snippets/common/button-story-action-event-handle.js.mdx new file mode 100644 index 00000000000..b8c01989e44 --- /dev/null +++ b/docs/snippets/common/button-story-action-event-handle.js.mdx @@ -0,0 +1,13 @@ +```js +// Button.stories.js + +export default { + title: 'Button', + parameters: { + actions: { + handles: ['mouseover', 'click .btn'] + } + } +}; + +``` diff --git a/docs/snippets/common/button-story-configure-backgrounds.js.mdx b/docs/snippets/common/button-story-configure-backgrounds.js.mdx new file mode 100644 index 00000000000..619f5c72059 --- /dev/null +++ b/docs/snippets/common/button-story-configure-backgrounds.js.mdx @@ -0,0 +1,17 @@ +```js +// Button.stories.js + +// To apply a set of backgrounds to all stories of Button: +export default { + title: 'Button', + parameters: { + backgrounds: { + default: 'twitter', + values: [ + { name: 'twitter', value: '#00aced' }, + { name: 'facebook', value: '#3b5998' }, + ], + }, + }, +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-controls-color-picker.js.mdx b/docs/snippets/common/button-story-controls-color-picker.js.mdx new file mode 100644 index 00000000000..719ffddde99 --- /dev/null +++ b/docs/snippets/common/button-story-controls-color-picker.js.mdx @@ -0,0 +1,11 @@ +```js +// Button.stories.js + +export default { + title: 'Button', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-controls-red-input.js.mdx b/docs/snippets/common/button-story-controls-red-input.js.mdx new file mode 100644 index 00000000000..7b96c7c45c1 --- /dev/null +++ b/docs/snippets/common/button-story-controls-red-input.js.mdx @@ -0,0 +1,8 @@ +```js +// Button.stories.js + +const Red = Template.bind({}); +Red.args = { + backgroundColor: '#e00', +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-default-export.js.mdx b/docs/snippets/common/button-story-default-export.js.mdx new file mode 100644 index 00000000000..d9428bb3a93 --- /dev/null +++ b/docs/snippets/common/button-story-default-export.js.mdx @@ -0,0 +1,7 @@ +```js +// Button.stories.js + +export default { + title: 'Button' +} +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-disable-backgrounds.js.mdx b/docs/snippets/common/button-story-disable-backgrounds.js.mdx new file mode 100644 index 00000000000..b8e47175b81 --- /dev/null +++ b/docs/snippets/common/button-story-disable-backgrounds.js.mdx @@ -0,0 +1,8 @@ +```js +// Button.stories.js + +export const Large = Template.bind({}); +Large.parameters = { + backgrounds: { disable: true } +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-disable-docspage-component.js.mdx b/docs/snippets/common/button-story-disable-docspage-component.js.mdx new file mode 100644 index 00000000000..2ff03bdc2a8 --- /dev/null +++ b/docs/snippets/common/button-story-disable-docspage-component.js.mdx @@ -0,0 +1,18 @@ +```js +// Button.stories.js + +import { Button } from './Button'; + +export default { + title: 'Button', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + parameters: { + docs: { + page: null + } + }, +}; +``` diff --git a/docs/snippets/common/button-story-disable-docspage.js.mdx b/docs/snippets/common/button-story-disable-docspage.js.mdx new file mode 100644 index 00000000000..c33f5026bc0 --- /dev/null +++ b/docs/snippets/common/button-story-disable-docspage.js.mdx @@ -0,0 +1,6 @@ +```js +// Button.stories.js + +export const Primary = Template.bind({}); +Primary.parameters = { docs: { page: null } } +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-grouped.js.mdx b/docs/snippets/common/button-story-grouped.js.mdx new file mode 100644 index 00000000000..fb5e9d4b68d --- /dev/null +++ b/docs/snippets/common/button-story-grouped.js.mdx @@ -0,0 +1,7 @@ +```js +// Button.stories.js + +export default { + title: 'Design System/Atoms/Button' +} +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-hide-nocontrols-warning.js.mdx b/docs/snippets/common/button-story-hide-nocontrols-warning.js.mdx new file mode 100644 index 00000000000..64f9b74f98e --- /dev/null +++ b/docs/snippets/common/button-story-hide-nocontrols-warning.js.mdx @@ -0,0 +1,9 @@ +```js +// Button.stories.js + +export const Large = Template.bind({}); + +Basic.parameters = { + controls: { hideNoControlsWarning: true }, +}; +``` \ No newline at end of file diff --git a/docs/snippets/common/button-story-hypothetical-example.js.mdx b/docs/snippets/common/button-story-hypothetical-example.js.mdx new file mode 100644 index 00000000000..fe4118be5c1 --- /dev/null +++ b/docs/snippets/common/button-story-hypothetical-example.js.mdx @@ -0,0 +1,17 @@ +```js +// Button.stories.js + +import { Button } from './Button'; + +export default { + title: 'Button', + component: Button, +}; + +export const Sample = () => ({ + template: ' - -); -``` - -### Storybook Decorators - -You can also expose this functionality as a Storybook decorator and use it like this: - -```js -import Button from './button'; -import Center from './center'; - -export default { - title: 'Button', - decorators: [storyFn =>

{storyFn()}
], -}; - -export const defaultView = () => ( - -); -``` - -You can also add a decorator globally for all stories like this: - -in `.storybook/preview.js`: - -```js -import { addDecorator } from '@storybook/react'; -import Center from './center'; - -addDecorator(storyFn =>
{storyFn()}
); -``` - -## 2. Native Addons - -Native addons use Storybook as a platform and interact with it. Native addons can add extra features beyond wrapping stories. - -For example, [storybook-actions](https://github.com/storybookjs/storybook/tree/master/addons/actions) is such an addon. - -![Demo of Storybook Addon Actions](../static/addon-actions-demo.gif) - -It will allow you to inspect the parameters of any event of your components. - -See the following links to learn more about native addons: - -- [Using addons](/addons/using-addons) -- [Addon gallery](https://storybook.js.org/addons/) -- [Write your own addon](/addons/writing-addons) diff --git a/docs/src/pages/addons/static/addon-actions-demo.gif b/docs/src/pages/addons/static/addon-actions-demo.gif deleted file mode 100644 index 21220819fd0..00000000000 Binary files a/docs/src/pages/addons/static/addon-actions-demo.gif and /dev/null differ diff --git a/docs/src/pages/addons/static/default-addons.png b/docs/src/pages/addons/static/default-addons.png deleted file mode 100644 index 37e45c5f885..00000000000 Binary files a/docs/src/pages/addons/static/default-addons.png and /dev/null differ diff --git a/docs/src/pages/addons/static/stories-with-notes.png b/docs/src/pages/addons/static/stories-with-notes.png deleted file mode 100644 index 1584dcef9ad..00000000000 Binary files a/docs/src/pages/addons/static/stories-with-notes.png and /dev/null differ diff --git a/docs/src/pages/addons/static/stories-without-notes.png b/docs/src/pages/addons/static/stories-without-notes.png deleted file mode 100644 index 81c7c1d9548..00000000000 Binary files a/docs/src/pages/addons/static/stories-without-notes.png and /dev/null differ diff --git a/docs/src/pages/addons/static/storybook-components.png b/docs/src/pages/addons/static/storybook-components.png deleted file mode 100644 index c201beb870c..00000000000 Binary files a/docs/src/pages/addons/static/storybook-components.png and /dev/null differ diff --git a/docs/src/pages/addons/using-addons/index.md b/docs/src/pages/addons/using-addons/index.md deleted file mode 100644 index 09e0428ec96..00000000000 --- a/docs/src/pages/addons/using-addons/index.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: 'using-addons' -title: 'Using Addons' ---- - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -Storybook comes with a variety of "core" addons developed and maintained alongside Storybook. Most examples in this site use [actions](https://github.com/storybookjs/storybook/tree/master/addons/actions) and [links](https://github.com/storybookjs/storybook/tree/master/addons/links). But you can use any third party addons distributed via NPM. - -Here's how to do it. - -We are going to use an addon called [Notes](https://github.com/storybookjs/storybook/tree/master/addons/notes). Basically, it allows you to write notes for your stories. - -First, we need to install the addons: - -```sh -yarn add -D @storybook/addons @storybook/addon-actions @storybook/addon-knobs -``` - -within `.storybook/main.js`: - -```js -module.exports = { - addons: ['@storybook/addon-actions', '@storybook/addon-knobs'], -}; -``` - -Once created, you'll have to restart storybook to make the underlying webpack aware of the addons file. - -This will register all the addons and you'll be able to see the actions and knobs panels (in that order) when you are viewing the story. (Links do not register a tab--check individual addon docs to see which Storybook features they use!) - -![Stories without notes](../static/stories-without-notes.png) - -## Addons tab order - -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 { action } from '@storybook/addon-actions'; -import Button from './Button'; - -export default { - title: 'Button', - component: Button, -}; - -export const buttonWithEmoji = () => ( - -); -buttonWithEmoji.parameters = { - notes: 'A small component', -}; -``` - -Then you'll be able to see those notes when you are viewing the story. - -![Stories with notes](../static/stories-with-notes.png) - -## Disable the addon - -You can disable an addon panel for a story by adding a `disabled` parameter. - -```js -import { action } from '@storybook/addon-actions'; -import Button from './Button'; - -export default { - title: 'Button', - component: Button, -}; - -export const buttonWithEmoji = () => ( - -); -buttonWithEmoji.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/preview.js`. Here's an example of how you might do that. - -```js -import { addParameters } from '@storybook/react'; - -addParameters({ - notes: 'global notes', -}); -``` - -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 deleted file mode 100644 index 05283f47127..00000000000 --- a/docs/src/pages/addons/writing-addons/index.md +++ /dev/null @@ -1,361 +0,0 @@ ---- -id: 'writing-addons' -title: 'Writing Addons' ---- - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -This is a complete guide on how to create addons for Storybook. - -## Storybook Basics - -Before we begin, we need to learn a bit about how Storybook works. Basically, Storybook has a **Manager App** and a **Preview Area**. - -Manager App is the client side UI for Storybook. Preview Area is the place where the story is rendered. Usually the Preview Area is an iframe. - -When you select a story from the Manager App, the relevant story is rendered inside the Preview Area. - -![Storybook Components](../static/storybook-components.png) - -As shown in the above image, there's a communication channel that the Manager App and Preview Area use to communicate with each other. - -## Capabilities - -With an addon, you can add more functionality to Storybook. Here are a few things you could do: - -- Add a panel to Storybook (like Action Logger). -- Add a tool to Storybook (like zoom or grid). -- Add a tab to Storybook (like notes). -- Interact/communicate with the iframe/manager. -- Interact/communicate with other addons. -- Change storybook's state using it's APIs. -- Navigating. -- Register keyboard shortcuts (coming soon). - -With this, you can write some pretty cool addons. Look at our [Addon gallery](https://storybook.js.org/addons/) to have a look at some sample addons. - -## Getting Started - -Let's write a simplistic addon for Storybook. We'll add some metadata to the story, which the addon can then use. - -### Add metadata using parameters - -We write a story for our addon like this: - -```js -import React from 'react'; -import Button from './Button'; - -export default { - title: 'Button', - parameters: { - myAddon: { - data: 'this data is passed to the addon', - }, - }, -}; -``` - -### Add a panel - -We write an addon that responds to a change in story selection like so: - -```js -// register.js - -import React from 'react'; -import { addons, types } from '@storybook/addons'; -import { useParameter } from '@storybook/api'; -import { AddonPanel } from '@storybook/components'; - -const ADDON_ID = 'myaddon'; -const PARAM_KEY = 'myAddon'; -const PANEL_ID = `${ADDON_ID}/panel`; - -const MyPanel = () => { - const value = useParameter(PARAM_KEY, null); - const item = value ? value.data : ''; - return
{item}
; -}; - -addons.register(ADDON_ID, (api) => { - const render = ({ active, key }) => ( - - - - ); - const title = 'My Addon'; - - addons.add(PANEL_ID, { - type: types.PANEL, - title, - render, - paramKey: PARAM_KEY, - }); -}); -``` - -#### Register the addon - -within `.storybook/main.js`: - -```js -module.exports = { - addons: ['path/to/addon'], -}; -``` - -Now restart/rebuild storybook and the addon should show up! -When changing stories, the addon's onStoryChange method will be invoked with the new storyId. - -#### TSX Addons - -When your addon needs additional typing or you want to keep it locally, you can register it within `.storybook/manager.tsx` -and import there your TSX components to be compiled by webpack, not requiring an external package nor an additional build step. - -```tsx -// manager.tsx - -import React from 'react'; -import { addons } from '@storybook/addons'; -import { AddonPanel } from '@storybook/components'; -import { ADDON_ID, PARAM_KEY, PANEL_ID, MyPanel } from './MyAddon'; - -addons.register(ADDON_ID, (api) => { - addons.addPanel(PANEL_ID, { - title: 'My Addon', - paramKey: PARAM_KEY, - render: ({ active, key }) => ( - - - - ), - }); -}); -``` - -#### Note - -If you get an error similar to: - -``` -ModuleParseError: Module parse failed: Unexpected token (92:22) -You may need an appropriate loader to handle this file type. -| var value = this.state.value; -| var active = this.props.active; -> return active ?
{value}
: null; -| } -| }]); -``` - -It is likely because you do not have a `.babelrc` file or do not have it configured with the correct presets `{ "presets": ["@babel/preset-env", "@babel/preset-react"] }` - -## A more complex addon - -If we want to create a more complex addon, one that wraps the component being rendered for example, there are a few more steps. -Essentially you can start communicating from and to the manager using the storybook API. - -Now we need to create two files, `register.js` and `index.js,`. -`register.js` will be loaded by the manager (the outer frame) and `index.js` will be loaded in the iframe/preview. If you want your addon to be framework agnostic, THIS is the file where you need to be careful about that. - -## Creating a decorator - -Let's add the following content to the `index.js`. It will expose a decorator called `withMyAddon` which we use the `.addDecorator()` API to decorate all our stories. - -The `@storybook/addons` package contains a `makeDecorator` function which we can use to create such a decorator: - -```js -import React from 'react'; -import addons, { makeDecorator } from '@storybook/addons'; - -export default makeDecorator({ - name: 'withMyAddon', - parameterName: 'myParameter', - // This means don't run this decorator if the notes decorator is not set - skipIfNoParametersOrOptions: true, - wrapper: (getStory, context, { parameters }) => { - const channel = addons.getChannel(); - - // Our API above sets the notes parameter to a string, - // which we send to the channel - channel.emit('my/customEvent', parameters); - // we can also add subscriptions here using channel.on('eventName', callback); - - return getStory(context); - }, -}); -``` - -In this case, our component can access something called the channel. It lets us communicate with the panel (in the manager). -It has a NodeJS [EventEmitter](https://nodejs.org/api/events.html) compatible API. - -In the above case, it will emit the notes' text to the channel, so our panel can listen to it. - -Then add the following code to the `register.js`. - -The storybook API itself has `.on()`, `.off()` and `.emit()` methods just like the [EventEmitter](https://nodejs.org/api/events.html). - -A very convenient way of using the channel in the manager is using the `useChannel` hook. - -```js -import React from 'react'; -import { addons } from '@storybook/addons'; -import { useChannel } from '@storybook/api'; -import { STORY_CHANGED } from '@storybook/core-events'; -import { AddonPanel } from '@storybook/components'; - -const MyPanel = () => { - const emit = useChannel({ - STORY_RENDERED: (id) => { - /* do something */ - }, - 'my/customEvent': () => { - /* so something */ - }, - }); - - return ; -}; - -// Register the addon with a unique name. -addons.register('my/addon', (api) => { - // Also need to set a unique name to the panel. - addons.addPanel('my/addon/panel', { - title: 'My Addon', - render: ({ active, key }) => ( - - - - ), - }); -}); -``` - -It will register our addon and add a panel. In that we'll render React component called `MyPanel`. - -Using the hook, we'll listen for events and gain access to the `emit` function for emitting events from our component. - -> In this example, we are only sending messages from the Preview Area to the Manager App (our panel). - -It also listens to another event, called onStory, in the storybook API, which fires when the user selects a story. - -Multiple addons can be loaded, but only a single panel can be shown, the render function will receive an `active` prop, which is true if the addon is shown. It is up to you to decide if this mean your component must be unmounted, or visually hidden. This allows you to keep state but unmount expensive renderings. - -The `AddonPanel` component will stop rendering of it's children if it's `active`-prop is false. - -A great way of preserving state even when your component is unmounted is using the `useAddonState` hook: - -```js -export const Panel = () => { - const [state, setState] = useAddonState('my/addon-id', 'initial state'); - - return ; -}; -``` - -This will store your addon's state into storybook core state, and so when your component gets unmounted & remounted, the state will be restored. - -This is also a great way to sync state between multiple components of the same addon. - -### Using the complex addon - -within `.storybook/main.js`: - -```js -module.exports = { - addons: ['path/to/addon'], -}; -``` - -Then you need to start using the decorator: - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import withMyAddon from 'path/to/index.js'; - -import Button from './Button'; - -export default { - title: 'Button', - decorators: [withMyAddon], -}; - -export const defaultView = () => ; -defaultView.parameters = { - myParameter: { data: 'awesome' }, -}; -``` - -### Disabling an addon panel - -It's possible to disable an addon panel for a particular story. - -To offer that capability, you need to pass the paramKey when you register the panel - -```js -addons.register(ADDON_ID, () => { - addons.add(PANEL_ID, { - type: types.PANEL, - title: 'My Addon', - render: () =>
Addon tab content
, - paramKey: 'myAddon', - }); -}); -``` - -While adding a story, you can then pass a `disabled` parameter. - -```js -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 - -We use [emotion](https://emotion.sh) for styling, AND we provide a theme which can be set by the user! - -We highly recommend you also use emotion to style your components for storybook, but it's not a requirement. You can use inline styles or another css-in-js lib. You can receive the theme as a prop by using the `withTheme` hoc from emotion. [Read more about theming](/configurations/theming). - -But if you do use emotion, you can use the active storybook theme, which benefits users. - -## Re-using existing components - -Wouldn't it be awesome if we provided you with some common used components you could use to build out your own addon quickly and fit in right away? -Good news! WE DO! We publish most of storybook's UI components as a package: `@storybook/components`. You can check them out in [our storybook](https://storybookjs.netlify.com/) (pretty meta right?). - -## Addon API - -Here we've only used a few functionalities of our [Addon API](/addons/api). -You can learn more about the complete API [here](/addons/api). - -## Packaging - -It's possible to package this addon into a NPM module. As an example, have a look at this [package](https://github.com/storybookjs/storybook/tree/master/addons/notes). - -In addition to moving the above code to a NPM module, we've set `react` and `@storybook/addons` as peer dependencies. - -### Local Development - -When you are developing your addon as a package, you can't use `npm link` to add it to your project. Instead add your package as a local dependency into your `package.json` as shown below: - -```json -{ - "dependencies": { - "@storybook/addon-custom": "file:///home/username/myrepo" - } -} -``` - -### Package Maintenance - -Your packaged Storybook addon needs to be written in ES5. If you are using ES6, then you need to transpile it. diff --git a/docs/src/pages/basics/community/index.md b/docs/src/pages/basics/community/index.md deleted file mode 100644 index 0306275cb10..00000000000 --- a/docs/src/pages/basics/community/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: 'community' -title: 'Community' ---- - -Storybook is maintained by a [community of users](https://github.com/storybookjs/storybook/graphs/contributors). It's easy to get involved and all contributions are welcome! - -You can read about how to get involved in detail in our [Contribution Guide](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md) but at a high level, a good place to get started is: - -1. Report issues at the [issue tracker](https://github.com/storybookjs/storybook/issues). -2. Help us to triage issues above, by answering questions, helping to create [reproductions](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions), and clarifying feature requests. -3. Pitch in with a PR, especially those marked ["good first issue"](https://github.com/storybookjs/storybook/labels/good%20first%20issue). diff --git a/docs/src/pages/basics/exporting-storybook/index.md b/docs/src/pages/basics/exporting-storybook/index.md deleted file mode 100644 index 9b1a55a99d9..00000000000 --- a/docs/src/pages/basics/exporting-storybook/index.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -id: 'exporting-storybook' -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](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. - -Add the following NPM script: - -```json -{ - "scripts": { - "build-storybook": "build-storybook -c .storybook -o .out" - } -} -``` - -Then run `yarn build-storybook`. - -This will build the storybook configured in the Storybook directory into a static web app and place it inside the `.out` directory. -Now you can deploy the content in the `.out` directory wherever you want. - -To test it locally: - -```sh -npx http-server .out -``` - -## Deploying to GitHub Pages - -Additionally, you can deploy Storybook directly into GitHub pages with our [storybook-deployer](https://github.com/storybookjs/storybook-deployer) tool. - -Or, you can export your storybook into the docs directory and use it as the root for GitHub pages. Have a look at [this guide](https://github.com/blog/2233-publish-your-project-documentation-with-github-pages) for more information. - -## Deploying to Vercel - -[Vercel](https://vercel.com/home) is a cloud platform for hosting static sites and Serverless Functions, that you can use to deploy your Storybook projects to your personal domain (or a free `.now.sh` suffixed URL). - -To deploy your Storybook project to Vercel, all that's required is to connect your [Git repository](https://vercel.com/docs/v2/git-integrations) and import the project. The build command, project directory, and project type will be automatically detected upon import. - -If you are working with more than just a Storybook project in your repository, and just want to build Storybook for your deployment, be sure to set the `build` script in the `package.json` file to the following: - -``` -`"build": "build-storybook -c .storybook -o build"` -``` - -Once imported, a deployment will be created. From now on, every time you `git push`, a new [Preview Deployment](https://vercel.com/docs/v2/platform/deployments#preview) will be created. If pushing or merging to the default branch, a [Production Deployment](https://vercel.com/docs/v2/platform/deployments#production) will be triggered. diff --git a/docs/src/pages/basics/introduction/index.md b/docs/src/pages/basics/introduction/index.md deleted file mode 100644 index 2b20153738a..00000000000 --- a/docs/src/pages/basics/introduction/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -id: 'introduction' -title: 'Introduction' ---- - -Storybook is a user interface development environment and playground for UI components. -The tool enables developers to create components independently and showcase components interactively in an isolated development environment. - -Storybook runs outside of the main app so users can develop UI components in isolation without worrying about app specific dependencies and requirements. - -![Storybook](../static/screenshot.png) - -Storybook also supports a lot of [addons](/addons/introduction) and comes with a flexible API to customize Storybook as desired. -A [Static version](/basics/exporting-storybook) of Storybook can also be built and deployed to an HTTP server. - -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](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/static/basic-stories.png b/docs/src/pages/basics/static/basic-stories.png deleted file mode 100644 index 750b2d63268..00000000000 Binary files a/docs/src/pages/basics/static/basic-stories.png and /dev/null differ diff --git a/docs/src/pages/basics/static/screenshot.png b/docs/src/pages/basics/static/screenshot.png deleted file mode 100644 index 750b2d63268..00000000000 Binary files a/docs/src/pages/basics/static/screenshot.png and /dev/null differ diff --git a/docs/src/pages/basics/toolbar-guide/index.md b/docs/src/pages/basics/toolbar-guide/index.md deleted file mode 100644 index fcaaecc75a9..00000000000 --- a/docs/src/pages/basics/toolbar-guide/index.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: 'toolbar-guide' -title: 'Toolbar' ---- - -Storybook comes with a toolbar which is displayed by default as part of the layout with a default height of `40px`. - -The user can toggle whether to hide or display the toolbar. It is on display by default and set on the canvas tab. This feature can be toggled as one of the [Global options](https://storybook.js.org/docs/configurations/options-parameter/). - -To hide or display the toolbar, the user should toggle the `isToolshown` key in `config.js`, - -```jsx -addParameter({ - options: { - /** - * show/hide tool bar - * @type {Boolean} - */ - isToolshown: true, - }, -}); -``` - -The toolbar is not an addon but provides easy access to them. Both Canvas and docs are tabs within the toolbar. While on the canvas tab users have access to the following default features: - -- Zoom-in, zoom-out, and reset-zoom on the preview -- Change the background of the preview -- Adjust for color blindness emulation -- Make the preview full screen -- Open the canvas in a new tab -- Copy canvas link - -On the other hand, the [DocsPage](https://github.com/storybookjs/storybook/tree/master/addons/docs#docspage) is also not an addon but the successor to addon-info. The user has access to the DocsPage when `Docs` is installed. It is added to the toolbar by default with zero config required. - -A user can [write custom addons](https://storybook.js.org/docs/addons/writing-addons/) and add them to the toolbar. This can be achieved by modifying a the value for `type` when registering an addon. - -For an addon to be displayed in the toolbar, the user must add the `TAB` type to the addon. The user must also add the `route` and `match` key/value pairs. - -```jsx -import React from 'react'; -import { addons, types } from '@storybook/addons'; - -const ADDON_ID = 'myaddon'; -const PARAM_KEY = 'myAddon'; -const PANEL_ID = `${ADDON_ID}/tab`; - -addons.register(ADDON_ID, api => { - const render = ({ active, key }) => ( -
- -
- ); - const title = 'My Addon'; - - addons.add(PANEL_ID, { - type: types.TAB, - route: ({ storyId }) => `/info/${storyId}`, - match: ({ viewMode }) => viewMode === 'info', - title, - render, - }); -}); -``` diff --git a/docs/src/pages/basics/writing-stories/index.md b/docs/src/pages/basics/writing-stories/index.md deleted file mode 100644 index 673b560e124..00000000000 --- a/docs/src/pages/basics/writing-stories/index.md +++ /dev/null @@ -1,390 +0,0 @@ ---- -id: 'writing-stories' -title: 'Writing Stories' ---- - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -A Storybook is a collection of stories. Each story represents a single visual state of a component. - -> Technically, a story is a function that returns something that can be rendered to screen. - -## Basic story - -Here is an example of stories for a `Button` component: - -```js -import React from 'react'; -import { action } from '@storybook/addon-actions'; -import Button from './Button'; - -export default { - component: Button, - title: 'Button', -}; - -export const text = () => ; - -export const emoji = () => ( - -); -``` - -This is what you'll see in Storybook: - -![Basic stories](../static/basic-stories.png) - -The named exports define the Button's stories, and the `default` export defines metadata that applies to the group. In this case, the `component` is `Button`. The `title` determines the title of the group in Storybook's left-hand navigation panel and should be unique, i.e. not re-used across files. In this case it's located at the top level, but typically it's [positioned within the story hierarchy](#story-hierarchy). - -This example is written in Storybook's [Component Story Format (CSF)](../../formats/component-story-format/). Storybook also supports: - -- a classic [storiesOf API](../../formats/storiesof-api/), which adds stories through Storybook's API. -- an experimental [MDX syntax](../../formats/mdx-syntax/), which mixes longform Markdown docs and JSX stories. - -Since CSF is a new addition to Storybook, most Storybook examples you'll find in the wild are written to the [storiesOf API](../../formats/storiesof-api/). - -Furthermore, Storybook for React Native currently only supports the `storiesOf` format. React Native will get CSF and MDX support in a future release. - -## Story file location - -Stories are easier to maintain when they are located alongside the components they document. We recommend: - -```plaintext -• -└── src - └── components - └── button - ├── button.js - └── button.stories.js -``` - -It's up to you to find a naming/placing scheme that works for your project/team. Other naming conventions: - -
- stories sub-directory in component directory - -```plaintext -• -└── src - └── components - └── button - ├── button.js - └── stories - └── button.stories.js -``` - -
- -
- stories directory outside src directory - -```plaintext -• -├── src -│ └── components -│ └── button.js -└── stories - └── button.stories.js -``` - -
- -## Loading stories - -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 `.storybook/preview.js`. - -The `configure` function accepts: - -- A single `require.context` "`req`" -- An array of `req`s to load from multiple locations -- A loader function that should return void or an array of module exports - -If you want to load from multiple locations, you can use an array: - -```js -import { configure } from '@storybook/react'; - -configure( - [ - require.context('../src/components', true, /\.stories\.js$/), - require.context('../lib', true, /\.stories\.js$/), - ], - module -); -``` - -Or if you want to do some custom loading logic, you can use a loader function. Just remember to return an array of module exports if you want to use Component Story Format. Here's an example that forces files to load in a specific order. - -```js -import { configure } from '@storybook/react'; - -const loaderFn = () => [ - require('./welcome.stories.js'), - require('./prelude.stories.js'), - require('./button.stories.js'), - require('./input.stories.js'), -]; - -configure(loaderFn, module); -``` - -Here's another example that mixes manual loading with glob-style loading: - -```js -import { configure } from '@storybook/react'; - -const loaderFn = () => { - const allExports = [require('./welcome.stories.js')]; - const req = require.context('../src/components', true, /\.stories\.js$/); - req.keys().forEach((fname) => allExports.push(req(fname))); - return allExports; -}; - -configure(loaderFn, module); -``` - -Storybook uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#requirecontext) to load modules dynamically. Take a look at the relevant Webpack [docs](https://webpack.js.org/guides/dependency-management/#requirecontext) to learn more about how to use `require.context`. - -If you are using the `storiesOf` API directly, or are using `@storybook/react-native` where CSF is unavailable, you should use a loader function with **no return value**: - -```js -import { configure } from '@storybook/react'; - -const loaderFn = () => { - // manual loading - require('./welcome.stories.js'); - require('./button.stories.js'); - - // dynamic loading, unavailable in react-native - const req = require.context('../src/components', true, /\.stories\.js$/); - req.keys().forEach((fname) => req(fname)); -}; - -configure(loaderFn, module); -``` - -Furthermore, the **React Native** packager resolves all imports at build-time, so it's not possible to load modules dynamically. There is a third party loader [react-native-storybook-loader](https://github.com/elderfo/react-native-storybook-loader) to automatically generate the import statements for all stories. - -## Decorators - -A decorator is a way to wrap a story with a common set of components, for example if you want to wrap a story in some formatting, or provide some context to the story. - -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/preview.js`: - -```jsx -import React from 'react'; -import { addDecorator } from '@storybook/react'; - -addDecorator((storyFn) =>
{storyFn()}
); -``` - -> \* In Vue projects you have to use the special component `` 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 ( ->
-> ->
-> ); -> }, -> }); -> addDecorator(decoratorVueJsx); -> -> var decoratorVueTemplate = () => ({ template: `
` }); -> 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 -import React from 'react'; -import MyComponent from './MyComponent'; - -export default { - title: 'MyComponent', - decorators: [(storyFn) =>
{storyFn()}
], -}; - -export const normal = () => ; -export const special = () => ; -special.decorators = [(storyFn) =>
{storyFn()}
]; -``` - -Decorators are not only for story formatting, they are generally useful for any kind of context needed by a story. - -- Theming libraries require a theme to be passed in through context. Rather than redefining this in every story, add a decorator. -- Likewise, state management libraries like Redux provide a global data store through context. -- Finally, Storybook [addons](../../addons/introduction) heavily use decorators. For example, the Storybook's [Knobs addon](https://github.com/storybookjs/storybook/tree/next/addons/knobs) uses decorators to modify the input properties of the story based on a UI. - -## Parameters - -Parameters are custom metadata for a story. Like decorators, they can also be hierarchically applied: globally, at the component level, or locally at the story level. - -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/preview.js`. - -```js -import { load, addParameters } from '@storybook/react'; -import defaultNotes from './instructions.md'; - -addParameters({ notes: defaultNotes }); -``` - -This would make sense if, for example, `instructions.md` contained instructions on how to document your components when there is no documentation available. - -Then for components that did have documentation, we might override it at the component/story level: - -```jsx -import React from 'react'; -import MyComponent from './MyComponent'; -import componentNotes from './notes.md'; -import specialNotes from './special.md'; - -export default { - title: 'MyComponent', - parameters: { notes: componentNotes }, -}; - -export const small = () => ; -export const medium = () => ; -export const special = () => ; -special.parameters = { - notes: specialNotes, -}; -``` - -In this example, the `small` and `medium` stories get the component notes documented in `notes.md` (as opposed to the generic instructions in `instructions.md`). The `special` story gets some special notes. - -## Searching - -By default, search results will show up based on the file name of your stories. As of storybook 5, you can extend this with `notes` to have certain stories show up when the search input contains matches. For example, if you built a `Callout` component that you want to be found by searching for `popover` or `tooltip` as well, you could use `notes` like this: - -```jsx -export const callout = () => Some children; -callout.parameters = { - notes: 'popover tooltip', -}; -``` - -## Story hierarchy - -Stories can be organized in a nested structure using "/" as a separator. - -For example the following snippets nest the `Button` and `Checkbox` components within the `Atoms` group, under a top-level heading called `Design System`. - -```jsx -// Button.stories.js -import React from 'react'; -import Button from './Button'; - -export default { - title: 'Design System/Atoms/Button', -}; -export const normal = () => ; -``` - -```jsx -// Checkbox.stories.js -import React from 'react'; -import Checkbox from './Checkbox'; - -export default { - title: 'Design System/Atoms/Checkbox', -}; -export const empty = () => ; -export const checked = () => ; -``` - -By default the top-level heading will be treated as any other group, but if you'd like it to be given special emphasis as a "root", use the `showRoots` config option. See the -[configuration options parameter](/configurations/options-parameter) page to learn more. - -## Generating nesting path based on \_\_dirname - -Nesting paths can be programmatically generated with template literals because story names are strings. - -One example would be to use `base` from [`paths.macro`](https://github.com/storybookjs/paths.macro): - -```js -import React from 'react'; -import base from 'paths.macro'; -import BaseButton from '../components/BaseButton'; - -export default { - title: `Other/${base}/Dirname Example`, -}; -export const story1 = () => ; -export const story2 = () => ; -``` - -_This uses [babel-plugin-macros](https://github.com/kentcdodds/babel-plugin-macros)_. - -## Run multiple storybooks - -Multiple storybooks can be built for different kinds of stories or components in a single repository by specifying different port numbers in the start scripts: - -```json -{ - "scripts": { - "start-storybook-for-theme": "start-storybook -p 9001 -c .storybook-theme", - "start-storybook-for-app": "start-storybook -p 8001 -c .storybook-app" - } -} -``` - -## Permalinking to stories - -Sometimes you might wish to change the name of a story or its position in the hierarchy, but preserve the link to the story or its documentation. Here's how to do it. - -Consider the following story: - -```js -export default { - title: 'Foo/Bar', -}; - -export const Baz = () => ; -``` - -Storybook's ID-generation logic will give this the ID `foo-bar--baz`, so the link would be `?path=/story/foo-bar--baz`. - -Now suppose you want to change the position in the hierarchy to `OtherFoo/Bar` and the story name to `Moo`. Here's how to do that: - -```js -export default { - title: 'OtherFoo/Bar', - id: 'Foo/Bar', // or 'foo-bar' if you prefer -}; - -export const Baz = () => ; -Baz.storyName = 'Moo'; -``` - -Storybook will prioritize the `id` over the title for ID generation, if provided, and will prioritize the `story.name` over the export key for display. diff --git a/docs/src/pages/configurations/add-custom-body/index.md b/docs/src/pages/configurations/add-custom-body/index.md deleted file mode 100644 index 371a868d413..00000000000 --- a/docs/src/pages/configurations/add-custom-body/index.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: 'add-custom-body' -title: 'Add Custom Body' ---- - -Sometimes, you may need to add different tags to the HTML body. This is useful for adding some custom content roots. - -You can accomplish this by creating a file called `preview-body.html` inside the Storybook config directory and add tags like this: - -```html -
-``` - -If using relative sizing in your project (like `rem` or `em`), you may update the base `font-size` by adding a `style` tag to `preview-body.html`: - -```html - -``` - -That’s it. Storybook will inject these tags to the html body. It is also possible to use [environment variables](https://storybook.js.org/docs/configurations/env-vars/#usage-in-custom-headbody). - -> **Important** -> -> Storybook will inject these tags to the iframe where your components are rendered. So, these won’t be loaded into the main Storybook UI. diff --git a/docs/src/pages/configurations/add-custom-head-tags/index.md b/docs/src/pages/configurations/add-custom-head-tags/index.md deleted file mode 100644 index 089a9f9c5ed..00000000000 --- a/docs/src/pages/configurations/add-custom-head-tags/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: 'add-custom-head-tags' -title: 'Add Custom Head Tags' ---- - -Sometimes, you may need to add different tags to the HTML head. This is useful for adding web fonts or some external scripts. - -You can accomplish this by creating a file called `preview-head.html` inside the Storybook config directory and add tags like this: - -```html - - -``` - -That's it. Storybook will inject these tags. It is also possible to use [environment variables](https://storybook.js.org/docs/configurations/env-vars/#usage-in-custom-headbody). - -> **Important** -> -> Storybook will inject these tags to the iframe where your components are rendered. So, these won’t be loaded into the main Storybook UI. - -## Add Tags or Scripts to the Main UI. - -Additionally, you may need to add different scripts or tags to the main Storybook UI. This might arise when your custom Webpack configuration outputs or requires additional chunks. - -Create a file called `manager-head.html` inside of the Storybook config directory and add any tags you require. - -> **Important** -> -> Your scripts will run before Storybook's React UI. Also be aware, that this is an uncommon scenario and could potentially break Storybook's UI. So use with caution. diff --git a/docs/src/pages/configurations/cli-options/index.md b/docs/src/pages/configurations/cli-options/index.md deleted file mode 100644 index 4e5e80f9c44..00000000000 --- a/docs/src/pages/configurations/cli-options/index.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: 'cli-options' -title: 'CLI Options' ---- - -React Storybook comes with two CLI utilities. They are `start-storybook` and `build-storybook`. - -They have some options you can pass to alter the storybook behaviors. We have seen some of them in previous docs. - -Here are all those options: - -## For start-storybook - -```plaintext -Usage: start-storybook [options] - -Options: - ---help output usage information --V, --version output the version number --p, --port [number] Port to run Storybook --h, --host [string] Host to run Storybook --s, --static-dir Directory where to load static files from, comma-separated list --c, --config-dir [dir-name] Directory where to load Storybook configurations from ---https Serve Storybook over HTTPS. Note: You must provide your own certificate information. ---ssl-ca Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate) ---ssl-cert Provide an SSL certificate. (Required with --https) ---ssl-key Provide an SSL key. (Required with --https) ---smoke-test Exit after successful start ---ci CI mode (skip interactive prompts, don't open browser) ---quiet Suppress verbose build output ---no-dll Do not use dll reference ---debug-webpack Display final webpack configurations for debugging purposes -``` - -## For build-storybook - -```plaintext -Usage: build-storybook [options] - -Options: - --h, --help output usage information --V, --version output the version number --s, --static-dir Directory where to load static files from, comma-separated list --o, --output-dir [dir-name] Directory where to store built files --c, --config-dir [dir-name] Directory where to load Storybook configurations from --w, --watch Enable watch mode ---loglevel [level] Control level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent] ---quiet Suppress verbose build output ---no-dll Do not use dll reference ---debug-webpack Display final webpack configurations for debugging purposes -``` diff --git a/docs/src/pages/configurations/composition/index.md b/docs/src/pages/configurations/composition/index.md deleted file mode 100644 index e17f6436c78..00000000000 --- a/docs/src/pages/configurations/composition/index.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -id: 'composition' -title: 'Storybook Composition' ---- - -Storybook composition is a feature that allows you to compose multiple storybook together. This can be useful if - -- you have a need for multiple frameworks -- you have multiple teams using separate storybooks -- you are using community component libraries - ---- - -## Composing a storybook - -To compose another storybook into yours, all you need is the URL of storybook. - -Here's how you do it: - -```js -// main.js -module.exports = { - refs: { - // long form - example: { - title: 'My composed storybook', - url: 'https://next--storybookjs.netlify.app/ember-cli', - }, - // shorthand - cra: 'https://next--storybookjs.netlify.app/cra-ts-kitchen-sink', - }, -} -``` - -The above code adds 2 refs to storybook. - -The storybooks being composed don't need to be of the same version or using the same framework. - -> Composing storybooks from < 6 is supported, as long as the storybooks are hosted on unique origins. If you want to compose multiple storybooks from the same origin, those must all be version 6 or higher. - -### Automatic loading - -The process above is actually often not even required! Storybook will auto-compose direct dependencies' storybooks if it's able to find them. - -The ref will be auto-loaded if the **direct dependency** has a field called `storybook` in it's `package.json` which looks like this: - -```json -{ - "storybook": { - "title": "My Package", - "url": "https://my.storybook-url.com" - } -} -``` - -This is really useful if you depend on a component library that is also using storybook. - -Not only do you get to view all of their stories, but their documentation too, if they use [addon-docs](https://github.com/storybookjs/storybook/tree/master/addons/docs)! - -## Preparing to have your storybook composed - -Publish your (static) storybook online using any hosting platform, ensuring CORS is enabled. - -When a user composes many storybooks together this may add considerable weight to the page-load. - -We want to make sure the user composing storybooks together isn't loading the entire storybook just to see the stories appear in the sidebar. - -To optimize the loading of refs we want to generate a `stories.json` file, which contains a static list of all the stories and the parameters needed to render in the sidebar. - -This can be done with the storybook-cli: `npx sb extract`. It requires you have a built storybook. -You may pass it 2 arguments for where you have storybook built, and where you wannt `stories.json` to be placed: `npx sb extract `. - -For an example what this file should look like, see: [here](https://next--storybookjs.netlify.app/dev-kits/stories.json). - -### Authentication - -If you have some authentication layer on your hosted storybook, the composing the storybook will fail. Storybook will show a message in the sidebar if that happens. - - -You can assist the user by creating a `metadata.json` file with a `authUrl` field, and ensure this file **is** loadable (even in the user is not authenticated): - -```json -{ - "authUrl": "https://example.com" -} -``` - -Storybook will show a UI to assist the user to login, and then reload the composed storybook. - -## Advanced - -When you compose a storybook into your own, you get **all** the stories, in their original format. You may not agree with some names, or you might want to filter or sort some stories to your liking. - -You can add a 'mapper' to storybook that will be used to transform the received stories to something of your liking. - -```js -import { addons } from '@storybook/addons'; - -addons.setConfig({ - storyMapper: (ref, story) => { - return { ...a, kind: a.kind.replace('|', '/') }; - } -}); -``` - -You can remove stories by returning `null`. diff --git a/docs/src/pages/configurations/custom-babel-config/index.md b/docs/src/pages/configurations/custom-babel-config/index.md deleted file mode 100644 index 281fad52183..00000000000 --- a/docs/src/pages/configurations/custom-babel-config/index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -id: 'custom-babel-config' -title: 'Custom Babel Config' ---- - -By default, Storybook loads your root `.babelrc` file and load those configurations. -But sometimes some of those options may cause Storybook to throw errors. - -In that case, you can provide a custom `.babelrc` just for Storybook. -For that, create a file called `.babelrc` file inside the Storybook config directory (by default, it's `.storybook`). - -Then Storybook will load the Babel configuration only from that file. - -> Currently we do not support loading the Babel config from the package.json. diff --git a/docs/src/pages/configurations/custom-postcss-config/index.md b/docs/src/pages/configurations/custom-postcss-config/index.md deleted file mode 100644 index a091c194647..00000000000 --- a/docs/src/pages/configurations/custom-postcss-config/index.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: 'custom-postcss-config' -title: 'Custom Postcss Config' ---- - -Storybook can load your custom postcss configuration. - -It searches for the first postcss config file in the parents directories of your loaded file : -- `.postcssrc` -- `.postcssrc.json` -- `.postcssrc.yml` -- `.postcssrc.js` -- `postcss.config.js` - -If no postcss config file is found, it applies a default configuration which is : - -```js -{ - ident: 'postcss', - postcss: {}, - plugins: () => [ - require('postcss-flexbugs-fixes'), - autoprefixer({ - flexbox: 'no-2009', - }), - ], -} -``` - -> Currently we do **not** support loading the Babel config from the `package.json` neither in the Storybook config directory (by default, it's `.storybook`). diff --git a/docs/src/pages/configurations/custom-webpack-config/index.md b/docs/src/pages/configurations/custom-webpack-config/index.md deleted file mode 100644 index 8016664bbfc..00000000000 --- a/docs/src/pages/configurations/custom-webpack-config/index.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -id: 'custom-webpack-config' -title: 'Custom Webpack Config' ---- - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -You can customize Storybook's webpack setup by providing a `webpackFinal` 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. - -> We're trying to make storybook more zero-config over time, **help to hook into the config of generators is very welcome**. - -
- This is what the config for storybook looks like when using CRA in dev-mode: - -```js -{ - mode: 'development', - bail: false, - devtool: '#cheap-module-source-map', - entry: [ - '@storybook/core/dist/server/common/polyfills.js', - '@storybook/core/dist/server/preview/globals.js', - '/preview.js', - 'webpack-hot-middleware/client.js?reload=true', - ], - output: { - path: './', - filename: '[name].[hash].bundle.js', - publicPath: '', - }, - plugins: [ - HtmlWebpackPlugin { - options: { - template: '@storybook/core/dist/server/templates/index.ejs', - templateContent: false, - templateParameters: [Function: templateParameters], - filename: 'iframe.html', - hash: false, - inject: false, - compile: true, - favicon: false, - minify: undefined, - cache: true, - showErrors: true, - chunks: 'all', - excludeChunks: [], - chunksSortMode: 'none', - meta: {}, - title: 'Webpack App', - xhtml: false, - alwaysWriteToDisk: true, - }, - }, - DefinePlugin { - definitions: { - 'process.env': { - NODE_ENV: '"development"', - NODE_PATH: '""', - PUBLIC_URL: '"."', - '' - '' - }, - }, - }, - WatchMissingNodeModulesPlugin { - nodeModulesPath: './node_modules', - }, - HotModuleReplacementPlugin {}, - CaseSensitivePathsPlugin {}, - ProgressPlugin {}, - DefinePlugin { - definitions: { - '' - '' - }, - }, - ], - module: { - rules: [ - { test: /\.(mjs|jsx?)$/, - use: [ - { loader: 'babel-loader', options: - { cacheDirectory: './node_modules/.cache/storybook', - presets: [ - [ './node_modules/@babel/preset-env/lib/index.js', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' } ], - './node_modules/@babel/preset-react/lib/index.js', - './node_modules/@babel/preset-flow/lib/index.js', - ], - plugins: [ - './node_modules/@babel/plugin-proposal-object-rest-spread/lib/index.js', - './node_modules/@babel/plugin-proposal-class-properties/lib/index.js', - './node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js', - [ './node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.js', { sourceMap: true, autoLabel: true } ], - './node_modules/babel-plugin-macros/dist/index.js', - './node_modules/babel-plugin-add-react-displayname/index.js', - [ './node_modules/babel-plugin-react-docgen/lib/index.js', { DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES' } ], - ], - }, - }, - ], - include: [ './' ], - exclude: [ './node_modules' ], - }, - { test: /\.md$/, - use: [ - { loader: './node_modules/raw-loader/index.js' }, - ], - }, - { test: /\.css$/, - use: [ - './node_modules/style-loader/index.js', - { loader: './node_modules/css-loader/dist/cjs.js', options: { importLoaders: 1 } }, - { loader: './node_modules/postcss-loader/src/index.js', options: { ident: 'postcss', postcss: {}, plugins: [Function: plugins] } }, - ], - }, - { test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/, - loader: './node_modules/file-loader/dist/cjs.js', - query: { name: 'static/media/[name].[hash:8].[ext]' }, - }, - { test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/, - loader: './node_modules/url-loader/dist/cjs.js', - query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }, - }, - ], - }, - resolve: { - extensions: [ '.mjs', '.js', '.jsx', '.json' ], - modules: [ 'node_modules' ], - mainFields: [ 'browser', 'main', 'module' ], - alias: { - 'core-js': './node_modules/core-js', - react: './node_modules/react', - 'react-dom': './node_modules/react-dom', - }, - }, - optimization: { - splitChunks: { chunks: 'all' }, - runtimeChunk: true, - minimizer: [ [Object] ], - }, - performance: { hints: false }, -} -``` - -
- -### Debug the default webpack config - - To effectively customize the webpack config, you might need to get the full default config it's using. - -
- -- Create a `.storybook/main.js` file. -- Edit its contents: - ```js - module.exports = { - webpackFinal: (config) => console.dir(config, { depth: null }) || config, - }; - ``` -- Then run storybook: - ```sh - yarn storybook --debug-webpack - ``` - -The console should log the entire config, for you to inspect. - -## Examples - -The value should export a `function`, it's first argument, is the config that storybook would use, if you were to not customize it. The second argument is an options object from storybook, this will have information about where config came from, whether we're in production of development mode etc. - -For example, here's a `.storybook/main.js` to add [Sass](https://sass-lang.com/) support: - -```js -const path = require('path'); - -// Export a function. Accept the base config as the only param. -module.exports = { - webpackFinal: async (config, { configType }) => { - // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' - // You can change the configuration based on that. - // 'PRODUCTION' is used when building the static version of storybook. - - // Make whatever fine-grained changes you need - config.module.rules.push({ - test: /\.scss$/, - use: ['style-loader', 'css-loader', 'sass-loader'], - include: path.resolve(__dirname, '../'), - }); - - // Return the altered config - return config; - }, -}; -``` - -Storybook uses the config returned from the above function to render your components in Storybook's "preview" iframe. Note that Storybook has a completely separate webpack config for its own UI (also referred to as the "manager"), so the customizations you make only applies to the rendering of your stories, i.e. you can completely replace `config.module.rules` if you want. - -Nevertheless, edit `config` with care. Make sure to preserve the following config options: - -- entry -- output - -Furthermore, `config` requires the `HtmlWebpackplugin` to generate the preview page, so rather than overwriting `config.plugins` you should probably append to it (or overwrite it with care), see [Issue #6020](https://github.com/storybookjs/storybook/issues/6020) for examples: - -```js -module.exports = { - webpackFinal: (config) => { - config.plugins.push(...); - return config; - }, -} -``` - -Finally, if your custom webpack config uses a loader that does not explicitly include specific file extensions via the `test` property, it is necessary to `exclude` the `.ejs` file extension from that loader. - -If you're using a non-standard Storybook config directory, you should put `main.js` there instead of `.storybook` and update the `include` path to make sure that it resolves to your project root. - -## Using Your Existing Config - -If you have an existing webpack config for your project and want to reuse this app's configuration, you can import your main webpack config into Storybook's `.storybook/main.js` and merge the 2 configs: - -**Example** -_replacing the loaders from storybook with the loaders from your app's `webpack.config.js`_ - -```js -const path = require('path'); - -// your app's webpack.config.js -const custom = require('../webpack.config.js'); - -module.exports = { - webpackFinal: (config) => { - return { ...config, module: { ...config.module, rules: custom.module.rules } }; - }, -}; -``` diff --git a/docs/src/pages/configurations/default-config/index.md b/docs/src/pages/configurations/default-config/index.md deleted file mode 100644 index 79daed6ed14..00000000000 --- a/docs/src/pages/configurations/default-config/index.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -id: 'default-config' -title: 'Default Config' ---- - -Let's learn about the default config that comes with Storybook. - -## Babel - -We use Babel for JavaScript(ES6) transpiling. -Here are some key features of Storybook's Babel configurations. - -### ES2016+ Support - -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/common/babel.js) to learn more about these plugins. - -### .babelrc support - -If your project has a `.babelrc` file, we'll use that instead of the default config file. -So, you could use any babel plugins or presets that you have used in your project with Storybook. - -## Webpack - -We use Webpack to serve and load JavaScript modules for the web. - -The webpack config [is configurable](/configurations/custom-webpack-config/), 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. - -> We're trying to make storybook more zero-config over time, **help to hook into the config of generators is very welcome**. - -
- This is what the config for storybook looks like when using CRA in dev-mode: - - ```js - { - mode: 'development', - bail: false, - devtool: '#cheap-module-source-map', - entry: [ - '@storybook/core/dist/server/common/polyfills.js', - '@storybook/core/dist/server/preview/globals.js', - '/preview.js', - 'webpack-hot-middleware/client.js?reload=true', - ], - output: { - path: './', - filename: '[name].[hash].bundle.js', - publicPath: '', - }, - plugins: [ - HtmlWebpackPlugin { - options: { - template: '@storybook/core/dist/server/templates/index.ejs', - templateContent: false, - templateParameters: [Function: templateParameters], - filename: 'iframe.html', - hash: false, - inject: false, - compile: true, - favicon: false, - minify: undefined, - cache: true, - showErrors: true, - chunks: 'all', - excludeChunks: [], - chunksSortMode: 'none', - meta: {}, - title: 'Webpack App', - xhtml: false, - alwaysWriteToDisk: true, - }, - }, - DefinePlugin { - definitions: { - 'process.env': { - NODE_ENV: '"development"', - NODE_PATH: '""', - PUBLIC_URL: '"."', - '' - '' - }, - }, - }, - WatchMissingNodeModulesPlugin { - nodeModulesPath: './node_modules', - }, - HotModuleReplacementPlugin {}, - CaseSensitivePathsPlugin {}, - ProgressPlugin {}, - DefinePlugin { - definitions: { - '' - '' - }, - }, - ], - module: { - rules: [ - { test: /\.(mjs|jsx?)$/, - use: [ - { loader: 'babel-loader', options: - { cacheDirectory: './node_modules/.cache/storybook', - presets: [ - [ './node_modules/@babel/preset-env/lib/index.js', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' } ], - './node_modules/@babel/preset-react/lib/index.js', - './node_modules/@babel/preset-flow/lib/index.js', - ], - plugins: [ - './node_modules/@babel/plugin-proposal-object-rest-spread/lib/index.js', - './node_modules/@babel/plugin-proposal-class-properties/lib/index.js', - './node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js', - [ './node_modules/babel-plugin-emotion/dist/babel-plugin-emotion.cjs.js', { sourceMap: true, autoLabel: true } ], - './node_modules/babel-plugin-macros/dist/index.js', - './node_modules/babel-plugin-add-react-displayname/index.js', - [ './node_modules/babel-plugin-react-docgen/lib/index.js', { DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES' } ], - ], - }, - }, - ], - include: [ './' ], - exclude: [ './node_modules' ], - }, - { test: /\.md$/, - use: [ - { loader: './node_modules/raw-loader/index.js' }, - ], - }, - { test: /\.css$/, - use: [ - './node_modules/style-loader/index.js', - { loader: './node_modules/css-loader/dist/cjs.js', options: { importLoaders: 1 } }, - { loader: './node_modules/postcss-loader/src/index.js', options: { ident: 'postcss', postcss: {}, plugins: [Function: plugins] } }, - ], - }, - { test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/, - loader: './node_modules/file-loader/dist/cjs.js', - query: { name: 'static/media/[name].[hash:8].[ext]' }, - }, - { test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/, - loader: './node_modules/url-loader/dist/cjs.js', - query: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]' }, - }, - ], - }, - resolve: { - extensions: [ '.mjs', '.js', '.jsx', '.json' ], - modules: [ 'node_modules' ], - mainFields: [ 'browser', 'main', 'module' ], - alias: { - 'core-js': './node_modules/core-js', - react: './node_modules/react', - 'react-dom': './node_modules/react-dom', - }, - }, - optimization: { - splitChunks: { chunks: 'all' }, - runtimeChunk: true, - minimizer: [ [Object] ], - }, - performance: { hints: false }, - } - ``` -
- - -### CSS Support - -You can import CSS files wherever you want, whether it's in the storybook config file, a UI component, or inside a story definition file. - -Basically, you can import CSS like this: - -```js -// from NPM modules -import 'bootstrap/dist/css/bootstrap.css'; - -// from local path -import './styles.css'; -``` - -> **Note:** with some frameworks/clis we inject plain CSS only. If you need a preprocessor like SASS, you need to [customize the webpack config](/configurations/custom-webpack-config/). -> -> **Warning:** storybooks for projects that use Angular CLI cannot import CSS by default. They must either [customize the webpack config](/configurations/custom-webpack-config/), or use the inline loader syntax: -> ```js -> import '!style-loader!css-loader!./styles.css'; -> ``` - -### Image and Static File Support - -You can also import images and media files directly via JavaScript. -This helps you to write stories with media files. This is how to do it: - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import imageFile from './static/image.png'; - -storiesOf('', module) - .add('with an image', () => ( - covfefe - )); -``` - -When you are building a storybook, we'll also export the imported image. -So, this is a good approach to loading all of your static content. - -> **Alternative:** storybook also has a way to mention static directories via the `-s` option of the `start-storybook` and `build-storybook` commands. [read more](/configurations/serving-static-files/) - -### JSON Loader - -You can import `.json` files, as you do with Node.js. -This will also allow you to use NPM projects that import `.json` files inside them. - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react'; - -import data from './data.json'; - -storiesOf('Component', module) - .add('with data', () => ( -
{JSON.stringify(data, null, 2)}
- )); -``` - -## NPM Modules - -You can use any of the NPM modules installed on your project. -You can import and use them. diff --git a/docs/src/pages/configurations/env-vars/index.md b/docs/src/pages/configurations/env-vars/index.md deleted file mode 100644 index 056fefd0e6c..00000000000 --- a/docs/src/pages/configurations/env-vars/index.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -id: 'env-vars' -title: 'Using Environment Variables' ---- - -Sometimes, we may use configuration items inside Storybook. It might be a theme color, some client secret, or a JSON string. So, we usually tend to hard code them. - -But you can expose those configurations via "environment variables." For that, you need to prefix your environment variables with `STORYBOOK_` prefix. - -For an example, let's expose two environment variables like this: - -```sh -STORYBOOK_THEME=red STORYBOOK_DATA_KEY=12345 npm run storybook -``` - -Then we can access these environment variables anywhere inside our JS code like below: - -```js -const out = console; - -out.log(process.env.STORYBOOK_THEME); -out.log(process.env.STORYBOOK_DATA_KEY); -``` - -> Even though we can access these env variables anywhere in the client side JS code, it's better to use them only inside stories and inside the main Storybook config file. - -## Usage in custom head/body - -These environment variables can be used in [custom head](/configurations/add-custom-head-tags) and [custom body](/configurations/add-custom-body) files. - -Storybook will replace percent-delimited variable names with their values; e.g. `%STORYBOOK_THEME%` will become `red`. - -> If using the environment variables as attributes or values in JavaScript, you may need to add quotes, as the value will be inserted directly. e.g. `` - -## Build time environment variables - -You can also pass these environment variables when you are [building your storybook](/basics/exporting-storybook) with `build-storybook`. - -Then they'll be hard coded to the static version of your Storybook. - -## NODE_ENV env variable - -In addition to the above prefixed environment variables, you can also pass the NODE_ENV variable to Storybook. But, you normally don't need to do that since we set a reasonable default value for it. - -- When running `npm run storybook`, we set NODE_ENV to 'development' -- When building storybook, we set NODE_ENV to 'production' diff --git a/docs/src/pages/configurations/options-parameter/index.md b/docs/src/pages/configurations/options-parameter/index.md deleted file mode 100644 index f137efe8d3d..00000000000 --- a/docs/src/pages/configurations/options-parameter/index.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -id: 'options-parameter' -title: 'Options Parameter' ---- - -Storybook UI is configurable using an options API that allows you to tweak its appearance globally and for each story. - -### Global options - -Import and use `setConfig` in your `manager.js` file. - -```js -import { addons } from '@storybook/addons'; - -addons.setConfig({ - /** - * show story component as full screen - * @type {Boolean} - */ - isFullscreen: false, - - /** - * display panel that shows a list of stories - * @type {Boolean} - */ - showNav: true, - - /** - * display panel that shows addon configurations - * @type {Boolean} - */ - showPanel: true, - - /** - * where to show the addon panel - * @type {('bottom'|'right')} - */ - panelPosition: 'bottom', - - /** - * sidebar tree animations - * @type {Boolean} - */ - sidebarAnimations: true, - - /** - * enable/disable shortcuts - * @type {Boolean} - */ - enableShortcuts: true, - - /** - * show/hide tool bar - * @type {Boolean} - */ - isToolshown: true, - - /** - * theme storybook, see link below - */ - theme: undefined, - - /** - * id to select an addon panel - * @type {String} - */ - selectedPanel: undefined, - - /** - * Select the default active tab on Mobile. - * 'sidebar' | 'canvas' | 'addons' - * @type {('sidebar'|'canvas'|'addons')} - */ - initialActive: 'sidebar', -}); -``` - -### showRoots - -Export `parameters` with the `options` key in your `preview.js` file. - -```js -export const parameters = { - options: { - /** - * display the top-level grouping as a "root" in the sidebar - * @type {Boolean} - */ - showRoots: false, - }, -}); -``` - -### Sorting stories - -By default, stories are sorted in the order in which they were imported. This can be overridden by adding `storySort` to the `options` parameters in your `preview.js` file. - -The most powerful method of sorting is to provide a function to `storySort`. Any custom sorting can be achieved with this method. - -```js -export const parameters = { - options: { - storySort: (a, b) => - a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }), - }, -}; -``` - -The `storySort` can also accept a configuration object. - -```js - -export parameters = { - options: { - storySort: { - method: 'alphabetical', // Optional, defaults to 'configure'. - order: ['Intro', 'Components'], // Optional, defaults to []. - locales: 'en-US', // Optional, defaults to system locale. - }, - }, -}; -``` - -To sort your stories alphabetically, set `method` to `'alphabetical'` and optionally set the `locales` string. To sort your stories using a custom list, use the `order` array; stories that don't match an item in the `order` list will appear after the items in the list. - -The `order` array can accept a nested array in order to sort 2nd-level story kinds. For example: - -```js -export parameters = { - options: { - storySort: { - order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components'], - }, - }, -}; -``` - -Which would result in this story ordering: - -1. `Intro` and then `Intro/*` stories -2. `Pages` story -3. `Pages/Home` and `Pages/Home/*` stories -4. `Pages/Login` and `Pages/Login/*` stories -5. `Pages/Admin` and `Pages/Admin/*` stories -6. `Pages/*` stories -7. `Components` and `Components/*` stories -8. All other stories - -Note that the `order` option is independent of the `method` option; stories are sorted first by the `order` array and then by either the `method: 'alphabetical'` or the default `configure()` import order. - -### Theming - -For more information on configuring the `theme`, see [theming](../theming/). - -### Per-story options - -The options-addon accepts story parameters on the `options` key: - -```js -import MyComponent from './my-component'; - -export default { - title: 'Options', - parameters: { - options: { selectedPanel: 'storybook/a11y/panel' }, - }, -}; -``` diff --git a/docs/src/pages/configurations/overview/index.md b/docs/src/pages/configurations/overview/index.md deleted file mode 100644 index 5c9de8814fb..00000000000 --- a/docs/src/pages/configurations/overview/index.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -id: 'overview' -title: 'Configuration overview' ---- - -For CLI options see: [here](../cli-options). - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -## Main configuration - -Storybook has a few files it uses for configuration, and they are grouped together into a directory (default: `.storybook`). - -The most important file is the `main.js` file. This is where general config is declared. - -Here's a minimal example of that file: - -```js -module.exports = { - stories: ['../src/components/**/*.stories.js'], - addons: [ - '@storybook/addon-essentials', - ], -}; -``` - -`stories` is a list of [glob](https://www.npmjs.com/package/glob) patterns that tells where your stories are located, relative to the configuration file. - -`logLevel` is a setting that tells storybook to only log to a specific level. Options are: `trace`, `debug`, `info`, `warn`, `error` and `silent`. - -The `addons` field can refer to traditional [addons](../../addons/introduction), but it can also include [presets](/docs/presets/introduction/), which are able to extend the config further. - -### `main.js` is a Preset -The `main.js` file is actually a preset! So if you know how to configure storybook, then you know how to write a preset, and vice-versa! -So the `main.js` API is equal to [that of presets](../../presets/writing-presets/#presets-api). - - -## Manager & preview - -Storybook works by being split into 2 applications ("manager" and "preview"), which communicate with each other over a postMessage channel. - -The preview application is essentially just your stories with a framework-agnostic 'router'. It renders whichever story the manager application tells it to render. - -The manager application renders the UI of [addons](../../addons/introduction), the navigator and [toolbar](../../basics/toolbar-guide/). - -There are two extra config files, if you need to configure the runtime of Manager or Preview. - -In `preview.js` you can add global [decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters): - -```js -// preview.js -import { addDecorator } from '@storybook/svelte'; -import { withKnobs } from '@storybook/addon-knobs'; - -addDecorator(withKnobs); -``` - -In `manager.js` you can add [UI options](../options-parameter/#global-options). - -```js -// manager.js -import { themes } from '@storybook/theming/create'; -import { addons } from '@storybook/addons'; - -addons.setConfig({ - theme: themes.dark, -}); -``` - -## webpack - -For how to customize webpack, [view the customize webpack section](../custom-webpack-config/) diff --git a/docs/src/pages/configurations/serving-static-files/index.md b/docs/src/pages/configurations/serving-static-files/index.md deleted file mode 100644 index 881a5bb538f..00000000000 --- a/docs/src/pages/configurations/serving-static-files/index.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -id: 'serving-static-files' -title: 'Serving Static Files' ---- - -It's often useful to load static files like images and videos when creating components and stories. - -Storybook provides two ways to do that. - -## 1. Via Imports - -You can import any media assets by importing (or requiring) them as shown below. - -```js -import React from 'react'; -import imageFile from './static/image.png'; - -export default { - title: 'img', -}; - -const image = { - src: imageFile, - alt: 'my image', -}; - -export const withAnImage = () => ( - {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. - -## 2. Via a Directory - -You can also configure a directory (or a list of directories) for searching static content when you are starting Storybook. You can do that with the -s flag. - -See the following npm script on how to use it: - -```json -{ - "scripts": { - "start-storybook": "start-storybook -s ./public -p 9001" - } -} -``` - -Here `./public` is our static directory. Now you can use static files in the public directory in your components or stories like this. - -```js -import React from 'react'; - -export default { - title: 'img', -}; - -// assume image.png is located in the "public" directory. -export const withAnImage = () => ( - my image -); -``` - -> You can also pass a list of directories separated by commas without spaces instead of a single directory. -> -> ```json -> { -> "scripts": { -> "start-storybook": "start-storybook -s ./public,./static -p 9001" -> } -> } -> ``` - -## 3. Via a CDN - -Upload your files to an online CDN and reference them. -In this example we're using a placeholder image service. - -```js -import React from 'react'; - -export default { - title: 'img', -}; - -// assume image.png is located in the "public" directory. -export const withAnImage = () => ( - 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, 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 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 deleted file mode 100644 index 4ed3ad2d176..00000000000 --- a/docs/src/pages/configurations/theming/index.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -id: 'theming' -title: 'Theming Storybook' ---- - -Storybook is theme-able! - -> Note that theming storybook changed recently, to be in `manager.js` (introduced in 5.3). But addon-docs is not yet compatible with this new way of configuring the theme. -> -> See here how to use theme storybook in a way that addon-docs will be themed as well: https://github.com/storybookjs/storybook/blob/master/addons/docs/docs/theming.md - -## Global theming - -It's possible to theme Storybook globally. - -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. - -Make sure you have installed `@storybook/addons` and `@storybook/theming` packages. -```sh -npm install @storybook/addons --save-dev -npm install @storybook/theming --save-dev -``` - -As an example, you can tell Storybook to use the "dark" theme by modifying `.storybook/manager.js`: - -```js -import { addons } from '@storybook/addons'; -import { themes } from '@storybook/theming'; - -addons.setConfig({ - theme: themes.dark, -}); -``` - -When setting a theme, set a full theme object. The theme is replaced, not combined. - -Read on for more on how to create your own theme. - -## Create a theme quickstart - -The easiest way to customize Storybook is to generate a new theme using the `create()` function from `storybook/theming`. This function includes shorthands for the most common theme variables. Here's how to use it: - -First create a new file in `.storybook` called `yourTheme.js`. - -Next paste the code below and tweak the variables. - -```ts -import { create } from '@storybook/theming/create'; - -export default create({ - base: 'light', - - colorPrimary: 'hotpink', - colorSecondary: 'deepskyblue', - - // UI - appBg: 'white', - appContentBg: 'silver', - appBorderColor: 'grey', - appBorderRadius: 4, - - // Typography - fontBase: '"Open Sans", sans-serif', - fontCode: 'monospace', - - // Text colors - textColor: 'black', - textInverseColor: 'rgba(255,255,255,0.9)', - - // Toolbar default and active colors - barTextColor: 'silver', - barSelectedColor: 'black', - barBg: 'hotpink', - - // Form colors - inputBg: 'white', - inputBorder: 'silver', - inputTextColor: 'black', - inputBorderRadius: 4, - - brandTitle: 'My custom storybook', - brandUrl: 'https://example.com', - brandImage: 'https://placehold.it/350x150', -}); -``` - -Finally, import your theme into `.storybook/manager.js` and add it to your Storybook parameters. - -```js -import { addons } from '@storybook/addons'; -import yourTheme from './yourTheme'; - -addons.setConfig({ - theme: yourTheme, -}); -``` - -The `storybook/theming` package is built using TypeScript, so this should help create a valid theme for typescript users. The types are part of the package itself. - -Many theme variables are optional, the `base` property is NOT. This is a perfectly valid theme: - -```ts -import { create } from '@storybook/theming/create'; - -export default create({ - base: 'light', - - brandTitle: 'My custom storybook', - brandUrl: 'https://example.com', - brandImage: 'https://placehold.it/350x150', -}); -``` - -## Addons and theme creation - -Some addons require specific theme variables that a Storybook user must add. If you share your theme with the community, make sure to support the official and other popular addons so your users have a consistent experience. - -For example, the popular Actions addon uses [react-inspector](https://github.com/xyc/react-inspector/blob/master/src/styles/themes/chromeLight.js) which has themes of its own. Supply additional theme variables to style it like so: - -```js -addonActionsTheme: { - ...chromeLight, - BASE_FONT_FAMILY: typography.fonts.mono, - BASE_BACKGROUND_COLOR: 'transparent', -} -``` - -### Using the theme for addon authors - -For a native Storybook experience, we encourage addon authors to reuse the theme variables above. The theming engine relies on [emotion](https://emotion.sh/), a CSS-in-JS library. - -```js -import { styled } from '@storybook/theming'; -``` - -Use the theme variables in object notation: - -```js -const Component = styled.div(({ theme }) => ({ - background: theme.background.app, - width: 0, -})); -``` - -Or with template literals: - -```js -const Component = styled.div` - background: `${props => props.theme.background.app}` - width: 0; -`; -``` diff --git a/docs/src/pages/configurations/typescript-config/index.md b/docs/src/pages/configurations/typescript-config/index.md deleted file mode 100644 index b540714b76f..00000000000 --- a/docs/src/pages/configurations/typescript-config/index.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -id: 'typescript-config' -title: 'TypeScript Config' ---- - -Storybook has built-in Typescript support, so your Typescript project should work with zero configuration needed. - -- [Default configuration](#default-configuration) -- [Main.js configuration](#mainjs-configuration) -- [Manual configuration](#manual-configuration) -- [More Resources](#more-resources) - -## Default configuration - -The base Typescript configuration uses `babel-loader` for Typescript transpilation, and optionally `fork-ts-checker-webpack-plugin` for checking. - -Each framework uses the base configuration unless otherwise specified: - -- **Angular** ignores the base and uses `ts-loader` and `ngx-template-loader`. -- **Vue** ignores the uses `ts-loader` and applies it to both `.tsx?` and `.vue` files. -- **React** adds `react-docgen-typescript-loader` the base. - -## Main.js configuration - -To make it easier to configure Typescript handling, we've added a new field, `typescript`, to [`main.js`](../overview/index.md). - -Here are the default values: - -```js -module.exports = { - typescript: { - check: false, - checkOptions: {}, - reactDocgen: 'react-docgen-typescript', - reactDocgenTypescriptOptions: { - shouldExtractLiteralValuesFromEnum: true, - propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), - }, - }, -}; -``` - -And here are the meaning of each field: - -| Field | Framework | Description | Type | -| -------------------------------- | --------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -| **check** | All | optionally run `fork-ts-checker-webpack-plugin` | `boolean` | -| **checkOptions** | All | Options to pass to `fork-ts-checker-webpack-plugin` if it's enabled | [See docs](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) | -| **reactDocgen** | React | which variant docgen processor to run | `'react-docgen-typescript' | 'react-docgen' | false` | -| **reactDocgenTypescriptOptions** | React | Options to pass to `react-docgen-typescript-loader` if `react-docgen-typescript` is enabled. | [See docs](https://github.com/strothj/react-docgen-typescript-loader) | - -## Manual configuration - -Manual configuration support will be added in a later pre-release. - -## More Resources - -- [Storybook, React, TypeScript and Jest](https://medium.com/@mtiller/storybook-react-typescript-and-jest-c9059ea06fa7) -- [React, Storybook & TypeScript](http://www.joshschreuder.me/react-storybooks-with-typescript/) diff --git a/docs/src/pages/examples/_examples.yml b/docs/src/pages/examples/_examples.yml deleted file mode 100644 index 1f0a287cc8a..00000000000 --- a/docs/src/pages/examples/_examples.yml +++ /dev/null @@ -1,206 +0,0 @@ -coursera: - thumbnail: coursera-ui.png - title: Coursera - description: Coursera UI component library - demo: https://building.coursera.org/coursera-ui/ -grommet: - thumbnail: grommet.png - title: Grommet - description: Styled components for Reactjs - demo: https://storybook.grommet.io/ - site: https://v2.grommet.io/ -wix: - thumbnail: wix.png - title: Wix Style React - description: Components that conform to Wix Style - source: https://github.com/wix/wix-style-react - demo: https://wix.github.io/wix-style-react/ - site: https://wix.com -carbon: - thumbnail: carbon.png - title: Carbon Components - description: IBM's Carbon Design System implemented in React. - source: https://github.com/carbon-design-system/carbon/tree/master/packages/react - demo: http://react.carbondesignsystem.com - site: http://carbondesignsystem.com -lonelyplanet: - thumbnail: lonelyplanet.jpg - title: Lonely Planet - description: All the tools you need to build the Lonely Planet UI experience. - source: https://github.com/lonelyplanet/backpack-ui - demo: https://lonelyplanet.github.io/backpack-ui/ - site: https://www.lonelyplanet.com/ -airbnb: - thumbnail: airbnb.jpg - title: Airbnb Dates - description: An internationalizable, mobile-friendly datepicker library for the web. - source: https://github.com/airbnb/react-dates - demo: http://airbnb.io/react-dates/ - site: http://airbnb.com -uber: - thumbnail: uber.png - title: Uber React-Vis - description: A composable charting library. - source: https://github.com/uber/react-vis - demo: https://uber.github.io/react-vis/website/dist/storybook/index.html - site: https://uber.github.io/react-vis/ -buffer: - thumbnail: buffer.jpg - title: Buffer Components - description: A collection of Buffer UI React components. - source: https://github.com/bufferapp/buffer-components - demo: https://bufferapp.github.io/buffer-components/ - site: https://buffer.com -algolia: - thumbnail: algolia.jpg - title: Algolia InstantSearch - description: Lightning-fast, hyper-configurable search. - source: https://github.com/algolia/react-instantsearch/ - demo: https://community.algolia.com/react-instantsearch/storybook/ -artsy: - thumbnail: artsy.png - title: Artsy Force - description: Artsy's "Force" component library - demo: https://artsy.github.io/reaction/ - source: https://github.com/artsy/reaction-force - site: https://artsy.net -necolas: - thumbnail: reactnativeweb.jpg - 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/docs/ -griddle: - thumbnail: griddle.jpg - title: Griddle - description: An ultra customizable datagrid component for React. - source: https://github.com/GriddleGriddle/Griddle - site: https://griddlegriddle.github.io/Griddle/ -tachyons: - thumbnail: tachyons.png - title: Tachyons Components - description: A searchable directory of Tachyons Components - source: https://github.com/blairanderson/tachyonstemplates - demo: https://www.tachyonstemplates.com/components/ -appbase: - thumbnail: appbase.jpg - title: Appbase Maps - description: A storybook playground for ReactiveMaps and ReactiveSearch. - source: https://github.com/appbaseio/playground - demo: https://opensource.appbase.io/playground/ - site: https://appbase.io -quran: - thumbnail: quran.jpg - title: Quran.com - description: Component library for Quran.com, Quranicaudio.com and Salah.com. - source: https://github.com/quran/common-components - demo: https://quran.github.io/common-components/ - site: https://quran.com/ -terraeclipse: - thumbnail: terraeclipse.jpg - title: TerraEclipse - description: React components for TerraEclipse, a political technology and strategy firm. - source: https://github.com/TerraEclipse/react-stack - demo: https://terraeclipse.github.io/react-stack/ -timeline: - thumbnail: timeline.jpg - title: React Event Timeline - description: Event timeline component. - source: https://github.com/rcdexta/react-event-timeline - demo: https://rcdexta.github.io/react-event-timeline/ -semanticui: - thumbnail: semanticui.png - title: Semantic-UI - description: Storybook for Semantic-UI React components - source: https://github.com/white-rabbit-japan/Semantic-UI-React-Storybook - demo: https://white-rabbit-japan.github.io/Semantic-UI-React-Storybook/ -hackoregon: - thumbnail: hackoregon.png - title: Hack Oregon - description: Official component library and storybook for Hack Oregon. - source: https://github.com/hackoregon/component-library - demo: https://hackoregon.github.io/component-library/ -todomvc: - thumbnail: todomvc.png - title: TodoMVC with Specs - description: Todo app Storybook with built-in unit tests. - source: https://github.com/thorjarhun/react-storybook-todolist - demo: https://thorjarhun.github.io/react-storybook-todolist/ -react-svg-pan-zoom: - thumbnail: react-svg-pan-zoom.png - title: React SVG Pan Zoom - description: A React component that adds pan and zoom features to SVG - source: https://github.com/chrvadala/react-svg-pan-zoom - demo: https://chrvadala.github.io/react-svg-pan-zoom/ -fyndiq-ui: - thumbnail: fyndiq.jpg - title: Fyndiq - description: Fyndiq UI Component library - source: https://github.com/fyndiq/fyndiq-ui - demo: https://fyndiq.github.io/fyndiq-ui/ -gumgum: - thumbnail: gumgum.png - title: GumGum - description: GumGum (Computer Vision Company) Component library - demo: https://storybook.gumgum.com - source: https://github.com/gumgum/gumdrops - site: https://gumgum.com -lucid-ui: - thumbnail: lucid-ui.png - title: Lucid UI - description: Component and charts library by AppNexus - demo: https://appnexus.github.io/lucid/ - source: https://github.com/appnexus/lucid - site: https://appnexus.com/ -trunx: - thumbnail: trunks.png - title: Trunx - description: Super Saiyan react components, son of awesome Bulma - demo: https://g14n.info/trunx - source: https://github.com/fibo/trunx - site: https://github.com/fibo/trunx -auth0: - thumbnail: cosmos.png - title: Cosmos - description: A Design System For Auth0 Products - demo: https://auth0-cosmos.now.sh/sandbox/ - source: https://github.com/auth0/cosmos - site: https://auth0-cosmos.now.sh/ -mockingbot: - thumbnail: mockingbot.png - title: MockingBot - description: MockingBot UI component library(ibot) - source: https://github.com/mockingbot/ibot - demo: https://ibot.guide -vanilla: - thumbnail: vanilla.png - title: Vanilla React - description: A simple implementation of Vanilla Framework using React - source: https://github.com/vanilla-framework/vanilla-framework-react - site: https://vanillaframework.io/ -govuk: - thumbnail: gov-uk.png - title: GOV.UK react - description: An implementation of the GOV.UK Design System in React using CSSinJS - demo: https://govuk-react.github.io/govuk-react/ - source: https://github.com/govuk-react/govuk-react - site: https://design-system.service.gov.uk/ -reactPakistan: - thumbnail: react-pakistan.jpg - title: React Pakistan - description: A series of reuseable React Commons, UI/UX components, icons, logos and more by React Pakistan. - demo: https://taimoormk.github.io/react-commons-collection/?path=/docs/react-pakistan-intro--page - site: https://www.npmjs.com/package/@react-pakistan/react-commons-collection -bbc: - thumbnail: bbc.jpg - title: BBC Psammead - description: React component library for BBC World Service and more - demo: https://bbc.github.io/psammead/ - source: https://github.com/bbc/psammead -pxblue: - thumbnail: pxblue.png - title: Power Xpert Blue - description: A design system for Eaton applications, including component libraries for React, Angular, and React Native - demo: https://pxblue-components.github.io/ - source: https://github.com/pxblue/react-component-library/tree/master/demos/storybook - site: https://pxblue.github.io diff --git a/docs/src/pages/examples/index.jsx b/docs/src/pages/examples/index.jsx deleted file mode 100644 index 921d4ff23fc..00000000000 --- a/docs/src/pages/examples/index.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import Examples from '../../components/Grid/Examples'; -import examples from './_examples.yml'; - -const examplesArray = Object.values(examples).map((example) => ({ - ...example, - // eslint-disable-next-line import/no-dynamic-require, global-require - thumbnailSrc: require(`./thumbnails/${example.thumbnail}`), -})); - -export default () => ; diff --git a/docs/src/pages/examples/thumbnails/airbnb.jpg b/docs/src/pages/examples/thumbnails/airbnb.jpg deleted file mode 100644 index 035c3871e56..00000000000 Binary files a/docs/src/pages/examples/thumbnails/airbnb.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/algolia.jpg b/docs/src/pages/examples/thumbnails/algolia.jpg deleted file mode 100644 index 5d27582a9ce..00000000000 Binary files a/docs/src/pages/examples/thumbnails/algolia.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/appbase.jpg b/docs/src/pages/examples/thumbnails/appbase.jpg deleted file mode 100644 index 8c32fa1327e..00000000000 Binary files a/docs/src/pages/examples/thumbnails/appbase.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/artsy.png b/docs/src/pages/examples/thumbnails/artsy.png deleted file mode 100644 index 650a9618090..00000000000 Binary files a/docs/src/pages/examples/thumbnails/artsy.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/atlassian.png b/docs/src/pages/examples/thumbnails/atlassian.png deleted file mode 100644 index 58f440ee9d1..00000000000 Binary files a/docs/src/pages/examples/thumbnails/atlassian.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/bbc.jpg b/docs/src/pages/examples/thumbnails/bbc.jpg deleted file mode 100644 index 49e1a72d0c3..00000000000 Binary files a/docs/src/pages/examples/thumbnails/bbc.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/buffer.jpg b/docs/src/pages/examples/thumbnails/buffer.jpg deleted file mode 100644 index 28f847feaac..00000000000 Binary files a/docs/src/pages/examples/thumbnails/buffer.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/carbon.png b/docs/src/pages/examples/thumbnails/carbon.png deleted file mode 100644 index 74582e2073c..00000000000 Binary files a/docs/src/pages/examples/thumbnails/carbon.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/cosmos.png b/docs/src/pages/examples/thumbnails/cosmos.png deleted file mode 100644 index 916dd0c2aaf..00000000000 Binary files a/docs/src/pages/examples/thumbnails/cosmos.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/coursera-ui.png b/docs/src/pages/examples/thumbnails/coursera-ui.png deleted file mode 100644 index 58da6e05ff6..00000000000 Binary files a/docs/src/pages/examples/thumbnails/coursera-ui.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/fyndiq.jpg b/docs/src/pages/examples/thumbnails/fyndiq.jpg deleted file mode 100644 index fdd786c1c35..00000000000 Binary files a/docs/src/pages/examples/thumbnails/fyndiq.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/gov-uk.png b/docs/src/pages/examples/thumbnails/gov-uk.png deleted file mode 100644 index 5356b39fb67..00000000000 Binary files a/docs/src/pages/examples/thumbnails/gov-uk.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/griddle.jpg b/docs/src/pages/examples/thumbnails/griddle.jpg deleted file mode 100644 index 9cd9118e774..00000000000 Binary files a/docs/src/pages/examples/thumbnails/griddle.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/grommet.png b/docs/src/pages/examples/thumbnails/grommet.png deleted file mode 100644 index d04e639d137..00000000000 Binary files a/docs/src/pages/examples/thumbnails/grommet.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/gumgum.png b/docs/src/pages/examples/thumbnails/gumgum.png deleted file mode 100644 index e643bbe100d..00000000000 Binary files a/docs/src/pages/examples/thumbnails/gumgum.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/hackoregon.png b/docs/src/pages/examples/thumbnails/hackoregon.png deleted file mode 100644 index 9699e42691a..00000000000 Binary files a/docs/src/pages/examples/thumbnails/hackoregon.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/lonelyplanet.jpg b/docs/src/pages/examples/thumbnails/lonelyplanet.jpg deleted file mode 100644 index 3846027e9e2..00000000000 Binary files a/docs/src/pages/examples/thumbnails/lonelyplanet.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/lucid-ui.png b/docs/src/pages/examples/thumbnails/lucid-ui.png deleted file mode 100644 index ba6f4fd8a73..00000000000 Binary files a/docs/src/pages/examples/thumbnails/lucid-ui.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/mockingbot.png b/docs/src/pages/examples/thumbnails/mockingbot.png deleted file mode 100644 index fb7c3fff1fd..00000000000 Binary files a/docs/src/pages/examples/thumbnails/mockingbot.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/pxblue.png b/docs/src/pages/examples/thumbnails/pxblue.png deleted file mode 100644 index b539d8de3a1..00000000000 Binary files a/docs/src/pages/examples/thumbnails/pxblue.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/quran.jpg b/docs/src/pages/examples/thumbnails/quran.jpg deleted file mode 100644 index 878def21503..00000000000 Binary files a/docs/src/pages/examples/thumbnails/quran.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/react-pakistan.jpg b/docs/src/pages/examples/thumbnails/react-pakistan.jpg deleted file mode 100644 index 427ff8aed99..00000000000 Binary files a/docs/src/pages/examples/thumbnails/react-pakistan.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/react-svg-pan-zoom.png b/docs/src/pages/examples/thumbnails/react-svg-pan-zoom.png deleted file mode 100644 index 4f7ede6646f..00000000000 Binary files a/docs/src/pages/examples/thumbnails/react-svg-pan-zoom.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/reactnativeweb.jpg b/docs/src/pages/examples/thumbnails/reactnativeweb.jpg deleted file mode 100644 index 54f792f3809..00000000000 Binary files a/docs/src/pages/examples/thumbnails/reactnativeweb.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/rebass.png b/docs/src/pages/examples/thumbnails/rebass.png deleted file mode 100644 index c3427d78ab0..00000000000 Binary files a/docs/src/pages/examples/thumbnails/rebass.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/semanticui.png b/docs/src/pages/examples/thumbnails/semanticui.png deleted file mode 100644 index e7136e0e416..00000000000 Binary files a/docs/src/pages/examples/thumbnails/semanticui.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/tachyons.png b/docs/src/pages/examples/thumbnails/tachyons.png deleted file mode 100644 index 28186071933..00000000000 Binary files a/docs/src/pages/examples/thumbnails/tachyons.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/terraeclipse.jpg b/docs/src/pages/examples/thumbnails/terraeclipse.jpg deleted file mode 100644 index 1aa35cece6d..00000000000 Binary files a/docs/src/pages/examples/thumbnails/terraeclipse.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/timeline.jpg b/docs/src/pages/examples/thumbnails/timeline.jpg deleted file mode 100644 index a331a566648..00000000000 Binary files a/docs/src/pages/examples/thumbnails/timeline.jpg and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/todomvc.png b/docs/src/pages/examples/thumbnails/todomvc.png deleted file mode 100644 index b31ee96579d..00000000000 Binary files a/docs/src/pages/examples/thumbnails/todomvc.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/trunks.png b/docs/src/pages/examples/thumbnails/trunks.png deleted file mode 100644 index 1e18be11511..00000000000 Binary files a/docs/src/pages/examples/thumbnails/trunks.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/uber.png b/docs/src/pages/examples/thumbnails/uber.png deleted file mode 100644 index a6d6f83f4b7..00000000000 Binary files a/docs/src/pages/examples/thumbnails/uber.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/vanilla.png b/docs/src/pages/examples/thumbnails/vanilla.png deleted file mode 100644 index 676051374cb..00000000000 Binary files a/docs/src/pages/examples/thumbnails/vanilla.png and /dev/null differ diff --git a/docs/src/pages/examples/thumbnails/wix.png b/docs/src/pages/examples/thumbnails/wix.png deleted file mode 100644 index 290893e783b..00000000000 Binary files a/docs/src/pages/examples/thumbnails/wix.png and /dev/null differ diff --git a/docs/src/pages/formats/component-story-format/index.md b/docs/src/pages/formats/component-story-format/index.md deleted file mode 100644 index 53954977083..00000000000 --- a/docs/src/pages/formats/component-story-format/index.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -id: 'component-story-format' -title: 'Component Story Format (CSF)' ---- - -Storybook's Component Story Format (CSF) is the recommended way to [write stories](../../basics/writing-stories/) since Storybook 5.2. [Read the announcement](https://medium.com/storybookjs/component-story-format-66f4c32366df) to learn more about how it came to be. - -In CSF, stories and component metadata are defined as ES Modules. Every component story file consists of a required **default export** and one or more **named exports**. - -CSF is supported in all frameworks except React Native, where you should use the [storiesOf API](../storiesof-api/) instead. - -## Default export - -The default export defines metadata about your component, including the `component` itself, its `title` (where it will show up in the [navigation UI story hierarchy](../../basics/writing-stories/#story-hierarchy)), [decorators](../../basics/writing-stories/#decorators), and [parameters](../../basics/writing-stories/#parameters). The `component` field is optional (but encouraged!), and is used by addons for automatic prop table generation and display of other component metadata. `title` should be unique, i.e. not re-used across files. - -```js -import MyComponent from './MyComponent'; - -export default { - title: 'Path/To/MyComponent', - component: MyComponent, - decorators: [ ... ], - parameters: { ... } -} -``` - -For more examples, see [writing stories](../../basics/writing-stories/). - -## Named story exports - -With CSF, every named export in the file represents a story function by default. - -```jsx -import MyComponent from './MyComponent'; - -export default { ... } - -export const Basic = () => ; -export const WithProp = () => ; -``` - -The exported identifiers will be converted to "start case" using Lodash's [startCase](https://lodash.com/docs/#startCase) function. For example: - -``` -name -> 'Name' -someName -> 'Some Name' -someNAME -> 'Some NAME' -some_custom_NAME -> 'Some Custom NAME' -someName1234 -> 'Some Name 1234' -someName1_2_3_4 -> 'Some Name 1 2 3 4' -``` - -It's recommended to start export names with a capital letter. - -Story functions can be annotated with a few different fields to define story-level [decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters), and also to define the `storyName` of the story. - -The `storyName` is useful if you want to use names with special characters, names that correspond to restricted keywords in Javascript, or names that collide with other variables in the file. If it's not specified, the export name will be used instead. - -```jsx -export const Simple = () => ; - -Simple.storyName = 'So simple!'; -Simple.decorators = [ ... ]; -Simple.parameters = { ... }; -``` - -## Args story inputs - -Starting in SB 6.0, stories accept named inputs called Args. Args are dynamic data that are provided (and possibly updated by) Storybook and its addons. - -Consider Storybook’s ["hello world" example](https://storybook.js.org/docs/basics/writing-stories/#basic-story) of a text button that logs its click events: - -```js -import { action } from '@storybook/addon-actions'; -import { Button } from './Button'; - -export default { title: 'Button', component: Button }; -export const Text = () => ) - .add('with some emoji', () => ( - - )); -``` - -The string argument to `storiesOf` is the component title. If you pass a string like `'Widgets|Button/Button'` it can also be used to position your component's story within [Storybook's story hierarchy](../../basics/writing-stories/#story-hierarchy). - -Each `.add` call takes a story name, a story function that returns a renderable object (JSX in the case of React), and optionally some parameters, which are described below. - -## Decorators and parameters - -[Decorators](../../basics/writing-stories/#decorators) and [parameters](../../basics/writing-stories/#parameters) can be specified globally, at the component level, or locally at the story level. - -Global decorators and parameters are specified in the Storybook config: - -```js -addDecorator(storyFn => {storyFn()}); -addParameters({ foo: 1 }); -``` - -Component-level decorators and parameters are supported as chained API calls: - -```js -storiesOf('Button', module) - .addDecorator(withKnobs) - .addParameters({ notes: someNotes }); -``` - -Story-level parameters are provided as a third argument to `.add`: - -```js -storiesOf('Button', module).add( - 'with text', - () => , - { notes: someNotes } -); -``` - -And finally, story-level decorators are provided via parameters: - -```js -storiesOf('Button', module).add( - 'with text', - () => , - { decorators: [withKnobs] } -); -``` - -## Component Story Format migration - -To make it easier to adopt the new [Component Story Format (CSF)](../component-story-format/), we've created an automatic migration tool to transform `storiesOf` API to Module format. - -```sh -sb migrate storiesof-to-csf --glob=src/**/*.stories.js -``` - -For more information, see the CLI's [Codemod README](https://github.com/storybookjs/storybook/tree/next/lib/codemod). diff --git a/docs/src/pages/guides/guide-angular/index.md b/docs/src/pages/guides/guide-angular/index.md deleted file mode 100644 index b008f65556a..00000000000 --- a/docs/src/pages/guides/guide-angular/index.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -id: 'guide-angular' -title: 'Storybook for Angular' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using angular, you could try forcing it to use angular: - -```sh -npx -p @storybook/cli sb init --type angular -``` - -## Manual setup - -If you want to set up Storybook manually for your Angular project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/angular - -Add `@storybook/angular` to your project. To do that, run: - -```sh -npm install @storybook/angular --save-dev -``` - -### Add @babel/core, and babel-loader - -Make sure that you have `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -That will load all the stories underneath your `../src` directory that match the pattern `*.stories.@(ts|js)`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. - -## Step 4: Storybook TypeScript configuration - -`@storybook/angular` is using [ForkTsCheckerWebpackPlugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin) to boost the build performance. -This makes it necessary to create a `tsconfig.json` file at `.storybook/tsconfig.json` with the following content: - -```json -{ - "extends": "../tsconfig.json", - "exclude": [ - "../src/test.ts", - "../src/**/*.spec.ts", - "../projects/**/*.spec.ts" - ], - "include": [ - "../src/**/*", - "../projects/**/*" - ] -} -``` - -## Step 5: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```ts -import { Button } from '@storybook/angular/demo'; - -export default { title: 'My Button' } - -export const withText = () => ({ - component: Button, - props: { - text: 'Hello Button', - }, -}); - -export const withEmoji = () => ({ - component: Button, - props: { - text: '😀 😎 👍 💯', - }, -}); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-ember/index.md b/docs/src/pages/guides/guide-ember/index.md deleted file mode 100644 index 456c6a27770..00000000000 --- a/docs/src/pages/guides/guide-ember/index.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -id: 'guide-ember' -title: 'Storybook for Ember' ---- - -You may have tried to use our quick start guide to setup your project for Storybook. If you want to set up Storybook manually, this is the guide for you. - -> This will also help you to understand how Storybook works. - -## Starter Guide Ember - -Storybook has its own Webpack setup and a dev server. - -In this guide, we will set up Storybook for your Ember project. - -## Table of contents - -- [Add @storybook/ember](#add-storybookember) -- [Setup environment](#setup-environment) -- [Create the config file](#create-the-config-file) -- [Write your stories](#write-your-stories) -- [Run your Storybook](#run-your-storybook) - -## Add @storybook/ember - -First of all, you need to add `@storybook/ember` to your project. To do that, run: - -```sh -ember install @storybook/ember-cli-storybook -``` - -If you don't have `package.json` in your project, you'll need to init it first: - -```sh -npm init -``` - -Then add the following npm scripts to your `package.json` in order to start the storybook later in this guide: - -> In order for your storybook to run properly be sure to be either run `ember serve` or `ember build` before running any storybook commands. Running `ember serve` before storybook will enable live reloading. - -```json -{ - "scripts": { - "storybook": "ember serve & start-storybook -p 9001 -s dist", - "build-storybook": "ember build && build-storybook -s dist" - } -} -``` - -## Setup environment - -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 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../app/**/*.stories.@(ts|js)'], -}; -``` - -That will load all the stories underneath your `../app` 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. - -## Write your stories - -Now you can write some stories inside the `../stories/index.stories.js` file, like this: - -> It is important that you import the `hbs` function that is provided by a babel plugin in `@storybook/ember` - -```js -import { hbs } from 'ember-cli-htmlbars'; - -export default { title: 'Demo' }; - -export const heading = () => hbs`

Hello World

`; - -export const button = () => { - return { - template: hbs``, - context: { - onClick: (e) => console.log(e) - } - } -}; - -export const component = () => { - return { - template: hbs`{{foo-bar - click=onClick - }}`, - context: { - onClick: (e) => console.log(e) - } - } -}; -``` - -> If you are using an older version of Ember <= 3.1 please use this story style - -```js -import { compile } from 'ember-source/dist/ember-template-compiler'; - -export default { title: 'Demo' }; - -export const heading = () => compile(`

Hello World

`); - -export const button = () => { - return { - template: compile(``), - context: { - onClick: (e) => console.log(e) - } - } -}; - -export const component = () => { - return { - template: compile(`{{foo-bar - click=onClick - }}`), - context: { - onClick: (e) => console.log(e) - } - } -}; -``` - -A story is either: - -1. A single handlebars fragment generated using the `hbs` function -2. An object that contains template and context that will be bound to the resulting element - -> In order to get your storybook to get new changes made to the `foo-bar` or any other components that are defined in your Ember app you must run `ember serve` as a sidecar for the build files to get generated. - -## Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Now you can change components and write stories whenever you need to. diff --git a/docs/src/pages/guides/guide-html/index.md b/docs/src/pages/guides/guide-html/index.md deleted file mode 100644 index 8658356282a..00000000000 --- a/docs/src/pages/guides/guide-html/index.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -id: 'guide-html' -title: 'Storybook for HTML' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using html, you could try forcing it to use html: - -```sh -npx -p @storybook/cli sb init --type html -``` - -## Manual setup - -If you want to set up Storybook manually for your html project, this is the guide for you. - -## Step 1: Add dependencies - -### Init npm if necessary - -If you don't have `package.json` in your project, you'll need to init it first: - -```sh -npm init -``` - -### Add @storybook/html - -Add `@storybook/html` to your project. To do that, run: - -```sh -npm install @storybook/html --save-dev -``` - -### Add @babel/core and babel-loader - -Make sure that you have `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -That will load all the stories underneath your `../src` directory that match the pattern `*.stories.@(ts|js)`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -export default { title: 'Button' }; - -export const withText = () => ''; - -export const withEmoji = () => { - const button = document.createElement('button'); - button.innerText = '😀 😎 👍 💯'; - return button; -}; -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-marko/index.md b/docs/src/pages/guides/guide-marko/index.md deleted file mode 100644 index 5d0fcd92ec9..00000000000 --- a/docs/src/pages/guides/guide-marko/index.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -id: 'guide-marko' -title: 'Storybook for Marko' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using marko, you could try forcing it to use marko: - -```sh -npx -p @storybook/cli sb init --type marko -``` - -## Manual setup - -If you want to set up Storybook manually for your marko project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/marko - -Add `@storybook/marko` to your project. To do that, run: - -```sh -npm install @storybook/marko --save-dev -``` - -### Add marko, @babel/core and babel-loader - -Make sure that you have `marko`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import Button from '../components/button/index.marko'; - -export default { title: 'Button' } - -export const withText = () => ({ - component: Button, - input: { text 'some text' } -}); - -export const withEmoji = () => ({ - component: Button, - input: { text '😀 😎 👍 💯' } -}); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-mithril/index.md b/docs/src/pages/guides/guide-mithril/index.md deleted file mode 100644 index 4ae2ce77d0b..00000000000 --- a/docs/src/pages/guides/guide-mithril/index.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: 'guide-mithril' -title: 'Storybook for Mithril' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using mithril, you could try forcing it to use mithril: - -```sh -npx -p @storybook/cli sb init --type mithril -``` - -## Manual setup - -If you want to set up Storybook manually for your mithril project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/mithril - -Add `@storybook/mithril` to your project. To do that, run: - -```sh -npm install @storybook/mithril --save-dev -``` - -### Add mithril, @babel/core and babel-loader - -Make sure that you have `mithril`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install mithril --save -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -/** @jsx m */ - -import m from 'mithril'; -import { Button } from ''; - -export default { title: 'Button' } - -export const withText = () => ( - -); - -export const withEmoji = () => ( - -); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-preact/index.md b/docs/src/pages/guides/guide-preact/index.md deleted file mode 100644 index 4a920a01e5f..00000000000 --- a/docs/src/pages/guides/guide-preact/index.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -id: 'guide-preact' -title: 'Storybook for Preact' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using preact, you could try forcing it to use preact: - -```sh -npx -p @storybook/cli sb init --type preact -``` - -## Manual setup - -If you want to set up Storybook manually for your preact project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/preact - -Add `@storybook/preact` to your project. To do that, run: - -```sh -npm install @storybook/preact --save-dev -``` - -### Add preact, @babel/core and babel-loader - -Make sure that you have `preact`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install preact --save -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import { h } from 'preact'; -import { Button } from ''; - -export default { title: 'Button' } - -export const withText = () => ( - -); - -export const withEmoji = () => ( - -); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-rax/index.md b/docs/src/pages/guides/guide-rax/index.md deleted file mode 100644 index fce19b9c826..00000000000 --- a/docs/src/pages/guides/guide-rax/index.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -id: 'guide-rax' -title: 'Storybook for Rax' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using [Rax](https://github.com/alibaba/rax), you could try forcing it to use Rax: - -```sh -npx -p @storybook/cli sb init --type rax -``` - -## Manual setup - -If you want to set up Storybook manually for your Rax project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/rax - -Add `@storybook/rax` to your project. To do that, run: - -```sh -npm install @storybook/rax --save-dev -``` - -### Add rax, @babel/core and babel-loader - -Make sure that you have `rax`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install rax --save -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import { createElement } from 'rax'; -import Button from ''; - -export const { title: 'Button' } -export const withText = () => ; -export const withEmoji = () => ; -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-react-native/index.md b/docs/src/pages/guides/guide-react-native/index.md deleted file mode 100644 index 8491694b123..00000000000 --- a/docs/src/pages/guides/guide-react-native/index.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -id: 'guide-react-native' -title: 'Storybook for React Native' ---- - -This guide will help you to manually setup React Native Storybook and explain about addons and Storybook server. - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using react-native, you could try forcing it to use react-native: - -```sh -npx -p @storybook/cli sb init --type react_native -``` - -## Manual setup - -**1. Add `@storybook/react-native` to your project.** - -To use React Native Storybook you need to have it as a dependency in your project. To do that, run: - -```sh -npm i --save-dev @storybook/react-native -``` - -**2. Create the Storybook directory.** - -Create a new directory called `storybook` in your project root. - -**3. Create an entry file for Storybook.** - -Create an `index.js` file as given below. Do not forget to replace `%APP_NAME%` with your app name if you are not -using expo. - -`storybook/index.js` - -```js -import { AppRegistry } from 'react-native'; -import { getStorybookUI, configure } from '@storybook/react-native'; - -import './rn-addons'; - -// import stories -configure(() => { - require('./stories'); -}, module); - -// Refer to https://github.com/storybookjs/storybook/tree/master/app/react-native#start-command-parameters -// To find allowed options for getStorybookUI -const StorybookUIRoot = getStorybookUI({}); - -// If you are using React Native vanilla write your app name here. -// If you use Expo you can safely remove this line. -AppRegistry.registerComponent('%APP_NAME%', () => StorybookUIRoot); - -export default StorybookUIRoot; -``` - -**4. Create a file for on device addons** - -Create a file called `rn-addons.js` that you can use to include on device addons (more about them in addons section). You can see an example below. - -`storybook/rn-addons.js` - -```js -import '@storybook/addon-ondevice-knobs/register'; -import '@storybook/addon-ondevice-notes/register'; -... -``` - -**5. Display StorybookUI in your app.** - -Finally you need to expose StorybookUI somewhere in your app. -The easiest solution is to replace your app entry with: - -```js -export default from './storybook'; -``` - -If you cannot replace your entry point, make sure that the component exported from `./storybook` is displayed somewhere in your app. `StorybookUI` is a RN `View` component that can be embedded anywhere in your RN application, e.g. on a tab or within an admin screen. - ---- - -## Writing stories - -Now you can write some stories inside the `storybook/stories/index.js` file, like this: - -```js -import React from 'react'; -import { storiesOf } from '@storybook/react-native'; -import { View, Text } from 'react-native'; - -const style = { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', -}; - -const CenteredView = ({ children }) => {children}; - -storiesOf('CenteredView', module).add('default view', () => ( - - Hello Storybook - -)); -``` - -Refer to [Writing Stories](https://storybook.js.org/basics/writing-stories) for more information. - ---- - -## Addons - -Storybook supports addons. You can read more about them [here](https://storybook.js.org/addons/introduction) - -There is one big difference in React Native is that it has two types of addons: Addons that work in the browser and addons that work on the app itself (on device addons). - -## Browser addons - -Browser addons are default addons to storybook. You create a file called `addons.js` inside `storybook` directory and the addons will be automatically loaded inside your browser. - -`storybook/addons.js` - -```js -import '@storybook/addon-actions/register'; -import '@storybook/addon-knobs/register'; -``` - -## On device addons - -On device addons are addons that are displayed in your app in addons panel. To use them you have to create a file called `rn-addons.js` in `storybook` directory. Because React Native does not dynamically resolve imports, you will also have to manually import this file before the `getStorybookUI` call. - -Example: - -`storybook/rn-addons.js` - -```js -import '@storybook/addon-ondevice-knobs/register'; -import '@storybook/addon-ondevice-notes/register'; -... -``` - -`storybook/index.js` - -```js -import { getStorybookUI } from '@storybook/react-native'; - -... -import './rn-addons'; -... - -const StorybookUI = getStorybookUI(); -export default StorybookUI; -``` - -This step is done automatically when you install Storybook for the first time. - -## Compatibility - -Web and onDevice addon compatibility can be found [here](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md) - -## Performance of on device addons - -Because on device addons are inside the app, they are also rerendered on every change. Be aware that this can have performance implications for your app. - ---- - -## Storybook server - -Storybook RN server, `@storybook/react-native-server` is a separate package that provides a standalone server that the Storybook ondevice client can connect to. - -Running storybook server gives a few advantages over running on-device: - -**Websockets connection.** By using websockets connection you can create your own tools that integrate with your storybook app and control it from outside of your app. - -**IDE Plugins.** Running server allows you to control your storybook view from inside web page or your IDE. There is a plugin for [JetBrains IDEs](https://plugins.jetbrains.com/plugin/9910-storybook) and there is one for [VS Code](https://github.com/orta/vscode-react-native-storybooks). - -**Web addons.** There are Storybook addons that work with React Native but do not have on device implementations. - -To run Storybook server, you need to install a few dependencies and add a run script to your `package.json`. - -First: - -```sh -npm i --save-dev @storybook/react-native-server babel-loader -``` - -Then: - -```json -{ - "scripts": { - ... - "storybook": "(adb reverse tcp:7007 tcp:7007 || true) && start-storybook" - } -} -``` - -Finally: - -```sh -npm run storybook -``` - -**Note** You can change port to custom with `start-storybook -p=YOUR_PORT_NUMBER`, but then you also have to pass it as an option to `getStorybookUI({port: YOUR_PORT_NUMBER})`. If you are running on android, there might be need to run `adb reverse tcp:YOUR_PORT_NUMBER tcp:YOUR_PORT_NUMBER`. - -### Start App -To see your Storybook stories on the device, you should start your mobile app for the of your choice (typically ios or android). (Note that due to an implementation detail, your stories will only show up in the left pane of your browser window after your device has connected to this storybook server.) - -### Historical notes - -Some context may be useful if you've used older versions of Storybook for RN and find these instructions confusing. - -Prior to v4, `@storybook/react-native` included a built-in server and needed that server to function. In v4.x, the server became optional, but was still included in the package. Finally in v5, we've split out the server into its own package (as documented above), and this has a variety of maintenance and compatibility benefits. - -For more information, see the RN migration instructions for [v4](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#removed-rn-packager) and [v5](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-native-server). diff --git a/docs/src/pages/guides/guide-react/index.md b/docs/src/pages/guides/guide-react/index.md deleted file mode 100644 index abb6fce106d..00000000000 --- a/docs/src/pages/guides/guide-react/index.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -id: 'guide-react' -title: 'Storybook for React' ---- - -## Automatic setup - -Before trying the below commands, you should try the following command. In most cases, Storybook will detect that you're using `react` or `react-scripts`, and install the appropriate packages. - -```sh -npx -p @storybook/cli sb init -``` - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using React, you could try forcing it to use React: - -```sh -npx -p @storybook/cli sb init --type react -``` - -If you're using [Create React App](https://create-react-app.dev/) (or a fork of `react-scripts`), you should use this command instead: - -```sh -npx -p @storybook/cli sb init --type react_scripts -``` - -Note: You must have a `package.json` in your project or the above commands will fail. - -## Manual setup - -If you want to set up Storybook manually for your React project, this is the guide for you. - -### A note for Create React App users - -You can now use [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app) to configure Storybook on your behalf. This is installed by Storybook during automatic setup (Storybook 5.3 or newer). - -## Step 1: Add dependencies - -### Add @storybook/react - -Add `@storybook/react` to your project. To do that, run: - -```sh -npm install @storybook/react --save-dev -``` - -### Add react, react-dom, @babel/core, and babel-loader - -Make sure that you have `react`, `react-dom`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install react react-dom --save -npm install babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import React from 'react'; -import { Button } from '@storybook/react/demo'; - -export default { title: 'Button' }; - -export const withText = () => ; - -export const withEmoji = () => ( - -); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-riot/index.md b/docs/src/pages/guides/guide-riot/index.md deleted file mode 100644 index 400d72c4352..00000000000 --- a/docs/src/pages/guides/guide-riot/index.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -id: 'guide-riot' -title: 'Storybook for Riot' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using Riot, you could try forcing it to use riot: - -```sh -npx -p @storybook/cli sb init --type riot -``` - -## Manual setup - -If you want to set up Storybook manually for your Riot project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/riot - -Add `@storybook/riot` to your project. To do that, run: - -```sh -npm install @storybook/riot --save-dev -``` - -### Add riot, @babel/core, and babel-loader - -Make sure that you have `riot`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install riot babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Storybook TypeScript configuration - -`@storybook/riot` is using [ForkTsCheckerWebpackPlugin](https://github.com/Realytics/fork-ts-checker-webpack-plugin) to boost the build performance. -This makes it necessary to create a `tsconfig.json` file at `.storybook/tsconfig.json` with the following content: - -```json -{ - "extends": "../tsconfig.json", - "exclude": [ - "../src/test.ts", - "../src/**/*.spec.ts", - "../projects/**/*.spec.ts" - ], - "include": [ - "../src/**/*", - "../projects/**/*" - ] -} -``` - -## Step 5: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import { tag, mount } from '@storybook/riot'; -import SimpleTestRaw from './SimpleTest.txt'; //can be loaded as string if you prefer -import './AnotherTest.tag'; -//if you need to import .tag files as text, use the raw-loader instead of the riot-tag-loader - -export default { title: 'My Component' }; - -// the template is compiled below -export const builtWithTag = () => ( - tag('test', '
simple test ({ opts.value })
', '', '', () => {}) && - mount('test', { value: 'with a parameter' }) -); - -// tags[0] will be the parent tag, always -// you can leave out the root tag, if we find out that the new root tag -// is a built-in html tag, it will be wrapped -export const builtAsString = () => ({ - tags: ['
simple test
'] -}); - -// the component is a string, it will be instantiated without params -// e.g. -export const builtFromRawImport = () => SimpleTestRaw; - -// the comprehensive form is this one -// list all the possible tags (the root element is in the content) -// then scenario is compiled and executed -// WARN : the tag file root element must have exactly the same name (or else you will see nothing) -export const builtFromTagsAndScenario = () => ({ - tags: [{ content: SimpleTestRaw, boundAs: 'mustBeUniquePlease' }], - scenario: - '', -}); - -// the tag is already compiled before running the js -// the tag name 'anothertest' must match exactly the root tag inside the tag file -// must be lower case -export const builtFromThePrecompilation = () => mount('anothertest', {}); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -My Component - ├── Built With Tag - ├── Built As String - ├── Built From Raw Import - ├── Built From Tags And Scenario - └── Built From The Precompilation -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-svelte/index.md b/docs/src/pages/guides/guide-svelte/index.md deleted file mode 100644 index 010c2e66ea8..00000000000 --- a/docs/src/pages/guides/guide-svelte/index.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -id: 'guide-svelte' -title: 'Storybook for Svelte' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using svelte, you could try forcing it to use svelte: - -```sh -npx -p @storybook/cli sb init --type svelte -``` - -## Manual setup - -If you want to set up Storybook manually for your Svelte project, this is the guide for you. - -> It is very important to remember that Svelte components are precompiled from `.svelte` or `.html` files to vanilla javascript, so there is no 'runtime'. - -## Step 1: Add dependencies - -### Add @storybook/svelte - -Add `@storybook/svelte` to your project. To do that, run: - -```sh -npm install @storybook/svelte --save-dev -``` - -### @babel/core, and babel-loader - -Make sure that you have `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -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 npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import MyButton from '../components/MyButton.svelte'; - -export default { title: 'MyButton' } - -export const withText = () => ({ - Component: MyButton, - props: { - buttonText: 'some text', - }, -}); - -export const withEmoji = () => ({ - Component: MyButton, - props: { - buttonText: '😀 😎 👍 💯', - }, -}); -``` - -Svelte storybooks don't support using templates in a story yet. -Instead, you can create a `.svelte` file to compose components together, or to access all normal Svelte functionality, like slots. - -So you can create a story "view" file, which is essentially a .svelte file to load your components into to test. - -```html - - - {buttonText} - -``` - -In this example, the `on:click` that is heard on the `MyButton` component is passed up to the containing component `MyButtonView` using the svelte shorthand. -It's the equivalent to `on:click="fire('click', event)"`, but it's worth knowing about especially in this "component wrapper" scenario. - -> If your component doesn't use slots, you don't need to do this, but if it does or some other svelte functionality that requires the component to exist in a svelte view, then this is how to do that. - -You would then write a story for this "view" the exact same way you did with a component. - -```js -import MyButtonView from '../views/MyButtonView.svelte'; - -export default { title 'Button' } - -export const wrappedComponentExample = () => ({ - Components: MyButtonView, - props: { - buttonText: 'some text', - rounded: true, - }, - on: { - click: (event) => { - console.log('clicked', event); - }, - }, -}); -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - ├── With Emoji - └── Wrapped Component Example -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-vue/index.md b/docs/src/pages/guides/guide-vue/index.md deleted file mode 100644 index 2b48dac8532..00000000000 --- a/docs/src/pages/guides/guide-vue/index.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -id: 'guide-vue' -title: 'Storybook for Vue' ---- - ---- -**NOTE** - -[Vue.js devtools] [browser extension support is in the works] but not yet available! -
-See workarounds… - -- In Firefox: - 1. Open the story you wish to inspect. - 2. Right-click anywhere in the story and select This Frame → Open Frame in New Tab. devtools should now work correctly in the new tab. - -- In Chromium / Chrome: - 1. Open the story you wish to inspect. - 2. Right-click anywhere in the story and select View frame source which will open in a new tab. E.g., `view-source:http://localhost:6006/iframe.html?id=components-fancybutton--button&viewMode=story`. - 3. Remove the `view-source:` scheme from the URL in the address bar to load just the frame. devtools should now work correctly. - -- Launch the standalone Vue.js devtools app via `npx -p @vue/devtools vue-devtools` and add (or create) `` to `.storybook/preview-head.html`. Now run Storybook and devtools should connect. - -
- -[Vue.js devtools]: https://github.com/vuejs/vue-devtools -[browser extension support is in the works]: https://github.com/storybookjs/storybook/issues/1708 - ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using vue, you could try forcing it to use vue: - -```sh -npx -p @storybook/cli sb init --type vue -``` - -## Manual setup - -If you want to set up Storybook manually for your Vue project, this is the guide for you. - -## Step 1: Add dependencies - -### Add @storybook/vue - -Add `@storybook/vue` to your project. To do that, run: - -```sh -npm install @storybook/vue --save-dev -``` - -### Add peer dependencies - -Make sure that you have `vue`, `vue-loader`, `vue-template-compiler`, `@babel/core`, and `babel-loader` in your dependencies as well, because we list these as a peer dependencies: - -```sh -npm install vue --save -npm install vue-loader vue-template-compiler @babel/core babel-loader --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - "build-storybook": "build-storybook" - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -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 `preview.js` file. -> ->
-> details -> -> ```js -> import { configure } from '@storybook/vue'; -> -> import Vue from 'vue'; -> -> // Import Vue plugins -> import Vuex from 'vuex'; -> -> // Import your global components. -> import Mybutton from '../src/stories/Button.vue'; -> -> // Install Vue plugins. -> Vue.use(Vuex); -> -> // Register global components. -> Vue.component('my-button', Mybutton); -> -> configure(require.context('../src', true, /\.stories\.js$/), module); -> ``` -> -> This example registered your custom `Button.vue` component, installed the Vuex plugin, and loaded your Storybook stories defined in `../src/index.stories.js`. -> -> All custom components and Vue plugins should be registered before calling `configure()`. -> ->
- - -## Step 4: Write your stories - -Now create a `../stories/index.js` file, and write your first story like this: - -```js -import Vue from 'vue'; -import MyButton from './Button.vue'; - -export default { title: 'Button' }; - -export const withText = () => 'with text'; - -export const withEmoji = () => '😀 😎 👍 💯'; - -export const asAComponent = () => ({ - components: { MyButton }, - template: 'rounded' -}); -``` - -Each story is a single state of your component. In the above case, there are three stories for the demo button component: - -```plaintext -Button - ├── With Text - ├── With Emoji - └── As A Component -``` - -> 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 `preview.js` file. -> ->
-> details -> -> If your story returns a plain string like below, you will need to register globally each VueJs component that it uses. -> -> ```js -> export const withText = () => 'with text'; -> ``` -> -> In big solutions, globally registered components can conflict with each other. -> -> Here are two other ways to use components in your stories without globally registering them. -> -> - register components locally in the "components" member of the vue component object. See the story "as a component" above. -> - use a JSX render function like below. No need to register anything. -> -> ```jsx -> export const withText = () => ({ -> render: h => with text -> }); -> ``` -> ->
- -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/guide-web-components/index.md b/docs/src/pages/guides/guide-web-components/index.md deleted file mode 100644 index 3ac18e343ca..00000000000 --- a/docs/src/pages/guides/guide-web-components/index.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -id: 'guide-web-components' -title: 'Storybook for Web Components' ---- - -## Automatic setup - -You may have tried to use our quick start guide to setup your project for Storybook. -If it failed because it couldn't detect you're using web components, you could try forcing it to use web_components: - -```sh -npx -p @storybook/cli sb init --type web_components -``` - -## Manual setup - -If you want to set up Storybook manually for your web components project, this is the guide for you. - -## Step 1: Add dependencies - -### Init npm if necessary - -If you don't have `package.json` in your project, you'll need to init it first: - -```sh -npm init -``` - -### Add @storybook/web-components - -Add `@storybook/web-components` to your project. To do that, run: - -```sh -npm install @storybook/web-components --save-dev -``` - -### Add lit-html, @babel/core and babel-loader - -Make sure that you have `lit-html`, `@babel/core`, and `babel-loader` in your dependencies as well because we list these as a peer dependencies: - -```sh -npm install lit-html babel-loader @babel/core --save-dev -``` - -## Step 2: Add npm scripts - -Then add the following scripts to your `package.json` in order to start the storybook later in this guide: - -```json -{ - "scripts": { - "storybook": "start-storybook", - } -} -``` - -## 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/main.js` with the following content: - -```js -module.exports = { - stories: ['../src/**/*.stories.@(ts|js)'], -}; -``` - -That will load all the stories underneath your `../src` directory that match the pattern `*.stories.@(ts|js)`. We recommend co-locating your stories with your source files, but you can place them wherever you choose. - -## Step 4: Write your stories - -Now create a `../src/index.stories.js` file, and write your first story like this: - -```js -import { html } from 'lit-html'; - -export default { title: 'Button' }; - -export const WithText = () => html` - -`; - -export const WithEmoji = () => html` - -`; -``` - -Each story is a single state of your component. In the above case, there are two stories for the demo button component: - -```plaintext -Button - ├── With Text - └── With Emoji -``` - -## Finally: Run your Storybook - -Now everything is ready. Run your storybook with: - -```sh -npm run storybook -``` - -Storybook should start, on a random open port in dev-mode. - -Now you can develop your components and write stories and see the changes in Storybook immediately since it uses Webpack's hot module reloading. diff --git a/docs/src/pages/guides/quick-start-guide/index.md b/docs/src/pages/guides/quick-start-guide/index.md deleted file mode 100644 index 0faa6006204..00000000000 --- a/docs/src/pages/guides/quick-start-guide/index.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -id: 'quick-start-guide' -title: 'Quick Start Guide' ---- - -Storybook supports many different frontend view layers with more coming! -React, Vue, Angular, Mithril, Marko, HTML, Svelte, Meteor, Ember, Riot and Preact are currently supported. Follow these steps to get started with Storybook. - -Get started using the automated command line tool. This command adds a set of boilerplate files for Storybook in your project: - -```sh -cd my-project-directory -npx -p @storybook/cli sb init -``` - -The tool inspects your `package.json` to determine which view layer you're using. If you want to develop HTML snippets in storybook, we can't determine that automatically. So to install storybook for HTML, use the `--type` flag to force that the HTML project type is set: - -```sh -npx -p @storybook/cli sb init --type html -``` - -It's also useful if our automatic detection fails. - -By default npx will use the latest version, if you want to try out the next version (or any specific version), you can use the following: - -```sh -npx -p @storybook/cli@5.0.0-rc.6 sb init -``` - -To setup a project manually, take a look at the [Slow Start Guide](/guides/slow-start-guide/). - -Start Storybook with: - -```sh -npm run storybook -``` - -Storybook should now be available in the browser with a link provided in the console. - ---- - -To learn more about what the Storybook CLI command `sb init` command does, have a look at the slow start guides: - -- [React](/guides/guide-react/) -- [React Native](/guides/guide-react-native/) -- [Vue](/guides/guide-vue/) -- [Angular](/guides/guide-angular/) -- [Mithril](/guides/guide-mithril/) -- [Marko](/guides/guide-marko/) -- [HTML](/guides/guide-html/) -- [Svelte](/guides/guide-svelte/) -- [Ember](/guides/guide-ember/) -- [Riot](/guides/guide-riot/) -- [Preact](/guides/guide-preact/) -- [Web Components](/guides/guide-web-components/) - -A step-by-step tutorial is available at [Learn Storybook](https://www.learnstorybook.com). diff --git a/docs/src/pages/guides/slow-start-guide/index.md b/docs/src/pages/guides/slow-start-guide/index.md deleted file mode 100644 index f411004c2d5..00000000000 --- a/docs/src/pages/guides/slow-start-guide/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: 'slow-start-guide' -title: 'Slow Start Guide' ---- - -Storybook supports multiple UI libraries. The manual setup for each framework is different: - -- [Storybook for React](/guides/guide-react/) -- [Storybook for React Native](/guides/guide-react-native/) -- [Storybook for Vue](/guides/guide-vue/) -- [Storybook for Angular](/guides/guide-angular/) -- [Storybook for Mithril](/guides/guide-mithril/) -- [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/index.jsx b/docs/src/pages/index.jsx deleted file mode 100644 index c8af3a7a45b..00000000000 --- a/docs/src/pages/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import Homepage from '../components/Homepage'; -import users from './_users.yml'; - -const usersArray = Object.values(users).map((user) => ({ - ...user, - // eslint-disable-next-line import/no-dynamic-require, global-require - logoSrc: require(`./logos/${user.logo}`), -})); - -export default () => ; diff --git a/docs/src/pages/logos/airbnb.svg b/docs/src/pages/logos/airbnb.svg deleted file mode 100644 index 87d66e34fda..00000000000 --- a/docs/src/pages/logos/airbnb.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/algolia.svg b/docs/src/pages/logos/algolia.svg deleted file mode 100644 index 492674a1bd3..00000000000 --- a/docs/src/pages/logos/algolia.svg +++ /dev/null @@ -1 +0,0 @@ -algolia-darkCreated with Sketch. \ No newline at end of file diff --git a/docs/src/pages/logos/appbase.svg b/docs/src/pages/logos/appbase.svg deleted file mode 100644 index 420ac5cf860..00000000000 --- a/docs/src/pages/logos/appbase.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/buffer.svg b/docs/src/pages/logos/buffer.svg deleted file mode 100644 index 578d44a3d17..00000000000 --- a/docs/src/pages/logos/buffer.svg +++ /dev/null @@ -1 +0,0 @@ -buffer-logoCreated with Sketch. \ No newline at end of file diff --git a/docs/src/pages/logos/coursera.svg b/docs/src/pages/logos/coursera.svg deleted file mode 100644 index f3b82819309..00000000000 --- a/docs/src/pages/logos/coursera.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/dbsbank.svg b/docs/src/pages/logos/dbsbank.svg deleted file mode 100644 index bc7b0f69780..00000000000 --- a/docs/src/pages/logos/dbsbank.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/lonelyplanet.svg b/docs/src/pages/logos/lonelyplanet.svg deleted file mode 100644 index 52645905e43..00000000000 --- a/docs/src/pages/logos/lonelyplanet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/mozilla.svg b/docs/src/pages/logos/mozilla.svg deleted file mode 100644 index 99199c227e4..00000000000 --- a/docs/src/pages/logos/mozilla.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/nulogy.png b/docs/src/pages/logos/nulogy.png deleted file mode 100644 index 2bc19b6a1ef..00000000000 Binary files a/docs/src/pages/logos/nulogy.png and /dev/null differ diff --git a/docs/src/pages/logos/postmates.png b/docs/src/pages/logos/postmates.png deleted file mode 100644 index 0a686ca0908..00000000000 Binary files a/docs/src/pages/logos/postmates.png and /dev/null differ diff --git a/docs/src/pages/logos/quran.svg b/docs/src/pages/logos/quran.svg deleted file mode 100644 index 6ca23780118..00000000000 --- a/docs/src/pages/logos/quran.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/src/pages/logos/remitly.svg b/docs/src/pages/logos/remitly.svg deleted file mode 100644 index e8f568978f8..00000000000 --- a/docs/src/pages/logos/remitly.svg +++ /dev/null @@ -1 +0,0 @@ -Page 1 \ No newline at end of file diff --git a/docs/src/pages/logos/slack.svg b/docs/src/pages/logos/slack.svg deleted file mode 100644 index fb5e19e4f23..00000000000 --- a/docs/src/pages/logos/slack.svg +++ /dev/null @@ -1 +0,0 @@ -backgroundLayer 1 \ No newline at end of file diff --git a/docs/src/pages/logos/squarespace.png b/docs/src/pages/logos/squarespace.png deleted file mode 100644 index 55a29f0a302..00000000000 Binary files a/docs/src/pages/logos/squarespace.png and /dev/null differ diff --git a/docs/src/pages/logos/wix.svg b/docs/src/pages/logos/wix.svg deleted file mode 100644 index b6e277dded5..00000000000 --- a/docs/src/pages/logos/wix.svg +++ /dev/null @@ -1 +0,0 @@ -Layer 1 \ No newline at end of file diff --git a/docs/src/pages/presets/introduction/index.md b/docs/src/pages/presets/introduction/index.md deleted file mode 100644 index 46b9c8358e7..00000000000 --- a/docs/src/pages/presets/introduction/index.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -id: 'introduction' -title: 'Intro to Presets' ---- - -> migration guide: This page documents the method to configure storybook introduced recently in 5.3.0, consult the [migration guide](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) if you want to migrate to this format of configuring storybook. - -Storybook **presets** are grouped collections of `babel`, `webpack`, and `addons` configurations that support specific use cases. - -For example, to write your stories in Typescript, rather than [manually configuring Storybook for typescript](../../configurations/typescript-config/) with individual [babel](../../configurations/custom-babel-config/) and [webpack](../../configurations/custom-webpack-config/) configs, you can use the `@storybook/preset-typescript` package, which does the heavy lifting for you. - -## Basic usage - -Each preset has its own installation instructions, but the idea of presets is to install an addon and then load its preset. - -For example, to get typescript support, first install the addon: - -```sh -yarn add @storybook/preset-typescript --dev -``` - -Then load it in the file `main.js` in your storybook folder (`.storybook` by default): - -```js -module.exports = { - addons: ['@storybook/preset-typescript'], -}; -``` - -That's it. When Storybook starts up, it will configure itself for typescript without any further configuration. For more information, see the Typescript preset [README](https://github.com/storybookjs/presets/tree/master/packages/preset-typescript). - -## Preset configuration - -Presets can also take optional parameters. These can be used by the preset itself or passed through to configure the webpack loaders that are used by the preset. - -Consider this example: - -```js -const path = require('path'); -module.exports = { - addons: [ - { - name: '@storybook/preset-typescript', - options: { - tsLoaderOptions: { - configFile: path.resolve(__dirname, '../tsconfig.json'), - }, - include: [path.resolve(__dirname)], - }, - }, - ], -}; -``` - -This configures the typescript loader using the app's `tsconfig.json` and also tells the typescript loader to only be applied to the current directory. - -Each preset has its own option and those options should be documented in the preset's README. - -## Go deeper - -To see what presets are available, see the [preset gallery](../preset-gallery/). To understand more about how presets work and write your own, see [writing presets](../writing-presets/). diff --git a/docs/src/pages/presets/preset-gallery/index.md b/docs/src/pages/presets/preset-gallery/index.md deleted file mode 100644 index daead0aa427..00000000000 --- a/docs/src/pages/presets/preset-gallery/index.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: 'preset-gallery' -title: 'Preset Gallery' ---- - -[Storybook presets](../introduction/) are a new feature in Storybook to dramatically simplify configuration. This is a list of available presets for Storybook. - -## Presets maintained by Storybook team - -Storybook-maintained presets are available in the [Presets repo](https://github.com/storybookjs/presets). They include: - -### [Create React App](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app) - -One-line [Create React App](https://create-react-app.dev/) configuration for Storybook. - -### [Typescript](https://github.com/storybookjs/presets/tree/master/packages/preset-typescript) - -One-line Typescript w/ docgen configuration for Storybook. - -### [SCSS](https://github.com/storybookjs/presets/tree/master/packages/preset-scss) - -One-line SCSS configuration for Storybook. - -### [AntDesign](https://github.com/storybookjs/presets/tree/master/packages/preset-ant-design) - -One-line AntDesign configuration for Storybook. - -## Community presets - -There are no community presets available yet. Check back here or edit this page to add yours. diff --git a/docs/src/pages/testing/automated-visual-testing/index.md b/docs/src/pages/testing/automated-visual-testing/index.md deleted file mode 100644 index 9f7cb4cf979..00000000000 --- a/docs/src/pages/testing/automated-visual-testing/index.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -id: 'automated-visual-testing' -title: 'Automated Visual Testing' ---- - -Automated Visual Testing is a quality assurance process meant to automatically verify that a UI visually appears as intended. There are many alternative names for this process, such as Visual Regression Testing, Visual Validation Testing, Visual UI Testing, or Visual Testing, but in all cases we're talking about confirming the thing your users will see—the actual pixels—without caring about how it's generated. - -## Benefits - -The largest benefit to Automated Visual Testing is that they can often supersede other, more brittle tests. Instead of asserting specific CSS rules, selectors, and HTML markup, you assert that something visually appears as expected. If you refactor style, markup, or state logic, but visually nothing changes, you wouldn't need to modify any tests or update visual baselines! In a majority of cases, what a user _sees_ is what you really care about, not how it was achieved. - -Another potential benefit is with cross-browser testing. If you perform your visual tests against multiple browsers, they can potentially catch regressions a developer didn't because it appeared correct in their preferred development browser. - -## Challenges - -The biggest challenge with Automated Visual Testing is that humans and machines perceive pixels differently. Two screenshots of a UI could appear entirely identical to a human but 100% different to a 1:1, naive diffing algorithm. - -For example, changes in [anti-aliasing](https://en.wikipedia.org/wiki/Spatial_anti-aliasing) are common, even if human eyes don't notice them. - -![Menu before and after differences](../static/image-diff-1.png) -![Close-up of menu before and after differences](../static/image-diff-2.png) - -Even though we didn't change any CSS and the menus appear visually the same to our eyes, if we compare their pixels 1:1 we find that lots of it has changed! This can happen between browser versions, underlying hardware changes on your cloud platform, and more. - -Similar situations happen all the time, such as how images, drop shadows, etc are rendered. - -Some more robust libraries and services solve this by using a combination of [Machine Learning](https://wikipedia.org/wiki/Machine_learning) and [heuristics](https://wikipedia.org/wiki/Heuristic_(computer_science)). The machine is trained what is and is not an acceptable variation. - -For example, every graphics card renders text and images differently at the pixel level. Your machine will likely have a different graphics card than the testing server and the other developers. While the naked eye can't tell the difference, a machine needs to be trained to "see" them like we do. - -Another issue is that these tests are often slow compared to more lightweight unit tests that don't require a full browser. So you might only run them when you commit, push, and/or on your Continuous Integration (CI) server. - -## Libraries and Services with Storybook Integration - -There are many libraries and services that have Storybook integrations out-of-box, with varying levels of sophistication. Some even use complex Machine Learning instead of 1:1 pixel comparison. - -Storybook uses [Chromatic](https://www.chromaticqa.com), a visual testing service made by Storybook maintainers, to prevent UI bugs in our [application](https://www.chromaticqa.com/library?appId=5a375b97f4b14f0020b0cda3), [design system](https://www.chromaticqa.com/library?appId=5ccbc373887ca40020446347), and [website](https://www.chromaticqa.com/library?appId=5be26744d2f6250024a9117d). - -Here are some in alphabetical order: - -- [Applitools](https://applitools.com/storybook) -- [Chromatic](https://www.chromaticqa.com) made by Storybook maintainers -- [Creevey](https://github.com/wKich/creevey) -- [Happo](https://happo.io) -- [Loki](https://loki.js.org/) -- [Percy](https://docs.percy.io/docs/storybook) -- [Screener](https://screener.io/v2/docs) -- [StoryShots](https://github.com/storybookjs/storybook/tree/master/addons/storyshots) with its [seamless integration](https://github.com/storybookjs/storybook/tree/master/addons/storyshots#configure-storyshots-for-image-snapshots) with [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) - -## Custom Solutions - -There are a number of popular libraries that don't offer out-of-box integration with Storybook, but still can be used to visual test it. [BackstopJS](https://github.com/garris/BackstopJS) and [Gemini](https://github.com/gemini-testing/gemini), for example, but there are quite a few! - -Storybook's web server supports the ability to render a component story standalone, in any particular state, without any of the Storybook layout elements. With these special URLs you can either create your own screenshots and diff them, or use a library which does that work for you. - -Let's take a look at an example, in this case using [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot)—but the idea is similar for most of them. - -> Most custom solutions, including jest-image-snapshot, do 1:1 pixel comparisons which may suffer from issues described in the **Challenges** section above. - -### Example using Puppeteer and Jest - -Here's a sample Storybook we'd like to visually test: - -![Storybook Screenshot](../static/storybook-screenshot.png) - -The Storybook UI has a bunch of elements you wouldn't want to include in your visual test. Besides being extraneous, your tests could incorrectly fail when you add a new, unrelated story or state because it would show up in the side menu. - -Instead, we'll want to render a component's story by itself. Let's assume the above Storybook runs on port 9009 and we can access it via [http://localhost:9009/](http://localhost:9009/). - -Now let's pick a single story: the "with text" story of the Button. - -The URL for that story contains a number of query parameters, but the first two are the most important: [http://localhost:9009/?selectedKind=Button&selectedStory=with+text](http://localhost:9009/?selectedKind=Button&selectedStory=with+text) - -- selectedKind=Button -- selectedStory=with+text - -Using these two parameters we can generate the URL to render the story by itself. Instead of the URL's path being the homepage/index we use `/iframe.html`: - -[http://localhost:9009/iframe.html?selectedKind=Button&selectedStory=with+text](http://localhost:9009/iframe.html?selectedKind=Button&selectedStory=with+text) - -![Storybook iframe Screenshot](../static/storybook-iframe-screenshot.png) - -Now that we know how to render the story by itself, let's get our testing environment all set up. - -For this example, we're going to use a number of packages: - -* [jest](https://jestjs.io/) - - Testing framework by Facebook -* [puppeteer](https://developers.google.com/web/tools/puppeteer/) - - Library by Google for controlling a [headless](https://wikipedia.org/wiki/Headless_software) Chrome browser. -* [jest-puppeteer](https://github.com/smooth-code/jest-puppeteer) - - Integration between Jest and Puppeteer -* [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) - - Image snapshot assertion helper -* [start-server-and-test](https://github.com/bahmutov/start-server-and-test) - - Used to start/stop our Storybook server automatically for our tests - -> Even though we're choosing Jest/Puppeteer in this example, the principles are the same with other solutions. - -Let's go ahead and add all of these: - -```sh -yarn add jest puppeteer jest-puppeteer jest-image-snapshot start-server-and-test --dev -``` - -There's a bit of setup code that needs to run before your tests, so we'll need to configure a Jest setup file to run first, if you haven't already. This is done with the [`setupFilesAfterEnv` config property](https://jestjs.io/docs/en/configuration.html#setupFilesAfterEnv-string), either in your package.json or in your `jest.config.js`. We'll also set `"preset": "jest-puppeteer"` so that we get the nice integration from jest-puppeteer. - -#### `integration/jest.config.js` - -```js -module.exports = { - preset: 'jest-puppeteer', - testRegex: './*\\.test\\.js$', - setupFilesAfterEnv: ['./setupTests.js'] -}; - - ``` - -With our configuration in place, we can create our file at `integration/setupTests.js` and add the necessary setup logic: - -#### `integration/setupTests.js` - -```js -const { toMatchImageSnapshot } = require('jest-image-snapshot'); - -expect.extend({ toMatchImageSnapshot }); -``` - -Finally, we'll create some npm scripts for us to kick everything off with [start-server-and-test](https://github.com/bahmutov/start-server-and-test): - -#### `package.json` - -```json -{ - "name": "my-app", - "scripts": { - "jest:integration": "jest -c integration/jest.config.js", - "test:integration": "start-server-and-test storybook http-get://localhost:9009 jest:integration", - "storybook": "start-storybook -p 9009 -s public" - } -} -``` - -Now we're ready to write our first visual snapshot test! - -#### `integration/Button.test.js` - -```js -describe('Button', () => { - it('visually looks correct', async () => { - // APIs from jest-puppeteer - await page.goto('http://localhost:9009/iframe.html?selectedKind=Button&selectedStory=with+text'); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); -}); -``` - -Go ahead and run it: - -```bash -npm run test:integration -``` - -``` -info Storybook started on => http://localhost:9009/ - - PASS integration/app.test.js - Button - ✓ visually looks correct (699ms) - - › 1 snapshot written. -Snapshot Summary - › 1 snapshot written from 1 test suite. - -Test Suites: 1 passed, 1 total -Tests: 1 passed, 1 total -Snapshots: 1 written, 1 total -Time: 1.528s, estimated 2s -Ran all test suites. -``` - -The first time we run a test it will create a new image snapshot to use as the baseline for subsequent test runs. Try changing the style of your story's component and running it again. You should get something like this: - -``` - FAIL integration/app.test.js - Button - ✕ visually looks correct (880ms) - - ● Button › visually looks correct - - Expected image to match or be a close match to snapshot but was 0.03625% different from snapshot (174 differing pixels). - See diff for details: /my-app/integration/__image_snapshots__/__diff_output__/app-test-js-button-visually-looks-correct-1-diff.png -``` - -If this should become the new baseline, we can tell Jest to update them: - -``` -npm run jest:integration -- -updateSnapshot -``` - -We're all set, but keep in mind the [challenges](#challenges) of doing 1:1 pixel matching! diff --git a/docs/src/pages/testing/interaction-testing/index.md b/docs/src/pages/testing/interaction-testing/index.md deleted file mode 100644 index 31a796d18c5..00000000000 --- a/docs/src/pages/testing/interaction-testing/index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: 'interaction-testing' -title: 'Interaction Testing' ---- - -For the interaction testing, [Enzyme](https://github.com/airbnb/enzyme) is the best tool we can use. With that, we can [simulate](http://airbnb.io/enzyme/docs/api/ReactWrapper/simulate.html) user inputs and see what they are doing. - -You can directly write these kind of tests with a full-featured testing framework, such as **Mocha** or **Jest**. Have a look at the [Enzyme guidelines](https://github.com/airbnb/enzyme/) for more information on how to integrate them. - -## Specs Addon - -If you like to write your tests directly inside your stories, we also have an addon called [specs](https://github.com/mthuret/storybook-addon-specifications). - -![Storybook Specs Addon](../static/specs-addon.png) - -With that, you can write test specs directly inside stories. -Additionally, you also can use your CI server to run those tests. diff --git a/docs/src/pages/testing/manual-testing/index.md b/docs/src/pages/testing/manual-testing/index.md deleted file mode 100644 index de6068e1b50..00000000000 --- a/docs/src/pages/testing/manual-testing/index.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: 'manual-testing' -title: 'Manual Testing' ---- - -Now we arrive at the most interesting (but also hardest) part of UI testing. -We usually do this as a team. - -First, we need to make a pretty solid Storybook or a set of Storybooks covering most of the scenarios of our UI components. -For that we can follow the following structure: - -- Write stories for your individual UI components. -- Write another set of stories for integrating the above UI components (you could consider your pages). - -For the individual UI components, you may be using a different repository. -Then, keep a storybook inside it for those components. Then, in the main app, write stories for integrations. - -## Testing Plan - -Open a new PR (or multiple of them). -Then run your Storybook and start reviewing one story at a time. -Then you can comment on the PR. diff --git a/docs/src/pages/testing/react-ui-testing/index.md b/docs/src/pages/testing/react-ui-testing/index.md deleted file mode 100644 index ea1018a1f66..00000000000 --- a/docs/src/pages/testing/react-ui-testing/index.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -id: 'react-ui-testing' -title: 'Introduction: React UI Testing' ---- - -There are different aspects we need to look at when testing UI. -There are also a lot of tools and techniques we can use.  - -## Reasons for Testing - -Before we talk about testing, we need to think about why we need to test. -There are many reasons; here are some of our reasons: - -- To find bugs. -- To make sure things won't break between new code commits. -- To keep tests as living documentations. - -Specifically, testing is important when working with teams since it allows different people the ability to contribute with confidence. - -## Different Aspects of UI Testing - -We refer to UI for many things. To put this in focus, let's narrow it down to React based user interfaces. - -### 1. Structural Testing - -Here we'll focus on the structure of the UI and how it's laid out. -For an example, let's say we have a "login component" as shown below: - -![Login Form](../static/login_form.png) - -For structural testing, we are testing whether or not it has following content: - -- A title with "Log in to Facebook" -- Two inputs for the username and password. -- A submit button. -- An error screen to show errors. - -For React, we have been using [Enzyme](https://github.com/airbnb/enzyme) as a way to do structural testing, but now we can also use [Jest's snapshot testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) to make things even more simple. - -### 2. Interaction Testing - -UI is all about interacting with the user. -We do this with a bunch of UI elements, such as buttons, links, and input elements. -With interaction testing, we need to test if they are working properly. - -Let's again use the above login component as an example. It should do these things: - -- When we click the submit button, it should give us the username and password. -- When we click the "Forgotten Account" link, it should redirect to a new page. - -We have few ways to do this type of testing with React. One way is to use [Enzyme](https://github.com/airbnb/enzyme). - -### 3. CSS/Style Testing - -UI is all about styles (whether they're simple, beautiful, or even ugly). -With style testing, we are evaluating the look and feel of our UI components between code changes. -This is a pretty complex subject and usually we do it by comparing images. - -If we are using inline styles all the way, we can use JEST snapshot testing. -But to get even better results, we should consider using tools such as: - -- [BackstopJS](https://github.com/garris/BackstopJS) -- [Gemini](https://github.com/gemini-testing/gemini) -- [Happo](https://github.com/happo/happo.io) - -### 4. Manual Testing - -All the above sections are about testing with automated tools. -But since we are building UI for humans, we must also manually test them to see how they feel. - -Another reason for manual testing is for the better user experience. - -We should always try to test our UI with the naked eye. -For this, we can use our existing Storybook. -This is something that we can't automate(yet) and takes time. -But it would be great if we could do this once in a while (especially with a major code changes). - -## How Storybook Can Help Us - -A **story** is the smallest unit in Storybook. -It's a fully functioning UI element where the input can be used for any of the testing methods we've mentioned above. - -Let's look at how Storybook can help you do the above mentioned different aspects of testing. - -- [Structural Testing with StoryShots](/testing/structural-testing) -- [Interaction Testing with Specs Addon](/testing/interaction-testing) -- [Storybook as the Base for Automated Visual Testing](/testing/automated-visual-testing) -- [Storybook for Manual UI Testing](/testing/manual-testing) diff --git a/docs/src/pages/testing/static/image-diff-1.png b/docs/src/pages/testing/static/image-diff-1.png deleted file mode 100644 index 8144e98f1c1..00000000000 Binary files a/docs/src/pages/testing/static/image-diff-1.png and /dev/null differ diff --git a/docs/src/pages/testing/static/image-diff-2.png b/docs/src/pages/testing/static/image-diff-2.png deleted file mode 100644 index 7aaedaab2ab..00000000000 Binary files a/docs/src/pages/testing/static/image-diff-2.png and /dev/null differ diff --git a/docs/src/pages/testing/static/image-diff-3.png b/docs/src/pages/testing/static/image-diff-3.png deleted file mode 100644 index b2b7e1eae3a..00000000000 Binary files a/docs/src/pages/testing/static/image-diff-3.png and /dev/null differ diff --git a/docs/src/pages/testing/static/login_form.png b/docs/src/pages/testing/static/login_form.png deleted file mode 100644 index 6a28d96f74d..00000000000 Binary files a/docs/src/pages/testing/static/login_form.png and /dev/null differ diff --git a/docs/src/pages/testing/static/specs-addon.png b/docs/src/pages/testing/static/specs-addon.png deleted file mode 100644 index 2b1557c5711..00000000000 Binary files a/docs/src/pages/testing/static/specs-addon.png and /dev/null differ diff --git a/docs/src/pages/testing/static/storybook-iframe-screenshot.png b/docs/src/pages/testing/static/storybook-iframe-screenshot.png deleted file mode 100644 index 0926710560b..00000000000 Binary files a/docs/src/pages/testing/static/storybook-iframe-screenshot.png and /dev/null differ diff --git a/docs/src/pages/testing/static/storybook-screenshot.png b/docs/src/pages/testing/static/storybook-screenshot.png deleted file mode 100644 index b904a3ab963..00000000000 Binary files a/docs/src/pages/testing/static/storybook-screenshot.png and /dev/null differ diff --git a/docs/src/pages/testing/static/storyshots-diff-view.png b/docs/src/pages/testing/static/storyshots-diff-view.png deleted file mode 100644 index bf70ba58c1e..00000000000 Binary files a/docs/src/pages/testing/static/storyshots-diff-view.png and /dev/null differ diff --git a/docs/src/pages/testing/static/storyshots-first-run.png b/docs/src/pages/testing/static/storyshots-first-run.png deleted file mode 100644 index a6f1758e3f7..00000000000 Binary files a/docs/src/pages/testing/static/storyshots-first-run.png and /dev/null differ diff --git a/docs/src/pages/testing/structural-testing/index.md b/docs/src/pages/testing/structural-testing/index.md deleted file mode 100644 index e86a45ecb2f..00000000000 --- a/docs/src/pages/testing/structural-testing/index.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: 'structural-testing' -title: 'Structural Testing' ---- - -For React, [Jest's snapshot testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) is the best way to do Structural Testing. -It's painless to use and maintain. -We've integrated Jest's snapshot testing directly into Storybook using an addon called [StoryShots](https://github.com/storybookjs/storybook/tree/master/addons/storyshots). -Now we can use existing stories as the input for snapshot testing. - -## What's Snapshot Testing? - -With Snapshot testing, we keep a file copy of the structure of UI components. -Think of it like a set of HTML sources. - -Then, after we've completed any UI changes, we compare new snapshots with the snapshots that we kept in the file. - -If things are not the same, we can do two things: - -1. We can consider new snapshots that show the current state, and then update them as new snapshots. -2. We can find the root cause for the change and fix our code. - -> We can also commit these snapshots directly into the source code. - -## Using StoryShots - -[StoryShots](https://github.com/storybookjs/storybook/tree/master/addons/storyshots) is our integration between Storybook and Jest Snapshot Testing. - -To use StoryShots, first make sure you are inside a Storybook-enabled repo (make sure it has few stories). - -Then, install StoryShots and any necessary [framework-specific peer dependencies](https://github.com/storybookjs/storybook/blob/next/addons/storyshots/storyshots-core/README.md). - -For instance if you're testing a react project: - -```sh -npm i -D @storybook/addon-storyshots react-test-renderer -``` - -Then, assuming you are using Jest for testing, you can create a test file `storyshots.test.js` that contains the following: - -```js -import initStoryshots from '@storybook/addon-storyshots'; - -initStoryshots({ /* configuration options */ }); -``` - -Now you can snapshot test all of your stories with: - -```sh -npm test -``` - -This will save the initial set of snapshots inside your Storybook config directory. - -![StoryShots First](../static/storyshots-first-run.png) - -After you complete any changes, you can run the test again and find all structural changes. - -![StoryShots Diff View](../static/storyshots-diff-view.png) - -* * * - -StoryShots also comes with a variety of customization options. Have a look at the StoryShots [repo](https://github.com/storybookjs/storybook/tree/master/addons/storyshots) for more information. diff --git a/docs/src/stories/_examples.yml b/docs/src/stories/_examples.yml deleted file mode 100644 index b0fb6e310e5..00000000000 --- a/docs/src/stories/_examples.yml +++ /dev/null @@ -1,30 +0,0 @@ -airbnb: - thumbnail: https://a0.muscache.com/im/pictures/90bce1b0-24ab-4e07-8499-3c4f4b0f2000.jpg?aki_policy=xl_poster - title: Airbnb Dates - description: An internationalizable, mobile-friendly datepicker library for the web. - source: https://github.com/airbnb/react-dates - demo: http://airbnb.io/react-dates/ - site: http://airbnb.com -appbase: - title: Appbase - description: A storybook playground for ReactiveMaps and ReactiveSearch. - source: https://github.com/appbaseio/playground - demo: https://opensource.appbase.io/playground/ - site: https://appbase.io -buffer: - title: Buffer - description: A collection of Buffer UI React components. - source: https://github.com/bufferapp/buffer-components - demo: https://bufferapp.github.io/buffer-components/ - site: https://buffer.com -lonelyplanet: - title: Lonely Planet - description: All the tools you need to build the Lonely Planet UI experience. - source: https://github.com/lonelyplanet/backpack-ui - site: https://www.lonelyplanet.com/ -griddle: - title: Griddle - description: An ultra customizable datagrid component for React. - source: https://github.com/GriddleGriddle/Griddle - demo: https://griddlegriddle.github.io/Griddle/examples/controlling-griddle/ - site: https://griddlegriddle.github.io/Griddle/ diff --git a/docs/src/stories/_users.yml b/docs/src/stories/_users.yml deleted file mode 100644 index b6c1d724dc0..00000000000 --- a/docs/src/stories/_users.yml +++ /dev/null @@ -1,31 +0,0 @@ -airbnb: - logo: ./logos/airbnb.svg - title: Airbnb Dates - description: An internationalizable, mobile-friendly datepicker library for the web. - source: https://github.com/airbnb/react-dates - demo: http://airbnb.io/react-dates/ - site: http://airbnb.com -buffer: - logo: ./logos/buffer.svg - title: Buffer Components - description: A collection of Buffer UI React components. - source: https://github.com/bufferapp/buffer-components - demo: https://bufferapp.github.io/buffer-components/ - site: https://buffer.com -lonelyplanet: - logo: ./logos/lonelyplanet.svg - title: Lonely Planet - description: All the tools you need to build the Lonely Planet UI experience. - source: https://github.com/lonelyplanet/backpack-ui - demo: https://lonelyplanet.github.io/backpack-ui/ - site: https://www.lonelyplanet.com/ -algolia: - logo: ./logos/algolia.svg - title: Algolia Instantsearch - description: Lightning-fast, hyper-configurable search. - source: https://github.com/algolia/react-instantsearch/ - demo: https://community.algolia.com/react-instantsearch/storybook/ -postmates: - logo: ./logos/postmates.png - title: Postmates - site: https://postmates.com diff --git a/docs/src/stories/data.js b/docs/src/stories/data.js deleted file mode 100644 index 4bd0bf47774..00000000000 --- a/docs/src/stories/data.js +++ /dev/null @@ -1,99 +0,0 @@ -import marked from 'marked'; - -export const docsData = { - categories: [ - { - id: 'cat-1', - title: 'CAT 1', - }, - { - id: 'cat-2', - title: 'CAT 2', - }, - ], - sections: [ - { - id: 'basics', - heading: 'Basics', - items: [ - { id: 'getting-started', title: 'Getting Started' }, - { id: 'writing-stories', title: 'Writing Stories' }, - { id: 'build-as-a-static-app', title: 'Build as a Static App' }, - ], - }, - { - id: 'guides', - heading: 'Guides', - items: [ - { id: 'getting-started', title: 'Getting Started' }, - { id: 'writing-stories', title: 'Writing Stories' }, - { id: 'build-as-a-static-app', title: 'Build as a Static App' }, - ], - }, - { - id: 'configurations', - heading: 'Configuations', - items: [ - { id: 'default-config', title: 'Default Config' }, - { id: 'webpack', title: 'Webpack' }, - { id: 'babel', title: 'Babel' }, - ], - }, - ], - selectedItem: { - id: 'writing-stories', - section: 'basics', - title: 'Writing Stories', - content: marked(` -You need to write stories to show your components inside Storybook.
-We've a set of APIs allows you to write stories and do more with them. - -When you are writing stories, you can follow these guidelines
-to write great stories. - -* Write UI components by passing data via props. -* In this way, you can isolate UI components easilly. -* Do not write app-specific code inside your UI components. - -~~~js -import { linkTo } from @storybook/addon-links - -storiesOf('Toggle', module) - .add('on', () => { - return - }) - .add('off', () => { - return - }); -~~~ - `), - }, - featuredStorybooks: [ - { - owner: 'https://avatars0.githubusercontent.com/u/698437?v=3&s=200', - storybook: { - name: 'React Dates', - link: 'http://airbnb.io/react-dates/', - }, - source: 'https://github.com/airbnb/react-dates', - }, - - { - owner: 'https://avatars3.githubusercontent.com/u/239676?v=3&s=460', - storybook: { - name: 'React Native Web', - link: 'https://necolas.github.io/react-native-web/docs/', - }, - source: 'https://github.com/necolas/react-native-web', - }, - - { - owner: 'https://avatars1.githubusercontent.com/u/15616844?v=3&s=200', - storybook: { - name: 'React Button', - link: 'http://kadira-samples.github.io/react-button/', - }, - source: 'https://github.com/kadira-samples/react-button', - }, - ], -}; diff --git a/docs/src/stories/implementations.js b/docs/src/stories/implementations.js deleted file mode 100644 index 05f373d506b..00000000000 --- a/docs/src/stories/implementations.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; - -import Homepage from '../components/Homepage'; -import Header from '../components/Header'; -import Heading from '../components/Homepage/Heading'; -import Demo from '../components/Homepage/Demo'; -import Platforms from '../components/Homepage/Platforms'; -import MainLinks from '../components/Homepage/MainLinks'; -import Featured from '../components/Homepage/Featured'; -import UsedBy from '../components/Homepage/UsedBy'; -import Footer from '../components/Footer'; -import Docs from '../components/Docs'; -import DocsContainer from '../components/Docs/Container'; -import DocsContent from '../components/Docs/Content'; -import DocsNav from '../components/Docs/Nav'; -import GridItem from '../components/Grid/GridItem'; -import Grid from '../components/Grid/Grid'; -import Examples from '../components/Grid/Examples'; - -import { docsData } from './data'; -import users from './_users.yml'; -import exampleData from './_examples.yml'; - -export default { - 'Homepage.page': ( - - ), - 'Homepage.header':
, - 'Homepage.heading': , - 'Homepage.demo': , - 'Homepage.built-for': , - 'Homepage.main-links': , - 'Homepage.featured-storybooks': , - 'Homepage.used-by': , - 'Homepage.footer':