diff --git a/.circleci/config.yml b/.circleci/config.yml index c5222aac5d6a..4407913d14a5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -250,7 +250,7 @@ jobs: cd .. npx create-react-app cra-bench cd cra-bench - npx @storybook/bench 'npx sb init' --label cra --extra-flags "--modern" + npx @storybook/bench@latest 'npx sb init' --label cra --extra-flags "--modern" e2e-tests-pnp: executor: class: medium diff --git a/.eslintrc.js b/.eslintrc.js index 3dc077d3aad4..b38e16c9be69 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,10 @@ module.exports = { extends: ['@storybook/eslint-config-storybook', 'plugin:storybook/recommended'], rules: { '@typescript-eslint/ban-ts-comment': 'warn', + 'jest/no-standalone-expect': [ + 'error', + { additionalTestBlockFunctions: ['it.skipWindows', 'it.onWindows'] }, + ], }, overrides: [ { diff --git a/.github/workflows/handle-release-branches.yml b/.github/workflows/handle-release-branches.yml index 95735ca4fb02..069b3390d2f6 100644 --- a/.github/workflows/handle-release-branches.yml +++ b/.github/workflows/handle-release-branches.yml @@ -40,7 +40,7 @@ jobs: - id: next-version uses: notiz-dev/github-action-json-property@release with: - path: ${{ github.workspace }}/next/docs/versions/next.json + path: ${{ github.workspace }}/next/package.json prop_path: version - run: | diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml index 43ea60fc73dd..822dc4569629 100644 --- a/.github/workflows/tests-unit.yml +++ b/.github/workflows/tests-unit.yml @@ -1,16 +1,33 @@ name: Unit tests -on: [push] +on: + push: + branches: + - next + pull_request: + types: [opened, reopened, labeled, synchronize] jobs: build: - name: Core Unit Tests - runs-on: ubuntu-latest + name: Core Unit Tests node-${{ matrix.node_version }}, ${{ matrix.os }} + if: ${{ github.event.label.name == 'ci:matrix' || github.event.type == 'push' }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + node_version: [12, 14, 16] + include: + - os: macos-latest + node_version: 16 + - os: windows-latest + node_version: 16 + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - name: Set node version to ${{ matrix.node_version }} + uses: actions/setup-node@v2 with: - node-version: "12.x" + node-version: ${{ matrix.node_version }} cache: yarn - name: install, bootstrap run: | diff --git a/.gitignore b/.gitignore index b60280d358b5..423ff6ec3edc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ dist .tern-port *.DS_Store .cache +.parcel-cache coverage/ *.lerna_backup build @@ -41,4 +42,4 @@ examples/angular-cli/addon-jest.testresults.json !/**/.yarn/plugins !/**/.yarn/sdks !/**/.yarn/versions -/**/.pnp.* \ No newline at end of file +/**/.pnp.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ff0eb1388d..59fb6083acd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,229 @@ +## 6.5.0-alpha.7 (January 4, 2022) + +### Bug Fixes + +- Addon-measure: Update z-index to fit with libraries with also high z-index ([#15860](https://github.com/storybookjs/storybook/pull/15860)) + +### Maintenance + +- Vue: Fix VueLoaderPlugin import to support vue-loader@16.x ([#14624](https://github.com/storybookjs/storybook/pull/14624)) + +### Dependency Upgrades + +- Upgrade react-syntax-highlighter to pick up security patch upstream in highlight.js ([#17100](https://github.com/storybookjs/storybook/pull/17100)) + +## 6.5.0-alpha.6 (January 3, 2022) + +### Features + +- Vue3: Add default render function CSF3 ([#17068](https://github.com/storybookjs/storybook/pull/17068)) +- Addon-docs/Vue: Include methods in ArgsTable ([#16975](https://github.com/storybookjs/storybook/pull/16975)) + +### Bug Fixes + +- CLI: Install `lit-html` in new web components project ([#17106](https://github.com/storybookjs/storybook/pull/17106)) +- Angular: Fix runCompodoc for Windows, local Compodoc, and user specified tsconfig ([#16728](https://github.com/storybookjs/storybook/pull/16728)) + +### Dependency Upgrades + +- React: Remove react-dev-utils ([#17022](https://github.com/storybookjs/storybook/pull/17022)) + +## 6.5.0-alpha.5 (December 23, 2021) + +### Dependency Upgrades + +- Update react-refresh@0.11.0 & react-refresh-webpack-plugin@0.5.3 same as cra5 ([#17056](https://github.com/storybookjs/storybook/pull/17056)) + +## 6.5.0-alpha.4 (December 18, 2021) + +### Bug Fixes + +- Angular: Fix for renamed method in angular 13.1 ([#17032](https://github.com/storybookjs/storybook/pull/17032)) +- Source-loader: Fix node.declaration edge case ([#17027](https://github.com/storybookjs/storybook/pull/17027)) +- Core: Fix debug output on webpack failures ([#16988](https://github.com/storybookjs/storybook/pull/16988)) + +### Maintenance + +- Build: Run unit tests on more node versions, mac, and windows ([#16744](https://github.com/storybookjs/storybook/pull/16744)) + +### Dependency Upgrades + +- Avoid referencing internal Emotion packages in built types ([#16905](https://github.com/storybookjs/storybook/pull/16905)) + +## 6.5.0-alpha.3 (December 9, 2021) + +### Bug Fixes + +- Core: Support custom PREVIEW URL for block story iframe ([#16773](https://github.com/storybookjs/storybook/pull/16773)) + +## 6.5.0-alpha.2 (December 9, 2021) + +### Bug Fixes + +- UI: Only push the view back to Story if the viewMode is settings ([#16943](https://github.com/storybookjs/storybook/pull/16943)) +- Core: Ensure we have a full story index before caching ([#16947](https://github.com/storybookjs/storybook/pull/16947)) +- Angular: Fix support for non-roman alphabets in story titles ([#16931](https://github.com/storybookjs/storybook/pull/16931)) +- Core: Be explicit about `viewMode` to fix Vue issue ([#16919](https://github.com/storybookjs/storybook/pull/16919)) +- Core: Remove unused and occluded types ([#16917](https://github.com/storybookjs/storybook/pull/16917)) +- CLI: Fix `sb repro` clobbering .vuerc ([#16897](https://github.com/storybookjs/storybook/pull/16897)) +- Core: Fix auto-title in webpack5 ([#16913](https://github.com/storybookjs/storybook/pull/16913)) +- Angular: Fix incorrect log ([#16885](https://github.com/storybookjs/storybook/pull/16885)) +- Angular: Fix tsConfig paths not resolving for Angular >=12.2 ([#16882](https://github.com/storybookjs/storybook/pull/16882)) +- Core: Add feature flag to disable legacy hierarchy separator warning ([#16915](https://github.com/storybookjs/storybook/pull/16915)) + +### Dependency Upgrades + +- Move @types/node to dependencies and accept v16 types ([#16904](https://github.com/storybookjs/storybook/pull/16904)) +- Bump lodash to 4.17.21 ([#16883](https://github.com/storybookjs/storybook/pull/16883)) + +## 6.4.9 (December 9, 2021) + +### Bug Fixes + +- Core: Ensure we have a full story index before caching ([#16947](https://github.com/storybookjs/storybook/pull/16947)) +- Angular: Fix support for non-roman alphabets in story titles ([#16931](https://github.com/storybookjs/storybook/pull/16931)) +- Core: Be explicit about `viewMode` to fix Vue issue ([#16919](https://github.com/storybookjs/storybook/pull/16919)) +- Core: Remove unused and occluded types ([#16917](https://github.com/storybookjs/storybook/pull/16917)) + +## 6.4.8 (December 6, 2021) + +### Bug Fixes + +- Core: Fix auto-title in webpack5 ([#16913](https://github.com/storybookjs/storybook/pull/16913)) +- CLI: Fix `sb repro` clobbering .vuerc ([#16897](https://github.com/storybookjs/storybook/pull/16897)) + +### Maintenance + +- Core: Add feature flag to disable legacy hierarchy separator warning ([#16915](https://github.com/storybookjs/storybook/pull/16915)) + +## 6.4.7 (December 3, 2021) + +### Bug Fixes + +- Angular: Fix incorrect log ([#16885](https://github.com/storybookjs/storybook/pull/16885)) + +## 6.4.6 (December 3, 2021) + +Npm publish failed. + +## 6.4.5 (December 3, 2021) + +### Bug Fixes + +- Angular: Fix tsConfig paths not resolving for Angular >=12.2 ([#16882](https://github.com/storybookjs/storybook/pull/16882)) +- Addon-docs: Fix transclusion crash on webpack rules without test field ([#16873](https://github.com/storybookjs/storybook/pull/16873)) + +### Dependency Upgrades + +- Bump lodash to 4.17.21 ([#16883](https://github.com/storybookjs/storybook/pull/16883)) + +## 6.5.0-alpha.1 (December 3, 2021) + +### Bug Fixes + +- CLI: Fix open storybook in default browser ([#16844](https://github.com/storybookjs/storybook/pull/16844)) +- Addon-docs: Fix transclusion crash on webpack rules without test field ([#16873](https://github.com/storybookjs/storybook/pull/16873)) + +### Maintenance + +- CLI: Improve `sb repro` directory prompt ([#16854](https://github.com/storybookjs/storybook/pull/16854)) + +## 6.4.4 (December 2, 2021) + +### Bug Fixes + +- CLI: Fix mainjsFramework automigrate ([#16866](https://github.com/storybookjs/storybook/pull/16866)) + +## 6.4.3 (December 1, 2021) + +### Bug Fixes + +- Don't render with `modernInline` if `inlineStories` is `false` ([#16853](https://github.com/storybookjs/storybook/pull/16853)) +- Preview: Don't hide the story while preparing ([#16850](https://github.com/storybookjs/storybook/pull/16850)) + +## 6.4.2 (December 1, 2021) + +### Bug Fixes + +- UI: Ensure all classes+animations for our loaders are prefixed ([#16815](https://github.com/storybookjs/storybook/pull/16815)) +- Angular: Add back-compat method to find options (styles) in angular.json ([#16832](https://github.com/storybookjs/storybook/pull/16832)) + +## 6.4.1 (November 30, 2021) + +### Bug Fixes + +- Core: Fix packageName check in build-dev ([#16823](https://github.com/storybookjs/storybook/pull/16823)) +- CSFFile: Fix function exports ([#16829](https://github.com/storybookjs/storybook/pull/16829)) + +### Maintenance + +- Fix `handle-release-branches` workflow ([#16801](https://github.com/storybookjs/storybook/pull/16801)) + +## 6.4.0 (November 27, 2021) + +Storybook 6.4 is here!! 🎉🎉🎉 + +SB6.4 adds interaction testing and performance re-architecture ahead of a huge 7.0 release. + +- ▶️ **Interactive stories** to simulate user behavior and tools to debug it +- ⚡️ **On-demand architecture** for smaller builds and faster load times +- ⛸ **Automigrate + versioned documentation** for easier upgrades +- 📋 **Linter** to enforce Storybook best practices +- 💯 **Hundreds more fixes** and quality of life improvements + +More info in the Github issue [Storybook 6.4 Release 🛠](https://github.com/storybookjs/storybook/issues/15355). Release announcement coming soon!!! + +## 6.4.0-rc.11 (November 26, 2021) + +### Bug Fixes + +- Core: Fix breaking change in process/browser ([#16795](https://github.com/storybookjs/storybook/pull/16795)) + +## 6.4.0-rc.10 (November 26, 2021) + +### Bug Fixes + +- Core: Allow args/argTypes/component to be set via parameters for storiesOf back-compat ([#16791](https://github.com/storybookjs/storybook/pull/16791)) +- Core: Sort the results of `globby` when constructing Story Index ([#16788](https://github.com/storybookjs/storybook/pull/16788)) +- Core: Don't log a console error when the story is missing ([#16783](https://github.com/storybookjs/storybook/pull/16783)) +- Addon-docs: Wait for the story component to render before emitting ([#16792](https://github.com/storybookjs/storybook/pull/16792)) +- Core: Ensure that `context.args` is always set ([#16790](https://github.com/storybookjs/storybook/pull/16790)) + +## 6.4.0-rc.9 (November 26, 2021) + +### Features + +- Angular: Add styles and stylePreprocessorOptions to angular builder ([#16675](https://github.com/storybookjs/storybook/pull/16675)) + +### Bug Fixes + +- Interactions: Unlock controls when play function is finished ([#16784](https://github.com/storybookjs/storybook/pull/16784)) + +### Maintenance + +- Misc: Cleanup typescript webpack types ([#16780](https://github.com/storybookjs/storybook/pull/16780)) + +## 6.4.0-rc.8 (November 25, 2021) + +### Bug Fixes + +- Interactions: Fix duplicate rows in waitFor ([#16465](https://github.com/storybookjs/storybook/pull/16465)) +- Core: Fix channel options so that they are merged in correct order ([#16764](https://github.com/storybookjs/storybook/pull/16764)) + +### Dependency Upgrades + +- Add missing peer dependencies ([#16551](https://github.com/storybookjs/storybook/pull/16551)) + +## 6.4.0-rc.7 (November 24, 2021) + +### Bug Fixes + +- Core: Add `./` to start of hidden file & folder paths ([#16723](https://github.com/storybookjs/storybook/pull/16723)) + +### Dependency Upgrades + +- Update peer dependencies for angular 13 support ([#16758](https://github.com/storybookjs/storybook/pull/16758)) + ## 6.4.0-rc.6 (November 22, 2021) ### Bug Fixes diff --git a/MIGRATION.md b/MIGRATION.md index c67da785fa7c..039111dd1c7e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -17,7 +17,10 @@ - [Emotion11 quasi-compatibility](#emotion11-quasi-compatibility) - [Babel mode v7](#babel-mode-v7) - [Loader behavior with args changes](#loader-behavior-with-args-changes) - - [Angular component parameter removed](#angular-component-parameter-removed) + - [6.4 Angular changes](#64-angular-changes) + - [SB Angular builder](#sb-angular-builder) + - [Angular13](#angular13) + - [Angular component parameter removed](#angular-component-parameter-removed) - [6.4 deprecations](#64-deprecations) - [Deprecated --static-dir CLI flag](#deprecated---static-dir-cli-flag) - [From version 6.2.x to 6.3.0](#from-version-62x-to-630) @@ -214,9 +217,9 @@ npx sb@next automigrate Or you can do the following steps manually to force Storybook to use webpack 5 for building your project: ```shell -yarn add @storybook/builder-webpack5@next @storybook/manager-webpack5 --dev +yarn add @storybook/builder-webpack5 @storybook/manager-webpack5 --dev # Or -npm install @storybook/builder-webpack5@next @storybook/manager-webpack5 --save-dev +npm install @storybook/builder-webpack5 @storybook/manager-webpack5 --save-dev ``` Then edit your `.storybook/main.js` config: @@ -267,7 +270,7 @@ export default { title: 'Components/Atoms/Button', }; -// ✅ undefined 6.3 KO / 7.0 OK +// ✅ undefined 6.3 OK / 7.0 OK export default { component: Button, }; @@ -407,9 +410,11 @@ The Story Store in v7 mode is async, so synchronous story loading APIs no longer Storyshots is not currently compatible with the v7 store. However, you can use the following workaround to opt-out of the v7 store when running storyshots; in your `main.js`: ```js -features: { - storyStoreV7: !global.navigator?.userAgent?.match?.('jsdom'); -} +module.exports = { + features: { + storyStoreV7: !global.navigator?.userAgent?.match?.('jsdom'), + }, +}; ``` There are some caveats with the above approach: @@ -424,9 +429,11 @@ Now that the web is moving to Emotion 11 for styling, popular libraries like MUI Unfortunately we're unable to upgrade Storybook to Emotion 11 without a semver major release, and we're not ready for that. So, as a workaround, we've created a feature flag which opts-out of the previous behavior of pinning the Emotion version to v10. To enable this workaround, add the following to your `.storybook/main.js` config: ```js -module.exports { - emotionAlias: false, -} +module.exports = { + features: { + emotionAlias: false, + }, +}; ``` Setting this should unlock theming for emotion11-based libraries in Storybook 6.4. @@ -466,7 +473,53 @@ This will create a `.babelrc.json` file. This file includes a bunch of babel plu In 6.4 the behavior of loaders when arg changes occurred was tweaked so loaders do not re-run. Instead the previous value of the loader is passed to the story, irrespective of the new args. -### Angular component parameter removed +### 6.4 Angular changes + +#### SB Angular builder + +Since SB6.3, Storybook for Angular supports a builder configuration in your project's `angular.json`. This provides an Angular-style configuration for running and building your Storybook. The full builder documentation will be shown in the [main documentation page](https://storybook.js.org/docs/angular) soon, but for now you can check out an example here: + +- `start-storybook`: https://github.com/storybookjs/storybook/blob/next/examples/angular-cli/angular.json#L78 +- `build-storybook`: https://github.com/storybookjs/storybook/blob/next/examples/angular-cli/angular.json#L86 + +#### Angular13 + +Angular 13 introduces breaking changes that require updating your Storybook configuration if you are migrating from a previous version of Angular. + +Most notably, the documented way of including global styles is no longer supported by Angular13. Previously you could write the following in your `.storybook/preview.js` config: + +``` +import '!style-loader!css-loader!sass-loader!./styles.scss'; +``` + +If you use Angular 13 and above, you should use the builder configuration instead: + +```json + "my-default-project": { + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "styles": ["src/styles.css", "src/styles.scss"], + } + } + }, + }, +``` + +If you need storybook-specific styles separate from your app, you can configure the styles in the [SB Angular builder](#sb-angular-builder), which completely overrides your project's styles: + +```json + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + "browserTarget": "my-default-project:build", + "styles": [".storybook/custom-styles.scss"], + }, + } +``` + +#### Angular component parameter removed In SB6.3 and earlier, the `default.component` metadata was implemented as a parameter, meaning that stories could set `parameters.component` to override the default export. This was an internal implementation that was never documented, but it was mistakenly used in some Angular examples. diff --git a/README.md b/README.md index 4c03ebc91655..8165065d78d3 100644 --- a/README.md +++ b/README.md @@ -94,21 +94,21 @@ For additional help, join us in the [Storybook Discord](https://discord.gg/story | Framework | Demo | | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| [React](app/react) | [v6.3.x](https://storybookjs.netlify.com/official-storybook/?path=/story/*) | [![React](https://img.shields.io/npm/dm/@storybook/react.svg)](app/react) | -| [Vue](app/vue) | [v6.3.x](https://storybookjs.netlify.com/vue-kitchen-sink/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) | -| [Angular](app/angular) | [v6.3.x](https://storybookjs.netlify.com/angular-cli/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) | -| [Web components](app/web-components) | [v6.3.x](https://storybookjs.netlify.com/web-components-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/web-components.svg)](app/web-components) | +| [React](app/react) | [v6.4.x](https://storybookjs.netlify.com/official-storybook/?path=/story/*) | [![React](https://img.shields.io/npm/dm/@storybook/react.svg)](app/react) | +| [Vue](app/vue) | [v6.4.x](https://storybookjs.netlify.com/vue-kitchen-sink/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) | +| [Angular](app/angular) | [v6.4.x](https://storybookjs.netlify.com/angular-cli/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) | +| [Web components](app/web-components) | [v6.4.x](https://storybookjs.netlify.com/web-components-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/web-components.svg)](app/web-components) | | [React Native](https://github.com/storybookjs/react-native) | - | [![React Native](https://img.shields.io/npm/dm/@storybook/react-native.svg)](app/react-native) | -| [HTML](app/html) | [v6.3.x](https://storybookjs.netlify.com/html-kitchen-sink/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) | -| [Ember](app/ember) | [v6.3.x](https://storybookjs.netlify.com/ember-cli/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember.svg)](app/ember) | -| [Svelte](app/svelte) | [v6.3.x](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/svelte.svg)](app/svelte) | -| [Preact](app/preact) | [v6.3.x](https://storybookjs.netlify.com/preact-kitchen-sink/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact.svg)](app/preact) | -| [Marionette.js](https://github.com/storybookjs/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) | -| [Mithril](https://github.com/storybookjs/mithril) | [v6.3.x](https://storybookjs.netlify.com/mithril-kitchen-sink/) | [![Mithril](https://img.shields.io/npm/dm/@storybook/mithril.svg)](app/mithril) | -| [Marko](https://github.com/storybookjs/marko) | [v6.3.x](https://storybookjs.netlify.com/marko-cli/) | [![Marko](https://img.shields.io/npm/dm/@storybook/marko.svg)](app/marko) | -| [Riot](https://github.com/storybookjs/riot) | [v6.3.x](https://storybookjs.netlify.com/riot-kitchen-sink/) | [![Riot](https://img.shields.io/npm/dm/@storybook/riot.svg)](app/riot) | -| [Rax](https://github.com/storybookjs/rax) | [v6.3.x](https://storybookjs.netlify.com/rax-kitchen-sink/) | [![Rax](https://img.shields.io/npm/dm/@storybook/rax.svg)](app/rax) | -| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [v6.3.x](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [![Rax](https://img.shields.io/npm/dm/@storybook/native.svg)](https://github.com/storybookjs/native) | +| [HTML](app/html) | [v6.4.x](https://storybookjs.netlify.com/html-kitchen-sink/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) | +| [Ember](app/ember) | [v6.4.x](https://storybookjs.netlify.com/ember-cli/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember.svg)](app/ember) | +| [Svelte](app/svelte) | [v6.4.x](https://storybookjs.netlify.com/svelte-kitchen-sink/) | [![Svelte](https://img.shields.io/npm/dm/@storybook/svelte.svg)](app/svelte) | +| [Preact](app/preact) | [v6.4.x](https://storybookjs.netlify.com/preact-kitchen-sink/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact.svg)](app/preact) | +| [Marionette.js](https://github.com/storybookjs/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) | +| [Mithril](https://github.com/storybookjs/mithril) | [v6.4.x](https://storybookjs.netlify.com/mithril-kitchen-sink/) | [![Mithril](https://img.shields.io/npm/dm/@storybook/mithril.svg)](app/mithril) | +| [Marko](https://github.com/storybookjs/marko) | [v6.4.x](https://storybookjs.netlify.com/marko-cli/) | [![Marko](https://img.shields.io/npm/dm/@storybook/marko.svg)](app/marko) | +| [Riot](https://github.com/storybookjs/riot) | [v6.4.x](https://storybookjs.netlify.com/riot-kitchen-sink/) | [![Riot](https://img.shields.io/npm/dm/@storybook/riot.svg)](app/riot) | +| [Rax](https://github.com/storybookjs/rax) | [v6.4.x](https://storybookjs.netlify.com/rax-kitchen-sink/) | [![Rax](https://img.shields.io/npm/dm/@storybook/rax.svg)](app/rax) | +| [Android, iOS, Flutter](https://github.com/storybookjs/native) | [v6.4.x](https://storybookjs.github.io/native/@storybook/native-flutter-example/index.html) | [![Rax](https://img.shields.io/npm/dm/@storybook/native.svg)](https://github.com/storybookjs/native) | ### Sub Projects @@ -141,13 +141,13 @@ See [Addon / Framework Support Table](https://storybook.js.org/docs/react/api/fr ### Deprecated Addons -| Addons | | -| -------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| Addons | | +| ---------------------------------------------------------------------------------- | ---------------------------------------------------------- | | [info](https://github.com/storybookjs/deprecated-addons/tree/master/addons/info) | Annotate stories with extra component usage information | | [notes](https://github.com/storybookjs/deprecated-addons/tree/master/addons/notes) | Annotate Storybook stories with notes | -| [contexts](https://storybook.js.org/addons/@storybook/addon-contexts/) | Addon for driving your components under dynamic contexts | -| [options](https://www.npmjs.com/package/@storybook/addon-options) | Customize the Storybook UI in code | -| [knobs](https://github.com/storybookjs/addon-knobs) | Interactively edit component prop data in the Storybook UI | +| [contexts](https://storybook.js.org/addons/@storybook/addon-contexts/) | Addon for driving your components under dynamic contexts | +| [options](https://www.npmjs.com/package/@storybook/addon-options) | Customize the Storybook UI in code | +| [knobs](https://github.com/storybookjs/addon-knobs) | Interactively edit component prop data in the Storybook UI | In order to continue improving your experience, we have to eventually deprecate certain addons in favor of new, better tools. diff --git a/addons/a11y/README.md b/addons/a11y/README.md index 15bbaa107841..6135d17dfc5d 100755 --- a/addons/a11y/README.md +++ b/addons/a11y/README.md @@ -22,6 +22,8 @@ module.exports = { }; ``` +And here's a sample story file to test the addon: + ```js import React from 'react'; diff --git a/addons/a11y/package.json b/addons/a11y/package.json index fb899a005b30..9e50b2eeddf2 100644 --- a/addons/a11y/package.json +++ b/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Test component compliance with web accessibility standards", "keywords": [ "a11y", @@ -45,18 +45,18 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/channels": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/channels": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/theming": "6.5.0-alpha.7", "axe-core": "^4.2.0", "core-js": "^3.8.2", "global": "^4.4.0", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "react-sizeme": "^3.0.1", "regenerator-runtime": "^0.13.7", "ts-dedent": "^2.0.0", @@ -81,7 +81,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Accessibility", diff --git a/addons/a11y/src/components/A11YPanel.test.tsx b/addons/a11y/src/components/A11YPanel.test.tsx index b7e77b1e51a3..744e7e418b8b 100644 --- a/addons/a11y/src/components/A11YPanel.test.tsx +++ b/addons/a11y/src/components/A11YPanel.test.tsx @@ -129,11 +129,14 @@ describe('A11YPanel', () => { const { getByText } = render(); const useChannelArgs = mockedApi.useChannel.mock.calls[0][0]; act(() => useChannelArgs[EVENTS.RESULT](axeResult)); - await waitFor(() => { - expect(getByText(/Tests completed/)).toBeTruthy(); - expect(getByText(/Violations/)).toBeTruthy(); - expect(getByText(/Passes/)).toBeTruthy(); - expect(getByText(/Incomplete/)).toBeTruthy(); - }); + await waitFor( + () => { + expect(getByText(/Tests completed/)).toBeTruthy(); + expect(getByText(/Violations/)).toBeTruthy(); + expect(getByText(/Passes/)).toBeTruthy(); + expect(getByText(/Incomplete/)).toBeTruthy(); + }, + { timeout: 2000 } + ); }); }); diff --git a/addons/actions/package.json b/addons/actions/package.json index a089125c6d1a..bb7e0d847e5b 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Get UI feedback when an action is performed on an interactive element", "keywords": [ "storybook", @@ -41,21 +41,21 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", "global": "^4.4.0", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "polished": "^4.0.5", "prop-types": "^15.7.2", "react-inspector": "^5.1.0", "regenerator-runtime": "^0.13.7", - "telejson": "^5.3.2", + "telejson": "^5.3.3", "ts-dedent": "^2.0.0", "util-deprecate": "^1.0.2", "uuid-browser": "^3.1.0" @@ -79,7 +79,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Actions", diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index 81a1f0b33f70..13f1231a79f5 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Switch backgrounds to view components in different settings", "keywords": [ "addon", @@ -45,13 +45,13 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "global": "^4.4.0", "memoizerific": "^1.11.3", @@ -77,7 +77,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Backgrounds", diff --git a/addons/controls/package.json b/addons/controls/package.json index 4ed6f30c4d27..30f914ede7ac 100644 --- a/addons/controls/package.json +++ b/addons/controls/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-controls", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Interact with component inputs dynamically in the Storybook UI", "keywords": [ "addon", @@ -45,17 +45,17 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/node-logger": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/node-logger": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "ts-dedent": "^2.0.0" }, "peerDependencies": { @@ -73,7 +73,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/register.js", "storybook": { "displayName": "Controls", diff --git a/addons/docs/package.json b/addons/docs/package.json index fc181e9c9cb2..ec45e11a0744 100644 --- a/addons/docs/package.json +++ b/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Document component usage and properties in Markdown", "keywords": [ "addon", @@ -63,21 +63,21 @@ "@mdx-js/loader": "^1.6.22", "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/builder-webpack4": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/builder-webpack4": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/csf-tools": "6.4.0-rc.6", - "@storybook/node-logger": "6.4.0-rc.6", - "@storybook/postinstall": "6.4.0-rc.6", - "@storybook/preview-web": "6.4.0-rc.6", - "@storybook/source-loader": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/csf-tools": "6.5.0-alpha.7", + "@storybook/node-logger": "6.5.0-alpha.7", + "@storybook/postinstall": "6.5.0-alpha.7", + "@storybook/preview-web": "6.5.0-alpha.7", + "@storybook/source-loader": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "acorn": "^7.4.1", "acorn-jsx": "^5.3.1", "acorn-walk": "^7.2.0", @@ -89,7 +89,7 @@ "html-tags": "^3.1.0", "js-string-escape": "^1.0.1", "loader-utils": "^2.0.0", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "nanoid": "^3.1.23", "p-limit": "^3.1.0", "prettier": "^2.2.1", @@ -104,13 +104,13 @@ "devDependencies": { "@angular/core": "^11.2.14", "@babel/core": "^7.12.10", - "@emotion/core": "^10.1.1", + "@emotion/core": "^10.3.1", "@emotion/styled": "^10.0.27", - "@storybook/angular": "6.4.0-rc.6", - "@storybook/html": "6.4.0-rc.6", - "@storybook/react": "6.4.0-rc.6", - "@storybook/vue": "6.4.0-rc.6", - "@storybook/web-components": "6.4.0-rc.6", + "@storybook/angular": "6.5.0-alpha.7", + "@storybook/html": "6.5.0-alpha.7", + "@storybook/react": "6.5.0-alpha.7", + "@storybook/vue": "6.5.0-alpha.7", + "@storybook/web-components": "6.5.0-alpha.7", "@types/cross-spawn": "^6.0.2", "@types/doctrine": "^0.0.3", "@types/enzyme": "^3.10.8", @@ -140,12 +140,12 @@ "zone.js": "^0.11.3" }, "peerDependencies": { - "@storybook/angular": "6.4.0-rc.6", - "@storybook/html": "6.4.0-rc.6", - "@storybook/react": "6.4.0-rc.6", - "@storybook/vue": "6.4.0-rc.6", - "@storybook/vue3": "6.4.0-rc.6", - "@storybook/web-components": "6.4.0-rc.6", + "@storybook/angular": "6.5.0-alpha.7", + "@storybook/html": "6.5.0-alpha.7", + "@storybook/react": "6.5.0-alpha.7", + "@storybook/vue": "6.5.0-alpha.7", + "@storybook/vue3": "6.5.0-alpha.7", + "@storybook/web-components": "6.5.0-alpha.7", "lit": "^2.0.0", "lit-html": "^1.4.1 || ^2.0.0", "react": "^16.8.0 || ^17.0.0", @@ -202,7 +202,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Docs", diff --git a/addons/docs/src/blocks/Story.tsx b/addons/docs/src/blocks/Story.tsx index 65fdb19c1e72..879f8ec59b75 100644 --- a/addons/docs/src/blocks/Story.tsx +++ b/addons/docs/src/blocks/Story.tsx @@ -111,6 +111,14 @@ export const getStoryProps = ( }; }; +function makeGate(): [Promise, () => void] { + let open; + const gate = new Promise((r) => { + open = r; + }); + return [gate, open]; +} + const Story: FunctionComponent = (props) => { const context = useContext(DocsContext); const channel = addons.getChannel(); @@ -139,47 +147,63 @@ const Story: FunctionComponent = (props) => { story, renderContext, element: storyRef.current as HTMLElement, + viewMode: 'docs', }); setShowLoader(false); } return () => cleanup && cleanup(); }, [story]); + const [storyFnRan, onStoryFnRan] = makeGate(); + const [rendered, onRendered] = makeGate(); + useEffect(onRendered); + if (!story) { return ; } - // If we are rendering a old-style inline Story via `PureStory` below, we want to emit - // the `STORY_RENDERED` event when it renders. The modern mode below calls out to - // `Preview.renderStoryToDom()` which itself emits the event. - const storyProps = getStoryProps(props, story, context, () => - channel.emit(Events.STORY_RENDERED, storyId) - ); + const storyProps = getStoryProps(props, story, context, onStoryFnRan); if (!storyProps) { return null; } - if (global?.FEATURES?.modernInlineRender) { - // We do this so React doesn't complain when we replace the span in a secondary render - const htmlContents = ``; - - // FIXME: height/style/etc. lifted from PureStory - const { height } = storyProps; - return ( -
- - {height ? ( - - ) : null} - {showLoader && } -
- -
- ); + if (storyProps.inline) { + // If we are rendering a old-style inline Story via `PureStory` below, we want to emit + // the `STORY_RENDERED` event when it renders. The modern mode below calls out to + // `Preview.renderStoryToDom()` which itself emits the event. + if (!global?.FEATURES?.modernInlineRender) { + // We need to wait for two things before we can consider the story rendered: + // (a) React's `useEffect` hook needs to fire. This is needed for React stories, as + // decorators of the form `` will not actually execute `B` in the first + // call to the story function. + // (b) The story function needs to actually have been called. + // Certain frameworks (i.e.angular) don't actually render the component in the very first + // React render cycle, so we need to wait for the framework to actually do that + Promise.all([storyFnRan, rendered]).then(() => { + channel.emit(Events.STORY_RENDERED, storyId); + }); + } else { + // We do this so React doesn't complain when we replace the span in a secondary render + const htmlContents = ``; + + // FIXME: height/style/etc. lifted from PureStory + const { height } = storyProps; + return ( +
+ + {height ? ( + + ) : null} + {showLoader && } +
+ +
+ ); + } } return ( diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc.snapshot b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc-posix.snapshot similarity index 100% rename from addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc.snapshot rename to addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc-posix.snapshot diff --git a/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc-windows.snapshot b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc-windows.snapshot new file mode 100644 index 000000000000..87b561823850 --- /dev/null +++ b/addons/docs/src/frameworks/angular/__testfixtures__/doc-button/compodoc-windows.snapshot @@ -0,0 +1,1297 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`angular component properties doc-button 1`] = ` +Object { + "classes": Array [], + "components": Array [ + Object { + "accessors": Object { + "inputValue": Object { + "getSignature": Object { + "description": "

Getter for inputValue.

+", + "line": 115, + "name": "inputValue", + "rawdescription": "Getter for \`inputValue\`.", + "returnType": "", + "type": "", + }, + "name": "inputValue", + "setSignature": Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "value", + "type": "string", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

Setter for inputValue that is also an @Input.

+", + "jsdoctags": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "value", + "tagName": Object { + "text": "param", + }, + "type": "string", + }, + ], + "line": 110, + "name": "inputValue", + "rawdescription": "Setter for \`inputValue\` that is also an \`@Input\`.", + "returnType": "void", + "type": "void", + }, + }, + "item": Object { + "name": "item", + "setSignature": Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "item", + "type": "T[]", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "item", + "tagName": Object { + "text": "param", + }, + "type": "T[]", + }, + ], + "line": 195, + "name": "item", + "returnType": "void", + "type": "void", + }, + }, + "value": Object { + "getSignature": Object { + "description": "

Get the private value.

+", + "line": 154, + "name": "value", + "rawdescription": "Get the private value.", + "returnType": "string | number", + "type": "", + }, + "name": "value", + "setSignature": Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "value", + "type": "string | number", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

Set the private value.

+", + "jsdoctags": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "value", + "tagName": Object { + "text": "param", + }, + "type": "string | number", + }, + ], + "line": 149, + "name": "value", + "rawdescription": "Set the private value.", + "returnType": "void", + "type": "void", + }, + }, + }, + "assetsDirs": Array [], + "deprecated": false, + "deprecationMessage": "", + "description": "

This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular.

+

It supports markdown, so you can embed formatted text, +like bold, italic, and inline code.

+
+

How you like dem apples?! It's never been easier to document all your components.

+
+", + "encapsulation": Array [], + "entryComponents": Array [], + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "hostBindings": Array [ + Object { + "defaultValue": "false", + "deprecated": false, + "deprecationMessage": "", + "line": 124, + "name": "class.focused", + "type": "boolean", + }, + ], + "hostListeners": Array [ + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "btn", + "type": "", + }, + ], + "argsDecorator": Array [ + "$event.target", + ], + "deprecated": false, + "deprecationMessage": "", + "line": 120, + "name": "click", + }, + ], + "id": "component-InputComponent-fd2eff3e4da750f1c06d4928670993b3", + "inputs": Array [], + "inputsClass": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "

Specify the accent-type of the button

+", + "line": 56, + "name": "accent", + "rawdescription": "Specify the accent-type of the button", + "type": "ButtonAccent", + }, + Object { + "defaultValue": "'secondary'", + "deprecated": false, + "deprecationMessage": "", + "description": "

Appearance style of the button.

+", + "line": 52, + "name": "appearance", + "rawdescription": "Appearance style of the button.", + "type": "\\"primary\\" | \\"secondary\\"", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "

Setter for inputValue that is also an @Input.

+", + "line": 110, + "name": "inputValue", + "rawdescription": "Setter for \`inputValue\` that is also an \`@Input\`.", + "type": "string", + }, + Object { + "defaultValue": "false", + "deprecated": false, + "deprecationMessage": "", + "description": "

Sets the button to a disabled state.

+", + "line": 60, + "name": "isDisabled", + "rawdescription": "Sets the button to a disabled state.", + "type": "boolean", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "line": 195, + "name": "item", + "type": "[]", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "

The inner text of the button.

+", + "jsdoctags": Array [ + Object { + "comment": "", + "end": 1590, + "flags": 4227072, + "kind": 317, + "modifierFlagsCache": 0, + "pos": 1576, + "tagName": Object { + "end": 1585, + "escapedText": "required", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 1577, + "transformFlags": 0, + }, + "transformFlags": 0, + }, + ], + "line": 68, + "name": "label", + "rawdescription": "The inner text of the button.", + "type": "string", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "line": 192, + "name": "showKeyAlias", + "type": "", + }, + Object { + "defaultValue": "'medium'", + "deprecated": false, + "deprecationMessage": "", + "description": "

Size of the button.

+", + "line": 72, + "name": "size", + "rawdescription": "Size of the button.", + "type": "ButtonSize", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "

Specifies some arbitrary object

+", + "line": 75, + "name": "someDataObject", + "rawdescription": "Specifies some arbitrary object", + "type": "ISomeInterface", + }, + Object { + "defaultValue": "false", + "deprecated": true, + "deprecationMessage": "", + "description": "

Some input you shouldn't use.

+", + "jsdoctags": Array [ + Object { + "comment": "", + "end": 1882, + "flags": 4227072, + "kind": 321, + "modifierFlagsCache": 0, + "pos": 1866, + "tagName": Object { + "end": 1877, + "escapedText": "deprecated", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 1867, + "transformFlags": 0, + }, + "transformFlags": 0, + }, + ], + "line": 83, + "name": "somethingYouShouldNotUse", + "rawdescription": "Some input you shouldn't use.", + "type": "boolean", + }, + ], + "methodsClass": Array [ + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "x", + "type": "number", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "y", + "type": "string | number", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

An internal calculation method which adds x and y together.

+", + "jsdoctags": Array [ + Object { + "comment": "

Some number you'd like to use.

+", + "deprecated": false, + "deprecationMessage": "", + "name": Object { + "end": 3678, + "escapedText": "x", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 3677, + "transformFlags": 0, + }, + "tagName": Object { + "end": 3676, + "escapedText": "param", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 3671, + "transformFlags": 0, + }, + "type": "number", + }, + Object { + "comment": "

Some other number or string you'd like to use, will have parseInt() applied before calculation.

+", + "deprecated": false, + "deprecationMessage": "", + "name": Object { + "end": 3724, + "escapedText": "y", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 3723, + "transformFlags": 0, + }, + "tagName": Object { + "end": 3722, + "escapedText": "param", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 3717, + "transformFlags": 0, + }, + "type": "string | number", + }, + ], + "line": 164, + "modifierKind": Array [ + 122, + ], + "name": "calc", + "optional": false, + "rawdescription": " + +An internal calculation method which adds \`x\` and \`y\` together. + +", + "returnType": "number", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "btn", + "type": "", + }, + ], + "decorators": Array [ + Object { + "name": "HostListener", + "stringifiedArguments": "'click', ['$event.target']", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "btn", + "tagName": Object { + "text": "param", + }, + "type": "", + }, + ], + "line": 120, + "name": "onClickListener", + "optional": false, + "returnType": "void", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "password", + "type": "string", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

A private method.

+", + "jsdoctags": Array [ + Object { + "comment": "

Some password.

+", + "deprecated": false, + "deprecationMessage": "", + "name": Object { + "end": 4263, + "escapedText": "password", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 4255, + "transformFlags": 0, + }, + "tagName": Object { + "end": 4254, + "escapedText": "param", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 4249, + "transformFlags": 0, + }, + "type": "string", + }, + ], + "line": 187, + "modifierKind": Array [ + 120, + ], + "name": "privateMethod", + "optional": false, + "rawdescription": " + +A private method. + +", + "returnType": "void", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "id", + "optional": true, + "type": "number", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

A protected method.

+", + "jsdoctags": Array [ + Object { + "comment": "

Some id.

+", + "deprecated": false, + "deprecationMessage": "", + "name": Object { + "end": 4113, + "escapedText": "id", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 4111, + "transformFlags": 0, + }, + "optional": true, + "tagName": Object { + "end": 4110, + "escapedText": "param", + "flags": 4227072, + "kind": 78, + "modifierFlagsCache": 0, + "pos": 4105, + "transformFlags": 0, + }, + "type": "number", + }, + ], + "line": 178, + "modifierKind": Array [ + 121, + ], + "name": "protectedMethod", + "optional": false, + "rawdescription": " + +A protected method. + +", + "returnType": "void", + "typeParameters": Array [], + }, + Object { + "args": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "things", + "type": "ISomeInterface", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "

A public method using an interface.

+", + "jsdoctags": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "things", + "tagName": Object { + "text": "param", + }, + "type": "ISomeInterface", + }, + ], + "line": 169, + "modifierKind": Array [ + 122, + ], + "name": "publicMethod", + "optional": false, + "rawdescription": " +A public method using an interface.", + "returnType": "void", + "typeParameters": Array [], + }, + ], + "name": "InputComponent", + "outputs": Array [], + "outputsClass": Array [ + Object { + "defaultValue": "new EventEmitter()", + "deprecated": false, + "deprecationMessage": "", + "description": "

Handler to be called when the button is clicked by a user.

+

Will also block the emission of the event if isDisabled is true.

+", + "line": 91, + "name": "onClick", + "rawdescription": " + +Handler to be called when the button is clicked by a user. + +Will also block the emission of the event if \`isDisabled\` is true. +", + "type": "EventEmitter", + }, + ], + "propertiesClass": Array [ + Object { + "defaultValue": "'some value'", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 106, + "modifierKind": Array [ + 120, + ], + "name": "_inputValue", + "optional": false, + "type": "string", + }, + Object { + "defaultValue": "'Private hello'", + "deprecated": false, + "deprecationMessage": "", + "description": "

Private value.

+", + "line": 146, + "modifierKind": Array [ + 120, + ], + "name": "_value", + "optional": false, + "rawdescription": " +Private value.", + "type": "string", + }, + Object { + "decorators": Array [ + Object { + "name": "ViewChild", + "stringifiedArguments": "'buttonRef', {static: false}", + }, + ], + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 48, + "name": "buttonRef", + "optional": false, + "type": "ElementRef", + }, + Object { + "decorators": Array [ + Object { + "name": "HostBinding", + "stringifiedArguments": "'class.focused'", + }, + ], + "defaultValue": "false", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 124, + "name": "focus", + "optional": false, + "type": "", + }, + Object { + "defaultValue": "'Public hello'", + "deprecated": false, + "deprecationMessage": "", + "description": "

Public value.

+", + "line": 143, + "modifierKind": Array [ + 122, + ], + "name": "internalProperty", + "optional": false, + "rawdescription": " +Public value.", + "type": "string", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 199, + "modifierKind": Array [ + 122, + ], + "name": "processedItem", + "optional": false, + "type": "T[]", + }, + ], + "providers": Array [], + "rawdescription": " + +This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + +It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, +like **bold**, _italic_, and \`inline code\`. + +> How you like dem apples?! It's never been easier to document all your components. + +", + "selector": "doc-button", + "sourceCode": "import { + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + Output, + ViewChild, +} from '@angular/core'; + +export const exportedConstant = 'An exported constant'; + +export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge'; + +export enum ButtonAccent { + 'Normal' = 'Normal', + 'High' = 'High', +} + +export interface ISomeInterface { + one: string; + two: boolean; + three: any[]; +} + +/** + * This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + * + * It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, + * like **bold**, _italic_, and \`inline code\`. + * + * > How you like dem apples?! It's never been easier to document all your components. + * + * @string Hello world + * @link [Example](http://example.com) + * @code \`ThingThing\` + * @html aaa + */ +@Component({ + selector: 'doc-button', + template: '', +}) +export class InputComponent { + @ViewChild('buttonRef', { static: false }) buttonRef: ElementRef; + + /** Appearance style of the button. */ + @Input() + public appearance: 'primary' | 'secondary' = 'secondary'; + + /** Specify the accent-type of the button */ + @Input() + public accent: ButtonAccent; + + /** Sets the button to a disabled state. */ + @Input() + public isDisabled = false; + + /** + * The inner text of the button. + * + * @required + */ + @Input() + public label: string; + + /** Size of the button. */ + @Input() + public size?: ButtonSize = 'medium'; + + /** Specifies some arbitrary object */ + @Input() public someDataObject: ISomeInterface; + + /** + * Some input you shouldn't use. + * + * @deprecated + */ + @Input() + public somethingYouShouldNotUse = false; + + /** + * Handler to be called when the button is clicked by a user. + * + * Will also block the emission of the event if \`isDisabled\` is true. + */ + @Output() + public onClick = new EventEmitter(); + + /** + * This is an internal method that we don't want to document and have added the \`ignore\` annotation to. + * + * @ignore + */ + public handleClick(event: Event) { + event.stopPropagation(); + + if (!this.isDisabled) { + this.onClick.emit(event); + } + } + + private _inputValue = 'some value'; + + /** Setter for \`inputValue\` that is also an \`@Input\`. */ + @Input() + public set inputValue(value: string) { + this._inputValue = value; + } + + /** Getter for \`inputValue\`. */ + public get inputValue() { + return this._inputValue; + } + + @HostListener('click', ['$event.target']) + onClickListener(btn) { + console.log('button', btn); + } + + @HostBinding('class.focused') focus = false; + + /** + * Returns all the CSS classes for the button. + * + * @ignore + */ + public get classes(): string[] { + return [this.appearance, this.size] + .filter((_class) => !!_class) + .map((_class) => \`btn-\${_class}\`); + } + + /** + * @ignore + */ + public ignoredProperty = 'Ignore me'; + + /** Public value. */ + public internalProperty = 'Public hello'; + + /** Private value. */ + private _value = 'Private hello'; + + /** Set the private value. */ + public set value(value: string | number) { + this._value = \`\${value}\`; + } + + /** Get the private value. */ + public get value(): string | number { + return this._value; + } + + /** + * An internal calculation method which adds \`x\` and \`y\` together. + * + * @param x Some number you'd like to use. + * @param y Some other number or string you'd like to use, will have \`parseInt()\` applied before calculation. + */ + public calc(x: number, y: string | number): number { + return x + parseInt(\`\${y}\`, 10); + } + + /** A public method using an interface. */ + public publicMethod(things: ISomeInterface) { + console.log(things); + } + + /** + * A protected method. + * + * @param id Some \`id\`. + */ + protected protectedMethod(id?: number) { + console.log(id); + } + + /** + * A private method. + * + * @param password Some \`password\`. + */ + private privateMethod(password: string) { + console.log(password); + } + + @Input('showKeyAlias') + public showKey: keyof T; + + @Input() + public set item(item: T[]) { + this.processedItem = item; + } + + public processedItem: T[]; +} +", + "styleUrls": Array [], + "styleUrlsData": "", + "styles": Array [], + "stylesData": "", + "template": "", + "templateUrl": Array [], + "type": "component", + "viewProviders": Array [], + }, + ], + "coverage": Object { + "count": 21, + "files": Array [ + Object { + "coverageCount": "16/25", + "coveragePercent": 64, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linktype": "component", + "name": "InputComponent", + "status": "good", + "type": "component", + }, + Object { + "coverageCount": "0/4", + "coveragePercent": 0, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linktype": "interface", + "name": "ISomeInterface", + "status": "low", + "type": "interface", + }, + Object { + "coverageCount": "0/1", + "coveragePercent": 0, + "filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "linksubtype": "variable", + "linktype": "miscellaneous", + "name": "exportedConstant", + "status": "low", + "type": "variable", + }, + ], + "status": "low", + }, + "directives": Array [], + "guards": Array [], + "injectables": Array [], + "interceptors": Array [], + "interfaces": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "id": "interface-ISomeInterface-fd2eff3e4da750f1c06d4928670993b3", + "indexSignatures": Array [], + "kind": 163, + "methods": Array [], + "name": "ISomeInterface", + "properties": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 25, + "name": "one", + "optional": false, + "type": "string", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 27, + "name": "three", + "optional": false, + "type": "any[]", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "description": "", + "line": 26, + "name": "two", + "optional": false, + "type": "boolean", + }, + ], + "sourceCode": "import { + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + Output, + ViewChild, +} from '@angular/core'; + +export const exportedConstant = 'An exported constant'; + +export type ButtonSize = 'small' | 'medium' | 'large' | 'xlarge'; + +export enum ButtonAccent { + 'Normal' = 'Normal', + 'High' = 'High', +} + +export interface ISomeInterface { + one: string; + two: boolean; + three: any[]; +} + +/** + * This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular. + * + * It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text, + * like **bold**, _italic_, and \`inline code\`. + * + * > How you like dem apples?! It's never been easier to document all your components. + * + * @string Hello world + * @link [Example](http://example.com) + * @code \`ThingThing\` + * @html aaa + */ +@Component({ + selector: 'doc-button', + template: '', +}) +export class InputComponent { + @ViewChild('buttonRef', { static: false }) buttonRef: ElementRef; + + /** Appearance style of the button. */ + @Input() + public appearance: 'primary' | 'secondary' = 'secondary'; + + /** Specify the accent-type of the button */ + @Input() + public accent: ButtonAccent; + + /** Sets the button to a disabled state. */ + @Input() + public isDisabled = false; + + /** + * The inner text of the button. + * + * @required + */ + @Input() + public label: string; + + /** Size of the button. */ + @Input() + public size?: ButtonSize = 'medium'; + + /** Specifies some arbitrary object */ + @Input() public someDataObject: ISomeInterface; + + /** + * Some input you shouldn't use. + * + * @deprecated + */ + @Input() + public somethingYouShouldNotUse = false; + + /** + * Handler to be called when the button is clicked by a user. + * + * Will also block the emission of the event if \`isDisabled\` is true. + */ + @Output() + public onClick = new EventEmitter(); + + /** + * This is an internal method that we don't want to document and have added the \`ignore\` annotation to. + * + * @ignore + */ + public handleClick(event: Event) { + event.stopPropagation(); + + if (!this.isDisabled) { + this.onClick.emit(event); + } + } + + private _inputValue = 'some value'; + + /** Setter for \`inputValue\` that is also an \`@Input\`. */ + @Input() + public set inputValue(value: string) { + this._inputValue = value; + } + + /** Getter for \`inputValue\`. */ + public get inputValue() { + return this._inputValue; + } + + @HostListener('click', ['$event.target']) + onClickListener(btn) { + console.log('button', btn); + } + + @HostBinding('class.focused') focus = false; + + /** + * Returns all the CSS classes for the button. + * + * @ignore + */ + public get classes(): string[] { + return [this.appearance, this.size] + .filter((_class) => !!_class) + .map((_class) => \`btn-\${_class}\`); + } + + /** + * @ignore + */ + public ignoredProperty = 'Ignore me'; + + /** Public value. */ + public internalProperty = 'Public hello'; + + /** Private value. */ + private _value = 'Private hello'; + + /** Set the private value. */ + public set value(value: string | number) { + this._value = \`\${value}\`; + } + + /** Get the private value. */ + public get value(): string | number { + return this._value; + } + + /** + * An internal calculation method which adds \`x\` and \`y\` together. + * + * @param x Some number you'd like to use. + * @param y Some other number or string you'd like to use, will have \`parseInt()\` applied before calculation. + */ + public calc(x: number, y: string | number): number { + return x + parseInt(\`\${y}\`, 10); + } + + /** A public method using an interface. */ + public publicMethod(things: ISomeInterface) { + console.log(things); + } + + /** + * A protected method. + * + * @param id Some \`id\`. + */ + protected protectedMethod(id?: number) { + console.log(id); + } + + /** + * A private method. + * + * @param password Some \`password\`. + */ + private privateMethod(password: string) { + console.log(password); + } + + @Input('showKeyAlias') + public showKey: keyof T; + + @Input() + public set item(item: T[]) { + this.processedItem = item; + } + + public processedItem: T[]; +} +", + "type": "interface", + }, + ], + "miscellaneous": Object { + "enumerations": Array [ + Object { + "childs": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "Normal", + "value": "Normal", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "High", + "value": "High", + }, + ], + "ctype": "miscellaneous", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "ButtonAccent", + "subtype": "enum", + }, + ], + "functions": Array [], + "groupedEnumerations": Object { + "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [ + Object { + "childs": Array [ + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "Normal", + "value": "Normal", + }, + Object { + "deprecated": false, + "deprecationMessage": "", + "name": "High", + "value": "High", + }, + ], + "ctype": "miscellaneous", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "ButtonAccent", + "subtype": "enum", + }, + ], + }, + "groupedFunctions": Object {}, + "groupedTypeAliases": Object { + "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [ + Object { + "ctype": "miscellaneous", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "kind": 183, + "name": "ButtonSize", + "rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"", + "subtype": "typealias", + }, + ], + }, + "groupedVariables": Object { + "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [ + Object { + "ctype": "miscellaneous", + "defaultValue": "'An exported constant'", + "deprecated": false, + "deprecationMessage": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "exportedConstant", + "subtype": "variable", + "type": "string", + }, + ], + }, + "typealiases": Array [ + Object { + "ctype": "miscellaneous", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "kind": 183, + "name": "ButtonSize", + "rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"", + "subtype": "typealias", + }, + ], + "variables": Array [ + Object { + "ctype": "miscellaneous", + "defaultValue": "'An exported constant'", + "deprecated": false, + "deprecationMessage": "", + "file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts", + "name": "exportedConstant", + "subtype": "variable", + "type": "string", + }, + ], + }, + "modules": Array [], + "pipes": Array [], + "routes": Array [], +} +`; diff --git a/addons/docs/src/frameworks/angular/angular-properties.test.ts b/addons/docs/src/frameworks/angular/angular-properties.test.ts index 28789a4edea4..47ce7b685495 100644 --- a/addons/docs/src/frameworks/angular/angular-properties.test.ts +++ b/addons/docs/src/frameworks/angular/angular-properties.test.ts @@ -6,6 +6,8 @@ import { sync as spawnSync } from 'cross-spawn'; import { findComponentByName, extractArgTypesFromData } from './compodoc'; +const { SNAPSHOT_OS } = global; + // File hierarchy: __testfixtures__ / some-test-case / input.* const inputRegExp = /^input\..*$/; @@ -42,7 +44,9 @@ describe('angular component properties', () => { // snapshot the output of compodoc const compodocOutput = runCompodoc(inputPath); const compodocJson = JSON.parse(compodocOutput); - expect(compodocJson).toMatchSpecificSnapshot(path.join(testDir, 'compodoc.snapshot')); + expect(compodocJson).toMatchSpecificSnapshot( + path.join(testDir, `compodoc-${SNAPSHOT_OS}.snapshot`) + ); // snapshot the output of addon-docs angular-properties const componentData = findComponentByName('InputComponent', compodocJson); diff --git a/addons/docs/src/frameworks/common/preset.ts b/addons/docs/src/frameworks/common/preset.ts index 43c7b4fbaf4d..b2369dac9b2f 100644 --- a/addons/docs/src/frameworks/common/preset.ts +++ b/addons/docs/src/frameworks/common/preset.ts @@ -77,7 +77,7 @@ export async function webpack( let rules = module.rules || []; if (transcludeMarkdown) { rules = [ - ...rules.filter((rule: any) => rule.test.toString() !== '/\\.md$/'), + ...rules.filter((rule: any) => rule.test?.toString() !== '/\\.md$/'), { test: /\.md$/, use: [ diff --git a/addons/docs/src/frameworks/vue/extractArgTypes.ts b/addons/docs/src/frameworks/vue/extractArgTypes.ts index eaa981115bbf..4f6e335ce008 100644 --- a/addons/docs/src/frameworks/vue/extractArgTypes.ts +++ b/addons/docs/src/frameworks/vue/extractArgTypes.ts @@ -8,7 +8,7 @@ import { } from '../../lib/docgen'; import { convert } from '../../lib/convert'; -const SECTIONS = ['props', 'events', 'slots']; +const SECTIONS = ['props', 'events', 'slots', 'methods']; /** * Check if "@values" tag is defined within docgenInfo. diff --git a/addons/essentials/package.json b/addons/essentials/package.json index c19079be3f70..c10d8c124a13 100644 --- a/addons/essentials/package.json +++ b/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", @@ -39,31 +39,31 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addon-actions": "6.4.0-rc.6", - "@storybook/addon-backgrounds": "6.4.0-rc.6", - "@storybook/addon-controls": "6.4.0-rc.6", - "@storybook/addon-docs": "6.4.0-rc.6", - "@storybook/addon-measure": "6.4.0-rc.6", - "@storybook/addon-outline": "6.4.0-rc.6", - "@storybook/addon-toolbars": "6.4.0-rc.6", - "@storybook/addon-viewport": "6.4.0-rc.6", - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/node-logger": "6.4.0-rc.6", + "@storybook/addon-actions": "6.5.0-alpha.7", + "@storybook/addon-backgrounds": "6.5.0-alpha.7", + "@storybook/addon-controls": "6.5.0-alpha.7", + "@storybook/addon-docs": "6.5.0-alpha.7", + "@storybook/addon-measure": "6.5.0-alpha.7", + "@storybook/addon-outline": "6.5.0-alpha.7", + "@storybook/addon-toolbars": "6.5.0-alpha.7", + "@storybook/addon-viewport": "6.5.0-alpha.7", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/node-logger": "6.5.0-alpha.7", "core-js": "^3.8.2", "regenerator-runtime": "^0.13.7", "ts-dedent": "^2.0.0" }, "devDependencies": { "@babel/core": "^7.12.10", - "@storybook/vue": "6.4.0-rc.6", + "@storybook/vue": "6.5.0-alpha.7", "@types/jest": "^26.0.16", "@types/webpack-env": "^1.16.0" }, "peerDependencies": { "@babel/core": "^7.9.6", - "@storybook/vue": "6.4.0-rc.6", - "@storybook/web-components": "6.4.0-rc.6", + "@storybook/vue": "6.5.0-alpha.7", + "@storybook/web-components": "6.5.0-alpha.7", "babel-loader": "^8.0.0", "lit-html": "^1.4.1 || ^2.0.0-rc.3", "react": "^16.8.0 || ^17.0.0", @@ -93,6 +93,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js" } diff --git a/addons/interactions/package.json b/addons/interactions/package.json index e1566b9e78e2..40bd672f3f3c 100644 --- a/addons/interactions/package.json +++ b/addons/interactions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-interactions", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Automate, test and debug user interactions", "keywords": [ "storybook-addons", @@ -41,22 +41,22 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/instrumenter": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/instrumenter": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "global": "^4.4.0", "jest-mock": "^27.0.6", "polished": "^4.0.5", "ts-dedent": "^2.2.0" }, "devDependencies": { - "@storybook/jest": "^0.0.2", - "@storybook/testing-library": "^0.0.3", + "@storybook/jest": "^0.0.5", + "@storybook/testing-library": "^0.0.7", "formik": "^2.2.9" }, "peerDependencies": { @@ -74,7 +74,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Interactions", diff --git a/addons/interactions/src/Panel.stories.tsx b/addons/interactions/src/Panel.stories.tsx index 53a3bc7504ba..8e22606d4ecf 100644 --- a/addons/interactions/src/Panel.stories.tsx +++ b/addons/interactions/src/Panel.stories.tsx @@ -1,10 +1,12 @@ import React from 'react'; +import { action } from '@storybook/addon-actions'; import { ComponentStoryObj, ComponentMeta } from '@storybook/react'; import { CallStates } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; import { getCall } from './mocks'; import { AddonPanelPure } from './Panel'; +import SubnavStories from './components/Subnav/Subnav.stories'; const StyledWrapper = styled.div(({ theme }) => ({ backgroundColor: theme.background.content, @@ -33,16 +35,14 @@ export default { }, args: { calls: new Map(), - endRef: null, + controls: SubnavStories.args.controls, + controlStates: SubnavStories.args.controlStates, + interactions: [getCall(CallStates.DONE)], fileName: 'addon-interactions.stories.tsx', hasException: false, - hasNext: false, - hasPrevious: true, - interactions: [getCall(CallStates.DONE)], - isDisabled: false, isPlaying: false, - showTabIcon: false, - isDebuggingEnabled: true, + onScrollToEnd: action('onScrollToEnd'), + endRef: null, // prop for the AddonPanel used as wrapper of Panel active: true, }, @@ -60,6 +60,14 @@ export const Paused: Story = { args: { isPlaying: true, interactions: [getCall(CallStates.WAITING)], + controlStates: { + debugger: true, + start: false, + back: false, + goto: true, + next: true, + end: true, + }, }, }; @@ -78,7 +86,7 @@ export const Failed: Story = { }; export const WithDebuggingDisabled: Story = { - args: { isDebuggingEnabled: false }, + args: { controlStates: { ...SubnavStories.args.controlStates, debugger: false } }, }; export const NoInteractions: Story = { diff --git a/addons/interactions/src/Panel.tsx b/addons/interactions/src/Panel.tsx index 8b21f9050592..722715504e8f 100644 --- a/addons/interactions/src/Panel.tsx +++ b/addons/interactions/src/Panel.tsx @@ -1,17 +1,23 @@ import global from 'global'; import * as React from 'react'; import ReactDOM from 'react-dom'; -import { useChannel, useParameter, useStorybookState } from '@storybook/api'; +import { useChannel, useParameter, StoryId } from '@storybook/api'; import { STORY_RENDER_PHASE_CHANGED } from '@storybook/core-events'; import { AddonPanel, Link, Placeholder } from '@storybook/components'; -import { EVENTS, Call, CallStates, LogItem } from '@storybook/instrumenter'; +import { EVENTS, Call, CallStates, ControlStates, LogItem } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; import { StatusIcon } from './components/StatusIcon/StatusIcon'; import { Subnav } from './components/Subnav/Subnav'; import { Interaction } from './components/Interaction/Interaction'; -const { FEATURES } = global; +export interface Controls { + start: (args: any) => void; + back: (args: any) => void; + goto: (args: any) => void; + next: (args: any) => void; + end: (args: any) => void; +} interface AddonPanelProps { active: boolean; @@ -19,26 +25,25 @@ interface AddonPanelProps { interface InteractionsPanelProps { active: boolean; - interactions: (Call & { state?: CallStates })[]; - isDisabled?: boolean; - hasPrevious?: boolean; - hasNext?: boolean; + controls: Controls; + controlStates: ControlStates; + interactions: (Call & { status?: CallStates })[]; fileName?: string; hasException?: boolean; isPlaying?: boolean; calls: Map; endRef?: React.Ref; - isDebuggingEnabled?: boolean; - onStart?: () => void; - onPrevious?: () => void; - onNext?: () => void; - onEnd?: () => void; onScrollToEnd?: () => void; - onInteractionClick?: (callId: string) => void; } -const pendingStates = [CallStates.ACTIVE, CallStates.WAITING]; -const completedStates = [CallStates.DONE, CallStates.ERROR]; +const INITIAL_CONTROL_STATES = { + debugger: false, + start: false, + back: false, + goto: false, + next: false, + end: false, +}; const TabIcon = styled(StatusIcon)({ marginLeft: 5, @@ -51,39 +56,27 @@ const TabStatus = ({ children }: { children: React.ReactChild }) => { export const AddonPanelPure: React.FC = React.memo( ({ + calls, + controls, + controlStates, interactions, - isDisabled, - hasPrevious, - hasNext, fileName, hasException, isPlaying, - onStart, - onPrevious, - onNext, - onEnd, onScrollToEnd, - calls, - onInteractionClick, endRef, - isDebuggingEnabled, ...panelProps }) => ( - {isDebuggingEnabled && interactions.length > 0 && ( + {controlStates.debugger && interactions.length > 0 && ( )} @@ -92,9 +85,8 @@ export const AddonPanelPure: React.FC = React.memo( key={call.id} call={call} callsById={calls} - isDebuggingEnabled={isDebuggingEnabled} - isDisabled={isDisabled} - onClick={() => onInteractionClick(call.id)} + controls={controls} + controlStates={controlStates} /> ))}
@@ -115,15 +107,17 @@ export const AddonPanelPure: React.FC = React.memo( ); export const Panel: React.FC = (props) => { - const [isLocked, setLock] = React.useState(false); - const [isPlaying, setPlaying] = React.useState(true); + const [storyId, setStoryId] = React.useState(); + const [controlStates, setControlStates] = React.useState(INITIAL_CONTROL_STATES); + const [isPlaying, setPlaying] = React.useState(false); const [scrollTarget, setScrollTarget] = React.useState(); - const calls = React.useRef>>(new Map()); - const setCall = ({ state, ...call }: Call) => calls.current.set(call.id, call); + // Calls are tracked in a ref so we don't needlessly rerender. + const calls = React.useRef>>(new Map()); + const setCall = ({ status, ...call }: Call) => calls.current.set(call.id, call); const [log, setLog] = React.useState([]); - const interactions = log.map(({ callId, state }) => ({ ...calls.current.get(callId), state })); + const interactions = log.map(({ callId, status }) => ({ ...calls.current.get(callId), status })); const endRef = React.useRef(); React.useEffect(() => { @@ -135,41 +129,38 @@ export const Panel: React.FC = (props) => { return () => observer.disconnect(); }, []); - const emit = useChannel({ - [EVENTS.CALL]: setCall, - [EVENTS.SYNC]: setLog, - [EVENTS.LOCK]: setLock, - [STORY_RENDER_PHASE_CHANGED]: ({ newPhase }) => { - setLock(false); - setPlaying(newPhase === 'playing'); + const emit = useChannel( + { + [EVENTS.CALL]: setCall, + [EVENTS.SYNC]: (payload) => { + setControlStates(payload.controlStates); + setLog(payload.logItems); + }, + [STORY_RENDER_PHASE_CHANGED]: (event) => { + setStoryId(event.storyId); + setPlaying(event.newPhase === 'playing'); + }, }, - }); + [] + ); + + const controls = React.useMemo( + () => ({ + start: () => emit(EVENTS.START, { storyId }), + back: () => emit(EVENTS.BACK, { storyId }), + goto: (callId: string) => emit(EVENTS.GOTO, { storyId, callId }), + next: () => emit(EVENTS.NEXT, { storyId }), + end: () => emit(EVENTS.END, { storyId }), + }), + [storyId] + ); - const { storyId } = useStorybookState(); const storyFilePath = useParameter('fileName', ''); const [fileName] = storyFilePath.toString().split('/').slice(-1); const scrollToTarget = () => scrollTarget?.scrollIntoView({ behavior: 'smooth', block: 'end' }); - const isDebuggingEnabled = FEATURES.interactionsDebugger === true; - const showStatus = log.length > 0 && !isPlaying; - const isDebugging = log.some((item) => pendingStates.includes(item.state)); - const hasPrevious = log.some((item) => completedStates.includes(item.state)); - const hasNext = log.some((item) => item.state === CallStates.WAITING); - const hasActive = log.some((item) => item.state === CallStates.ACTIVE); - const hasException = log.some((item) => item.state === CallStates.ERROR); - const isDisabled = isDebuggingEnabled - ? hasActive || isLocked || (isPlaying && !isDebugging) - : true; - - const onStart = React.useCallback(() => emit(EVENTS.START, { storyId }), [storyId]); - const onPrevious = React.useCallback(() => emit(EVENTS.BACK, { storyId }), [storyId]); - const onNext = React.useCallback(() => emit(EVENTS.NEXT, { storyId }), [storyId]); - const onEnd = React.useCallback(() => emit(EVENTS.END, { storyId }), [storyId]); - const onInteractionClick = React.useCallback( - (callId: string) => emit(EVENTS.GOTO, { storyId, callId }), - [storyId] - ); + const hasException = log.some((item) => item.status === CallStates.ERROR); return ( @@ -178,21 +169,14 @@ export const Panel: React.FC = (props) => { (hasException ? : ` (${interactions.length})`)} diff --git a/addons/interactions/src/components/AccountForm/addon-interactions.stories.tsx b/addons/interactions/src/components/AccountForm/addon-interactions.stories.tsx index c1d32f82da9c..0ee5b32d843a 100644 --- a/addons/interactions/src/components/AccountForm/addon-interactions.stories.tsx +++ b/addons/interactions/src/components/AccountForm/addon-interactions.stories.tsx @@ -37,6 +37,19 @@ Demo.play = async ({ args, canvasElement }) => { await expect(args.onSubmit).toHaveBeenCalledWith(expect.stringMatching(/([A-Z])\w+/gi)); }; +export const FindBy: CSF2Story = (args) => { + const [isLoading, setIsLoading] = React.useState(true); + React.useEffect(() => { + setTimeout(() => setIsLoading(false), 500); + }, []); + return isLoading ?
Loading...
: ; +}; +FindBy.play = async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByRole('button'); + await expect(true).toBe(true); +}; + export const WaitFor: CSF2Story = (args) => ( ; }; WaitForElementToBeRemoved.play = async ({ canvasElement }) => { diff --git a/addons/interactions/src/components/Interaction/Interaction.stories.tsx b/addons/interactions/src/components/Interaction/Interaction.stories.tsx index 751841e7c764..3fc5ab83b5ef 100644 --- a/addons/interactions/src/components/Interaction/Interaction.stories.tsx +++ b/addons/interactions/src/components/Interaction/Interaction.stories.tsx @@ -5,6 +5,7 @@ import { userEvent, within } from '@storybook/testing-library'; import { getCall } from '../../mocks'; import { Interaction } from './Interaction'; +import SubnavStories from '../Subnav/Subnav.stories'; type Story = ComponentStoryObj; @@ -13,8 +14,8 @@ export default { component: Interaction, args: { callsById: new Map(), - isDisabled: false, - isDebuggingEnabled: true, + controls: SubnavStories.args.controls, + controlStates: SubnavStories.args.controlStates, }, } as ComponentMeta; @@ -43,7 +44,7 @@ export const Done: Story = { }; export const Disabled: Story = { - args: { ...Done.args, isDisabled: true }, + args: { ...Done.args, controlStates: { ...SubnavStories.args.controlStates, goto: false } }, }; export const Hovered: Story = { diff --git a/addons/interactions/src/components/Interaction/Interaction.tsx b/addons/interactions/src/components/Interaction/Interaction.tsx index f9a9dc366d38..d9d6bf27ddb1 100644 --- a/addons/interactions/src/components/Interaction/Interaction.tsx +++ b/addons/interactions/src/components/Interaction/Interaction.tsx @@ -1,15 +1,18 @@ import * as React from 'react'; -import { Call, CallStates } from '@storybook/instrumenter'; +import { Call, CallStates, ControlStates } from '@storybook/instrumenter'; import { styled, typography } from '@storybook/theming'; import { transparentize } from 'polished'; import { MatcherResult } from '../MatcherResult'; import { MethodCall } from '../MethodCall'; import { StatusIcon } from '../StatusIcon/StatusIcon'; +import { Controls } from '../../Panel'; const MethodCallWrapper = styled.div(() => ({ fontFamily: typography.fonts.mono, fontSize: typography.size.s1, + overflowWrap: 'break-word', + inlineSize: 'calc( 100% - 40px )', })); const RowContainer = styled('div', { shouldForwardProp: (prop) => !['call'].includes(prop) })<{ @@ -20,7 +23,7 @@ const RowContainer = styled('div', { shouldForwardProp: (prop) => !['call'].incl borderBottom: `1px solid ${theme.appBorderColor}`, fontFamily: typography.fonts.base, fontSize: 13, - ...(call.state === CallStates.ERROR && { + ...(call.status === CallStates.ERROR && { backgroundColor: theme.base === 'dark' ? transparentize(0.93, theme.color.negative) : theme.background.warning, }), @@ -38,19 +41,17 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu margin: 0, padding: '8px 15px', textAlign: 'start', - cursor: disabled || call.state === CallStates.ERROR ? 'default' : 'pointer', - '&:hover': { - background: theme.background.hoverable, - }, + cursor: disabled || call.status === CallStates.ERROR ? 'default' : 'pointer', + '&:hover': disabled ? {} : { background: theme.background.hoverable }, '&:focus-visible': { outline: 0, boxShadow: `inset 3px 0 0 0 ${ - call.state === CallStates.ERROR ? theme.color.warning : theme.color.secondary + call.status === CallStates.ERROR ? theme.color.warning : theme.color.secondary }`, - background: call.state === CallStates.ERROR ? 'transparent' : theme.background.hoverable, + background: call.status === CallStates.ERROR ? 'transparent' : theme.background.hoverable, }, '& > div': { - opacity: call.state === CallStates.WAITING ? 0.5 : 1, + opacity: call.status === CallStates.WAITING ? 0.5 : 1, }, })); @@ -63,32 +64,30 @@ const RowMessage = styled('pre')({ export const Interaction = ({ call, callsById, - onClick, - isDisabled, - isDebuggingEnabled, + controls, + controlStates, }: { call: Call; callsById: Map; - onClick: React.MouseEventHandler; - isDisabled: boolean; - isDebuggingEnabled?: boolean; + controls: Controls; + controlStates: ControlStates; }) => { const [isHovered, setIsHovered] = React.useState(false); return ( isDebuggingEnabled && setIsHovered(true)} - onMouseLeave={() => isDebuggingEnabled && setIsHovered(false)} + onClick={() => controls.goto(call.id)} + disabled={!controlStates.goto} + onMouseEnter={() => controlStates.goto && setIsHovered(true)} + onMouseLeave={() => controlStates.goto && setIsHovered(false)} > - + - {call.state === CallStates.ERROR && + {call.status === CallStates.ERROR && call.exception && (call.exception.message.startsWith('expect(') ? ( diff --git a/addons/interactions/src/components/StatusBadge/StatusBadge.tsx b/addons/interactions/src/components/StatusBadge/StatusBadge.tsx index a535875c233c..4c28636166f3 100644 --- a/addons/interactions/src/components/StatusBadge/StatusBadge.tsx +++ b/addons/interactions/src/components/StatusBadge/StatusBadge.tsx @@ -3,7 +3,7 @@ import { Call, CallStates } from '@storybook/instrumenter'; import { styled, typography } from '@storybook/theming'; export interface StatusBadgeProps { - status: Call['state']; + status: Call['status']; } const StyledBadge = styled.div(({ theme, status }) => { diff --git a/addons/interactions/src/components/StatusIcon/StatusIcon.tsx b/addons/interactions/src/components/StatusIcon/StatusIcon.tsx index 9aed8af4df2d..d26d7bd493bc 100644 --- a/addons/interactions/src/components/StatusIcon/StatusIcon.tsx +++ b/addons/interactions/src/components/StatusIcon/StatusIcon.tsx @@ -7,7 +7,7 @@ import { transparentize } from 'polished'; import localTheme from '../../theme'; export interface StatusIconProps extends IconsProps { - status: Call['state']; + status: Call['status']; } const { diff --git a/addons/interactions/src/components/Subnav/Subnav.stories.tsx b/addons/interactions/src/components/Subnav/Subnav.stories.tsx index b9c6f6e81896..9605bc53252c 100644 --- a/addons/interactions/src/components/Subnav/Subnav.stories.tsx +++ b/addons/interactions/src/components/Subnav/Subnav.stories.tsx @@ -1,3 +1,4 @@ +import { action } from '@storybook/addon-actions'; import { CallStates } from '@storybook/instrumenter'; import { Subnav } from './Subnav'; @@ -5,11 +6,21 @@ export default { title: 'Addons/Interactions/Subnav', component: Subnav, args: { - onPrevious: () => {}, - onNext: () => {}, - onReplay: () => {}, - goToStart: () => {}, - goToEnd: () => {}, + controls: { + start: action('start'), + back: action('back'), + goto: action('goto'), + next: action('next'), + end: action('end'), + }, + controlStates: { + debugger: true, + start: true, + back: true, + goto: true, + next: false, + end: false, + }, storyFileName: 'Subnav.stories.tsx', hasNext: true, hasPrevious: true, @@ -34,18 +45,44 @@ export const Runs = { }, }; -export const AtTheBeginning = { - name: 'at the beginning', +export const AtStart = { args: { - status: CallStates.DONE, - hasPrevious: false, + status: CallStates.WAITING, + controlStates: { + debugger: true, + start: false, + back: false, + goto: true, + next: true, + end: true, + }, }, }; -export const AtTheEnd = { - name: 'at the end', +export const Midway = { args: { - status: CallStates.DONE, - hasNext: false, + status: CallStates.WAITING, + controlStates: { + debugger: true, + start: true, + back: true, + goto: true, + next: true, + end: true, + }, + }, +}; + +export const Locked = { + args: { + status: CallStates.ACTIVE, + controlStates: { + debugger: true, + start: false, + back: false, + goto: false, + next: false, + end: false, + }, }, }; diff --git a/addons/interactions/src/components/Subnav/Subnav.tsx b/addons/interactions/src/components/Subnav/Subnav.tsx index c4fe801ccf85..e35b133fbf69 100644 --- a/addons/interactions/src/components/Subnav/Subnav.tsx +++ b/addons/interactions/src/components/Subnav/Subnav.tsx @@ -9,34 +9,33 @@ import { WithTooltip, Bar, } from '@storybook/components'; -import { Call, CallStates } from '@storybook/instrumenter'; +import { Call, CallStates, ControlStates } from '@storybook/instrumenter'; import { styled } from '@storybook/theming'; import { StatusBadge } from '../StatusBadge/StatusBadge'; +import { Controls } from '../../Panel'; -const StyledSubnav = styled.nav(({ theme }) => ({ +const SubnavWrapper = styled.div(({ theme }) => ({ background: theme.background.app, borderBottom: `1px solid ${theme.appBorderColor}`, + position: 'sticky', + top: 0, + zIndex: 1, +})); + +const StyledSubnav = styled.nav(({ theme }) => ({ height: 40, display: 'flex', alignItems: 'center', justifyContent: 'space-between', paddingLeft: 15, - position: 'sticky', - top: 0, - zIndex: 1, })); export interface SubnavProps { - isDisabled: boolean; - hasPrevious: boolean; - hasNext: boolean; + controls: Controls; + controlStates: ControlStates; + status: Call['status']; storyFileName?: string; - status: Call['state']; - onStart: () => void; - onPrevious: () => void; - onNext: () => void; - onEnd: () => void; onScrollToEnd?: () => void; } @@ -55,7 +54,7 @@ const Note = styled(TooltipNote)(({ theme }) => ({ fontFamily: theme.typography.fonts.base, })); -export const StyledIconButton = styled(IconButton)(({ theme }) => ({ +export const StyledIconButton = styled(IconButton as any)(({ theme }) => ({ color: theme.color.mediumdark, margin: '0 3px', })); @@ -68,6 +67,7 @@ const StyledLocation = styled(P)(({ theme }) => ({ color: theme.textMutedColor, justifyContent: 'flex-end', textAlign: 'right', + whiteSpace: 'nowrap', marginTop: 'auto', marginBottom: 1, paddingRight: 15, @@ -90,97 +90,59 @@ const JumpToEndButton = styled(StyledButton)({ lineHeight: '12px', }); -const withTooltipModifiers = [ - { - name: 'preventOverflow', - options: { - padding: 0, - }, - }, - { - name: 'offset', - options: { - offset: [0, -2], - }, - }, -]; - export const Subnav: React.FC = ({ - isDisabled, - hasNext, - hasPrevious, - storyFileName, + controls, + controlStates, status, - onStart, - onPrevious, - onNext, - onEnd, + storyFileName, onScrollToEnd, }) => { const buttonText = status === CallStates.ERROR ? 'Scroll to error' : 'Scroll to end'; return ( - - - - - - - {buttonText} - - - - - } - > - - - - - - } - > - - - - - - } - > - - - - - - } - > - - - - - - {storyFileName && ( + + + - {storyFileName} + + + + {buttonText} + + + + + }> + + + + + + }> + + + + + + }> + + + + + + }> + + + + - )} - - + {storyFileName && ( + + {storyFileName} + + )} + + + ); }; diff --git a/addons/interactions/src/mocks/index.ts b/addons/interactions/src/mocks/index.ts index 591fdd9cfbce..7952b6ecf896 100644 --- a/addons/interactions/src/mocks/index.ts +++ b/addons/interactions/src/mocks/index.ts @@ -1,8 +1,9 @@ import { CallStates, Call } from '@storybook/instrumenter'; -export const getCall = (state: CallStates): Call => { +export const getCall = (status: CallStates): Call => { const defaultData = { id: 'addons-interactions-accountform--standard-email-filled [3] change', + cursor: 0, path: ['fireEvent'], method: 'change', storyId: 'addons-interactions-accountform--standard-email-filled', @@ -19,11 +20,11 @@ export const getCall = (state: CallStates): Call => { ], interceptable: true, retain: false, - state, + status, }; const overrides = CallStates.ERROR - ? { exception: { callId: '', stack: '', message: "Things didn't work!" } } + ? { exception: { name: 'Error', stack: '', message: "Things didn't work!" } } : {}; return { ...defaultData, ...overrides }; diff --git a/addons/interactions/tsconfig.json b/addons/interactions/tsconfig.json new file mode 100644 index 000000000000..d1ee4fc75941 --- /dev/null +++ b/addons/interactions/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.*", + "src/**/tests/**/*", + "src/**/__tests__/**/*", + "src/**/*.stories.*", + "src/**/*.mockdata.*", + "src/**/__testfixtures__/**" + ] +} diff --git a/addons/jest/README.md b/addons/jest/README.md index 5a75fce42ca5..1f224a8d1b0d 100644 --- a/addons/jest/README.md +++ b/addons/jest/README.md @@ -46,7 +46,7 @@ You may want to add the result file to `.gitignore`, since it's a generated file But much like lockfiles and snapshots, checking-in generated files can have certain advantages as well. It's up to you. We recommend to **do** check in the test results file so starting Storybook from a clean git clone doesn't require running all tests first, -but this can mean you'll encounter merge conflicts on this file in the future (_re-generating this file is very similar to re-generating lockfiles and snapshots_). +but this can mean you'll encounter merge conflicts on this file in the future (_re-generating this file is very similar to re-generating lockfiles and snapshots_). ### Generating the test results @@ -101,17 +101,17 @@ You can also add multiple tests results within your story by including the `jest ```js // MyComponent.stories.js | MyComponent.stories.jsx -import MyComponent from './MyComponent'; +import MyComponent from './MyComponent'; -import results from '../.jest-test-results.json'; +import results from '../.jest-test-results.json'; -import { withTests } from '@storybook/addon-jest'; +import { withTests } from '@storybook/addon-jest'; -export default { - component: MyComponent, - title: 'MyComponent', +export default { + component: MyComponent, + title: 'MyComponent', decorators: [withTests({ results })], -}; +}; const Template = (args) => ; @@ -126,7 +126,7 @@ Default.parameters = { ### Global level -To avoid importing the results of the tests in each story, you can update +To avoid importing the results of the tests in each story, you can update your [`.storybook/preview.js`](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) and include a decorator allowing you to display the results only for the stories that have the `jest` parameter defined: ```js @@ -140,7 +140,7 @@ export const decorators = [ withTests({ results, }), -]; +]; ``` Then in your story file: @@ -162,7 +162,7 @@ Default.args={ text: 'Jest results in Storybook', }; Default.parameters = { - jest: ['MyComponent.test.js'], + jest: 'MyComponent.test.js', }; ``` @@ -230,7 +230,7 @@ const Template: Story = (args: MyComponent) => ({ export const Default = Template.bind({}); Default.parameters = { - jest: ['MyComponent.component'], + jest: 'MyComponent.component', }; ``` diff --git a/addons/jest/package.json b/addons/jest/package.json index 9524564c9e1b..f28f1c2610ee 100644 --- a/addons/jest/package.json +++ b/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -47,11 +47,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "global": "^4.4.0", "react-sizeme": "^3.0.1", @@ -76,7 +76,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Jest", diff --git a/addons/links/package.json b/addons/links/package.json index 86781c202ffb..8166dfac8c5b 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Link stories together to build demos and prototypes with your UI components", "keywords": [ "addon", @@ -41,11 +41,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/router": "6.4.0-rc.6", + "@storybook/router": "6.5.0-alpha.7", "@types/qs": "^6.9.5", "core-js": "^3.8.2", "global": "^4.4.0", @@ -72,7 +72,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Links", diff --git a/addons/measure/package.json b/addons/measure/package.json index 4817802ba839..b0d35eaa4b78 100644 --- a/addons/measure/package.json +++ b/addons/measure/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-measure", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Inspect layouts by visualizing the box model", "keywords": [ "storybook-addons", @@ -44,11 +44,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", "core-js": "^3.8.2", "global": "^4.4.0" @@ -71,7 +71,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Measure", diff --git a/addons/measure/src/box-model/canvas.ts b/addons/measure/src/box-model/canvas.ts index 08387ec05a64..e35ee8a6eac8 100644 --- a/addons/measure/src/box-model/canvas.ts +++ b/addons/measure/src/box-model/canvas.ts @@ -32,7 +32,7 @@ function createCanvas(): CanvasState { canvas.style.position = 'absolute'; canvas.style.left = '0'; canvas.style.top = '0'; - canvas.style.zIndex = '100000'; + canvas.style.zIndex = '2147483647'; // Disable any user interactions canvas.style.pointerEvents = 'none'; global.document.body.appendChild(canvas); diff --git a/addons/outline/package.json b/addons/outline/package.json index 06156a520627..beacf8f64cd8 100644 --- a/addons/outline/package.json +++ b/addons/outline/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-outline", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Outline all elements with CSS to help with layout placement and alignment", "keywords": [ "storybook-addons", @@ -47,11 +47,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -76,7 +76,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Outline", diff --git a/addons/storyshots/storyshots-core/package.json b/addons/storyshots/storyshots-core/package.json index aea4253150a7..271b5d6a6aa6 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.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Take a code snapshot of every story automatically with Jest", "keywords": [ "addon", @@ -45,12 +45,12 @@ }, "dependencies": { "@jest/transform": "^26.6.2", - "@storybook/addons": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", "@storybook/babel-plugin-require-context-hook": "1.0.1", - "@storybook/client-api": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-client": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/client-api": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-client": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", "@types/glob": "^7.1.3", "@types/jest": "^26.0.16", @@ -69,11 +69,11 @@ "devDependencies": { "@angular/core": "^11.2.0", "@angular/platform-browser-dynamic": "^11.2.0", - "@storybook/addon-docs": "6.4.0-rc.6", - "@storybook/angular": "6.4.0-rc.6", - "@storybook/react": "6.4.0-rc.6", - "@storybook/vue": "6.4.0-rc.6", - "@storybook/vue3": "6.4.0-rc.6", + "@storybook/addon-docs": "6.5.0-alpha.7", + "@storybook/angular": "6.5.0-alpha.7", + "@storybook/react": "6.5.0-alpha.7", + "@storybook/vue": "6.5.0-alpha.7", + "@storybook/vue3": "6.5.0-alpha.7", "babel-loader": "^8.0.0", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.1", @@ -151,7 +151,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "storybook": { "displayName": "Storyshots", "icon": "https://user-images.githubusercontent.com/263385/101991676-48cdf300-3c7c-11eb-8aa1-944dab6ab29b.png", diff --git a/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts b/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts index 76c88463876a..509aa5a13b24 100644 --- a/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts +++ b/addons/storyshots/storyshots-core/src/frameworks/configure.test.ts @@ -1,3 +1,4 @@ +import path from 'path'; import { getPreviewFile, getMainFile } from './configure'; // eslint-disable-next-line global-require, jest/no-mocks-import @@ -19,9 +20,9 @@ describe('preview files', () => { ${'config.js'} ${'config.jsx'} `('resolves a valid preview file from $filepath', ({ filepath }) => { - setupFiles({ [`test/${filepath}`]: 'true' }); + setupFiles({ [path.join('test', filepath)]: 'true' }); - expect(getPreviewFile('test/')).toEqual(`test/${filepath}`); + expect(getPreviewFile('test/')).toEqual(`test${path.sep}${filepath}`); }); it('returns false when none of the paths exist', () => { @@ -39,9 +40,9 @@ describe('main files', () => { ${'main.js'} ${'main.jsx'} `('resolves a valid main file path from $filepath', ({ filepath }) => { - setupFiles({ [`test/${filepath}`]: 'true' }); + setupFiles({ [path.join('test', filepath)]: 'true' }); - expect(getMainFile('test/')).toEqual(`test/${filepath}`); + expect(getMainFile('test/')).toEqual(`test${path.sep}${filepath}`); }); it('returns false when none of the paths exist', () => { diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index 214165a89bd8..8f53c6c7f83d 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.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Image snapshots addition to StoryShots based on puppeteer", "keywords": [ "addon", @@ -42,7 +42,7 @@ "dependencies": { "@axe-core/puppeteer": "^4.2.0", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/node-logger": "6.4.0-rc.6", + "@storybook/node-logger": "6.5.0-alpha.7", "@types/jest-image-snapshot": "^4.1.3", "core-js": "^3.8.2", "jest-image-snapshot": "^4.3.0", @@ -53,7 +53,7 @@ "@types/puppeteer": "^5.4.0" }, "peerDependencies": { - "@storybook/addon-storyshots": "6.4.0-rc.6", + "@storybook/addon-storyshots": "6.5.0-alpha.7", "puppeteer": "^2.0.0 || ^3.0.0" }, "peerDependenciesMeta": { @@ -64,5 +64,5 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470" + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6" } diff --git a/addons/storysource/package.json b/addons/storysource/package.json index 6a6877faa3fc..e5058dada762 100644 --- a/addons/storysource/package.json +++ b/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "View a story’s source code to see how it works and paste into your app", "keywords": [ "addon", @@ -41,19 +41,19 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/router": "6.4.0-rc.6", - "@storybook/source-loader": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/router": "6.5.0-alpha.7", + "@storybook/source-loader": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "estraverse": "^5.2.0", "loader-utils": "^2.0.0", "prettier": "^2.2.1", "prop-types": "^15.7.2", - "react-syntax-highlighter": "^13.5.3", + "react-syntax-highlighter": "^15.4.2", "regenerator-runtime": "^0.13.7" }, "devDependencies": { @@ -75,7 +75,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/index.js", "storybook": { "displayName": "Storysource", diff --git a/addons/toolbars/package.json b/addons/toolbars/package.json index 1acb0a1e87e6..a6c9dbfe5cc5 100644 --- a/addons/toolbars/package.json +++ b/addons/toolbars/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-toolbars", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Create your own toolbar items that control story rendering", "keywords": [ "addon", @@ -45,10 +45,10 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "regenerator-runtime": "^0.13.7" }, @@ -67,7 +67,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/register.js", "storybook": { "displayName": "Toolbars", diff --git a/addons/viewport/package.json b/addons/viewport/package.json index d7e53c55709b..95777ffeb61a 100644 --- a/addons/viewport/package.json +++ b/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Build responsive components by adjusting Storybook’s viewport size and orientation", "keywords": [ "addon", @@ -42,12 +42,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-logger": "6.4.0-rc.6", - "@storybook/components": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", - "@storybook/theming": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-logger": "6.5.0-alpha.7", + "@storybook/components": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", + "@storybook/theming": "6.5.0-alpha.7", "core-js": "^3.8.2", "global": "^4.4.0", "memoizerific": "^1.11.3", @@ -69,7 +69,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/preview.js", "storybook": { "displayName": "Viewport", diff --git a/app/angular/package.json b/app/angular/package.json index 132e19c22afb..e447ff5d9db3 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,15 +45,16 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", - "@storybook/core-events": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", + "@storybook/core-events": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/node-logger": "6.4.0-rc.6", + "@storybook/node-logger": "6.5.0-alpha.7", "@storybook/semver": "^7.3.2", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "autoprefixer": "^9.8.6", "core-js": "^3.8.2", @@ -69,7 +70,7 @@ "regenerator-runtime": "^0.13.7", "sass-loader": "^10.1.0", "strip-json-comments": "3.1.1", - "telejson": "^5.3.2", + "telejson": "^5.3.3", "ts-dedent": "^2.0.0", "ts-loader": "^8.0.14", "tsconfig-paths-webpack-plugin": "^3.3.0", @@ -99,7 +100,7 @@ }, "peerDependencies": { "@angular-devkit/architect": ">=0.8.9", - "@angular-devkit/build-angular": ">=0.8.9", + "@angular-devkit/build-angular": ">=0.8.9 || >= 12.0.0", "@angular-devkit/core": "^0.6.1 || >=7.0.0", "@angular/cli": ">=6.0.0", "@angular/common": ">=6.0.0", @@ -113,7 +114,7 @@ "@babel/core": "*", "@nrwl/workspace": ">=11.1.0", "@webcomponents/custom-elements": ">=1.4.3", - "rxjs": "^6.0.0", + "rxjs": "^6.0.0 || ^7.4.0", "typescript": "^3.4.0 || >=4.0.0", "zone.js": "^0.8.29 || ^0.9.0 || ^0.10.0 || ^0.11.0" }, @@ -138,5 +139,5 @@ "access": "public" }, "builders": "dist/ts3.9/builders/builders.json", - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470" + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6" } diff --git a/app/angular/src/builders/build-storybook/index.spec.ts b/app/angular/src/builders/build-storybook/index.spec.ts index c1c9b784daa1..56b51507f877 100644 --- a/app/angular/src/builders/build-storybook/index.spec.ts +++ b/app/angular/src/builders/build-storybook/index.spec.ts @@ -5,11 +5,13 @@ import * as path from 'path'; const buildStandaloneMock = jest.fn(); jest.doMock('@storybook/angular/standalone', () => buildStandaloneMock); +jest.doMock('find-up', () => ({ sync: () => './storybook/tsconfig.ts' })); const cpSpawnMock = { spawn: jest.fn(), }; jest.doMock('child_process', () => cpSpawnMock); + describe('Build Storybook Builder', () => { let architect: Architect; let architectHost: TestingArchitectHost; @@ -75,15 +77,13 @@ describe('Build Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: false, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', }); }); @@ -102,14 +102,12 @@ describe('Build Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: null, angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: false, - compodocArgs: ['-e', 'json'], tsConfig: 'path/to/tsConfig.json', }); }); @@ -145,26 +143,51 @@ describe('Build Storybook Builder', () => { await run.stop(); expect(output.success).toBeTruthy(); - expect(cpSpawnMock.spawn).toHaveBeenCalledWith('compodoc', [ - '-p', - 'src/tsconfig.app.json', - '-d', - '', - '-e', - 'json', - ]); + expect(cpSpawnMock.spawn).toHaveBeenCalledWith( + 'npx', + ['compodoc', '-p', './storybook/tsconfig.ts', '-d', '', '-e', 'json'], + { + cwd: '', + env: process.env, + shell: true, + } + ); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: true, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', + }); + }); + + it('should start storybook with styles options', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:build-storybook', { + tsConfig: 'path/to/tsConfig.json', + compodoc: false, + styles: ['style.scss'], + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + angularBuilderContext: expect.any(Object), + angularBuilderOptions: { styles: ['style.scss'] }, + configDir: '.storybook', + loglevel: undefined, + quiet: false, + outputDir: 'storybook-static', + mode: 'static', + tsConfig: 'path/to/tsConfig.json', }); }); }); diff --git a/app/angular/src/builders/build-storybook/index.ts b/app/angular/src/builders/build-storybook/index.ts index 64827de6e809..6108034e9a34 100644 --- a/app/angular/src/builders/build-storybook/index.ts +++ b/app/angular/src/builders/build-storybook/index.ts @@ -9,10 +9,15 @@ import { JsonObject } from '@angular-devkit/core'; import { from, Observable, of, throwError } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; import { catchError, map, mapTo, switchMap } from 'rxjs/operators'; +import { sync as findUpSync } from 'find-up'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; -import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; +import { + BrowserBuilderOptions, + ExtraEntryPoint, + StylePreprocessorOptions, +} from '@angular-devkit/build-angular'; import { runCompodoc } from '../utils/run-compodoc'; import { buildStandaloneErrorHandler } from '../utils/build-standalone-errors-handler'; @@ -21,6 +26,8 @@ export type StorybookBuilderOptions = JsonObject & { tsConfig?: string; compodoc: boolean; compodocArgs: string[]; + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; } & Pick< // makes sure the option exists CLIOptions, @@ -46,12 +53,29 @@ function commandBuilder( return runCompodoc$.pipe(mapTo({ tsConfig })); }), map(({ tsConfig }) => { - const { browserTarget, ...otherOptions } = options; + const { + browserTarget, + stylePreprocessorOptions, + styles, + configDir, + docs, + loglevel, + outputDir, + quiet, + } = options; const standaloneOptions: StandaloneOptions = { - ...otherOptions, + configDir, + docs, + loglevel, + outputDir, + quiet, angularBrowserTarget: browserTarget, angularBuilderContext: context, + angularBuilderOptions: { + ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), + ...(styles ? { styles } : {}), + }, tsConfig, }; return standaloneOptions; @@ -76,7 +100,10 @@ async function setup(options: StorybookBuilderOptions, context: BuilderContext) } return { - tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, + tsConfig: + options.tsConfig ?? + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserOptions.tsConfig, }; } diff --git a/app/angular/src/builders/build-storybook/schema.json b/app/angular/src/builders/build-storybook/schema.json index a4e894863922..d20f55765ebf 100644 --- a/app/angular/src/builders/build-storybook/schema.json +++ b/app/angular/src/builders/build-storybook/schema.json @@ -51,15 +51,62 @@ "items": { "type": "string" } + }, + "styles": { + "type": "array", + "description": "Global styles to be included in the build.", + "items": { + "$ref": "#/definitions/extraEntryPoint" + }, + "default": "" + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "default": "" } }, "additionalProperties": false, - "oneOf": [ - { - "required": ["browserTarget"] - }, - { - "required": ["tsConfig"] + "definitions": { + "extraEntryPoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include." + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include." + } + ] } - ] + } } diff --git a/app/angular/src/builders/start-storybook/index.spec.ts b/app/angular/src/builders/start-storybook/index.spec.ts index 57463172af23..ee621ca1d477 100644 --- a/app/angular/src/builders/start-storybook/index.spec.ts +++ b/app/angular/src/builders/start-storybook/index.spec.ts @@ -5,6 +5,7 @@ import * as path from 'path'; const buildStandaloneMock = jest.fn(); jest.doMock('@storybook/angular/standalone', () => buildStandaloneMock); +jest.doMock('find-up', () => ({ sync: () => './storybook/tsconfig.ts' })); const cpSpawnMock = { spawn: jest.fn(), @@ -76,9 +77,9 @@ describe('Start Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 4400, @@ -87,9 +88,7 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: false, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', }); }); @@ -109,9 +108,9 @@ describe('Start Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: null, angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 4400, @@ -120,8 +119,6 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: false, - compodocArgs: ['-e', 'json'], tsConfig: 'path/to/tsConfig.json', }); }); @@ -157,20 +154,21 @@ describe('Start Storybook Builder', () => { await run.stop(); expect(output.success).toBeTruthy(); - expect(cpSpawnMock.spawn).toHaveBeenCalledWith('compodoc', [ - '-p', - 'src/tsconfig.app.json', - '-d', - '', - '-e', - 'json', - ]); + expect(cpSpawnMock.spawn).toHaveBeenCalledWith( + 'npx', + ['compodoc', '-p', './storybook/tsconfig.ts', '-d', '', '-e', 'json'], + { + cwd: '', + env: process.env, + shell: true, + } + ); expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 9009, @@ -179,9 +177,41 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: true, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', + }); + }); + + it('should start storybook with styles options', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', { + tsConfig: 'path/to/tsConfig.json', + port: 4400, + compodoc: false, + styles: ['src/styles.css'], + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + angularBuilderContext: expect.any(Object), + angularBuilderOptions: { + styles: ['src/styles.css'], + }, + ci: false, + configDir: '.storybook', + host: 'localhost', + https: false, + port: 4400, + quiet: false, + smokeTest: false, + sslCa: undefined, + sslCert: undefined, + sslKey: undefined, + tsConfig: 'path/to/tsConfig.json', }); }); }); diff --git a/app/angular/src/builders/start-storybook/index.ts b/app/angular/src/builders/start-storybook/index.ts index eade7127f29f..4f80db9866b2 100644 --- a/app/angular/src/builders/start-storybook/index.ts +++ b/app/angular/src/builders/start-storybook/index.ts @@ -6,10 +6,15 @@ import { Target, } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; -import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; +import { + BrowserBuilderOptions, + ExtraEntryPoint, + StylePreprocessorOptions, +} from '@angular-devkit/build-angular'; import { from, Observable, of } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; import { map, switchMap, mapTo } from 'rxjs/operators'; +import { sync as findUpSync } from 'find-up'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; @@ -21,6 +26,8 @@ export type StorybookBuilderOptions = JsonObject & { tsConfig?: string; compodoc: boolean; compodocArgs: string[]; + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; } & Pick< // makes sure the option exists CLIOptions, @@ -56,12 +63,41 @@ function commandBuilder( return runCompodoc$.pipe(mapTo({ tsConfig })); }), map(({ tsConfig }) => { - const { browserTarget, ...otherOptions } = options; + const { + browserTarget, + stylePreprocessorOptions, + styles, + ci, + configDir, + docs, + host, + https, + port, + quiet, + smokeTest, + sslCa, + sslCert, + sslKey, + } = options; const standaloneOptions: StandaloneOptions = { - ...otherOptions, + ci, + configDir, + docs, + host, + https, + port, + quiet, + smokeTest, + sslCa, + sslCert, + sslKey, angularBrowserTarget: browserTarget, angularBuilderContext: context, + angularBuilderOptions: { + ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), + ...(styles ? { styles } : {}), + }, tsConfig, }; @@ -87,7 +123,10 @@ async function setup(options: StorybookBuilderOptions, context: BuilderContext) } return { - tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, + tsConfig: + options.tsConfig ?? + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserOptions.tsConfig, }; } function runInstance(options: StandaloneOptions) { diff --git a/app/angular/src/builders/start-storybook/schema.json b/app/angular/src/builders/start-storybook/schema.json index 6c429fd26850..9911fe8e7bed 100644 --- a/app/angular/src/builders/start-storybook/schema.json +++ b/app/angular/src/builders/start-storybook/schema.json @@ -78,7 +78,62 @@ "items": { "type": "string" } + }, + "styles": { + "type": "array", + "description": "Global styles to be included in the build.", + "items": { + "$ref": "#/definitions/extraEntryPoint" + }, + "default": "" + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "default": "" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "extraEntryPoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include." + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include." + } + ] + } + } } diff --git a/app/angular/src/builders/utils/run-compodoc.spec.ts b/app/angular/src/builders/utils/run-compodoc.spec.ts new file mode 100644 index 000000000000..3c99cfefc499 --- /dev/null +++ b/app/angular/src/builders/utils/run-compodoc.spec.ts @@ -0,0 +1,88 @@ +import { BuilderContext } from '@angular-devkit/architect'; +import { LoggerApi } from '@angular-devkit/core/src/logger'; +import { take } from 'rxjs/operators'; + +const cpSpawnMock = { + spawn: jest.fn(), +}; +jest.doMock('child_process', () => cpSpawnMock); + +const { runCompodoc } = require('./run-compodoc'); + +const builderContextLoggerMock: LoggerApi = { + createChild: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), +}; + +describe('runCompodoc', () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { FOO: 'bar' }; + cpSpawnMock.spawn.mockImplementation(() => ({ + stdout: { on: () => {} }, + stderr: { on: () => {} }, + on: (_event: string, cb: any) => cb(0), + })); + }); + + afterEach(() => { + process.env = originalEnv; + jest.clearAllMocks(); + }); + + it('should run compodoc with tsconfig from context', async () => { + runCompodoc( + { + compodocArgs: [], + tsconfig: 'path/to/tsconfig.json', + }, + { + workspaceRoot: 'path/to/project', + logger: builderContextLoggerMock, + } as BuilderContext + ) + .pipe(take(1)) + .subscribe(); + + expect(cpSpawnMock.spawn).toHaveBeenCalledWith( + 'npx', + ['compodoc', '-p', 'path/to/tsconfig.json', '-d', 'path/to/project'], + { + cwd: 'path/to/project', + env: { FOO: 'bar' }, + shell: true, + } + ); + }); + + it('should run compodoc with tsconfig from compodocArgs', async () => { + runCompodoc( + { + compodocArgs: ['-p', 'path/to/tsconfig.stories.json'], + tsconfig: 'path/to/tsconfig.json', + }, + { + workspaceRoot: 'path/to/project', + logger: builderContextLoggerMock, + } as BuilderContext + ) + .pipe(take(1)) + .subscribe(); + + expect(cpSpawnMock.spawn).toHaveBeenCalledWith( + 'npx', + ['compodoc', '-d', 'path/to/project', '-p', 'path/to/tsconfig.stories.json'], + { + cwd: 'path/to/project', + env: { FOO: 'bar' }, + shell: true, + } + ); + }); +}); diff --git a/app/angular/src/builders/utils/run-compodoc.ts b/app/angular/src/builders/utils/run-compodoc.ts index 76ddadc5c326..3ad8daa12ecb 100644 --- a/app/angular/src/builders/utils/run-compodoc.ts +++ b/app/angular/src/builders/utils/run-compodoc.ts @@ -2,22 +2,29 @@ import { BuilderContext } from '@angular-devkit/architect'; import { spawn } from 'child_process'; import { Observable } from 'rxjs'; +const hasTsConfigArg = (args: string[]) => args.indexOf('-p') !== -1; + export const runCompodoc = ( { compodocArgs, tsconfig }: { compodocArgs: string[]; tsconfig: string }, context: BuilderContext ): Observable => { return new Observable((observer) => { const finalCompodocArgs = [ + 'compodoc', // Default options - '-p', - tsconfig, + ...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsconfig]), '-d', `${context.workspaceRoot}`, ...compodocArgs, ]; try { - const child = spawn('compodoc', finalCompodocArgs); + context.logger.info(finalCompodocArgs.join(' ')); + const child = spawn('npx', finalCompodocArgs, { + cwd: context.workspaceRoot, + env: process.env, + shell: true, + }); child.stdout.on('data', (data) => { context.logger.info(data.toString()); diff --git a/app/angular/src/client/preview/angular-beta/AbstractRenderer.ts b/app/angular/src/client/preview/angular-beta/AbstractRenderer.ts index d2d5108b2e86..5c9e7c4e7197 100644 --- a/app/angular/src/client/preview/angular-beta/AbstractRenderer.ts +++ b/app/angular/src/client/preview/angular-beta/AbstractRenderer.ts @@ -108,7 +108,7 @@ export abstract class AbstractRenderer { parameters: Parameters; targetDOMNode: HTMLElement; }) { - const targetSelector = `${this.storyId}`; + const targetSelector = `${this.generateTargetSelectorFromStoryId()}`; const newStoryProps$ = new BehaviorSubject(storyFnAngular.props); const moduleMetadata = getStorybookModuleMetadata( @@ -144,6 +144,26 @@ export abstract class AbstractRenderer { await this.afterFullRender(); } + /** + * Only ASCII alphanumerics can be used as HTML tag name. + * https://html.spec.whatwg.org/#elements-2 + * + * Therefore, stories break when non-ASCII alphanumerics are included in target selector. + * https://github.com/storybookjs/storybook/issues/15147 + * + * This method returns storyId when it doesn't contain any non-ASCII alphanumerics. + * Otherwise, it generates a valid HTML tag name from storyId by removing non-ASCII alphanumerics from storyId, prefixing "sb-", and suffixing "-component" + * @protected + * @memberof AbstractRenderer + */ + protected generateTargetSelectorFromStoryId() { + const invalidHtmlTag = /[^A-Za-z0-9-]/g; + const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(this.storyId); + return storyIdIsInvalidHtmlTagName + ? `sb-${this.storyId.replace(invalidHtmlTag, '')}-component` + : this.storyId; + } + protected initAngularRootElement(targetDOMNode: HTMLElement, targetSelector: string) { // Adds DOM element that angular will use as bootstrap component // eslint-disable-next-line no-param-reassign diff --git a/app/angular/src/client/preview/angular-beta/RendererFactory.test.ts b/app/angular/src/client/preview/angular-beta/RendererFactory.test.ts index 237e9b2c9b92..d9976268d77d 100644 --- a/app/angular/src/client/preview/angular-beta/RendererFactory.test.ts +++ b/app/angular/src/client/preview/angular-beta/RendererFactory.test.ts @@ -239,6 +239,49 @@ describe('RendererFactory', () => { expect(countDestroy).toEqual(1); }); + + describe('when story id contains non-Ascii characters', () => { + it('should render my-story for story template', async () => { + const render = await rendererFactory.getRendererInstance( + 'my-ストーリー', + rootTargetDOMNode + ); + await render.render({ + storyFnAngular: { + template: '🦊', + props: {}, + }, + forced: false, + parameters: {}, + targetDOMNode: rootTargetDOMNode, + }); + + expect(document.body.getElementsByTagName('sb-my--component')[0].innerHTML).toBe('🦊'); + }); + + it('should render my-story for story component', async () => { + @Component({ selector: 'foo', template: '🦊' }) + class FooComponent {} + + const render = await rendererFactory.getRendererInstance( + 'my-ストーリー', + rootTargetDOMNode + ); + await render.render({ + storyFnAngular: { + props: {}, + }, + forced: false, + parameters: {}, + component: FooComponent, + targetDOMNode: rootTargetDOMNode, + }); + + expect(document.body.getElementsByTagName('sb-my--component')[0].innerHTML).toBe( + '🦊' + ); + }); + }); }); describe('DocsRenderer', () => { diff --git a/app/angular/src/server/__mocks-ng-workspace__/minimal-config/angular.json b/app/angular/src/server/__mocks-ng-workspace__/minimal-config/angular.json index ac7d21f9eb2c..f3dcfcd45586 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/minimal-config/angular.json +++ b/app/angular/src/server/__mocks-ng-workspace__/minimal-config/angular.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { diff --git a/app/angular/src/server/__mocks-ng-workspace__/some-config/angular.json b/app/angular/src/server/__mocks-ng-workspace__/some-config/angular.json index 3d3cb9cde581..358ec4f98f18 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/some-config/angular.json +++ b/app/angular/src/server/__mocks-ng-workspace__/some-config/angular.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-angularBrowserTarget/angular.json b/app/angular/src/server/__mocks-ng-workspace__/with-angularBrowserTarget/angular.json index 0bd10ad584f9..f55b743522a1 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/with-angularBrowserTarget/angular.json +++ b/app/angular/src/server/__mocks-ng-workspace__/with-angularBrowserTarget/angular.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { @@ -14,6 +15,7 @@ }, "no-confs-project": { "root": "", + "sourceRoot": "src", "architect": { "target-build": { "options": { @@ -25,6 +27,7 @@ }, "no-target-conf-project": { "root": "", + "sourceRoot": "src", "architect": { "target-build": { "options": { @@ -41,6 +44,7 @@ }, "target-project": { "root": "", + "sourceRoot": "src", "architect": { "target-build": { "options": { diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-nx-workspace/workspace.json b/app/angular/src/server/__mocks-ng-workspace__/with-nx-workspace/workspace.json index 39f1562a36a7..9d9fc9b3ef36 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/with-nx-workspace/workspace.json +++ b/app/angular/src/server/__mocks-ng-workspace__/with-nx-workspace/workspace.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-nx/angular.json b/app/angular/src/server/__mocks-ng-workspace__/with-nx/angular.json index 39f1562a36a7..9d9fc9b3ef36 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/with-nx/angular.json +++ b/app/angular/src/server/__mocks-ng-workspace__/with-nx/angular.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { diff --git a/app/angular/src/server/__mocks-ng-workspace__/with-options-styles/angular.json b/app/angular/src/server/__mocks-ng-workspace__/with-options-styles/angular.json index 39f1562a36a7..9d9fc9b3ef36 100644 --- a/app/angular/src/server/__mocks-ng-workspace__/with-options-styles/angular.json +++ b/app/angular/src/server/__mocks-ng-workspace__/with-options-styles/angular.json @@ -3,6 +3,7 @@ "projects": { "foo-project": { "root": "", + "sourceRoot": "src", "architect": { "build": { "options": { diff --git a/app/angular/src/server/angular-cli-webpack-12.2.x.js b/app/angular/src/server/angular-cli-webpack-12.2.x.js index 3f1d69e5863e..bb3f12011b8a 100644 --- a/app/angular/src/server/angular-cli-webpack-12.2.x.js +++ b/app/angular/src/server/angular-cli-webpack-12.2.x.js @@ -7,6 +7,7 @@ const { getStylesConfig, getTypeScriptConfig, } = require('@angular-devkit/build-angular/src/webpack/configs'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const { filterOutStylingRules } = require('./utils/filter-out-styling-rules'); @@ -65,6 +66,12 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext } const resolve = { ...baseConfig.resolve, modules: Array.from(new Set([...baseConfig.resolve.modules, ...cliConfig.resolve.modules])), + plugins: [ + new TsconfigPathsPlugin({ + configFile: builderOptions.tsConfig, + mainFields: ['browser', 'module', 'main'], + }), + ], }; return { diff --git a/app/angular/src/server/angular-cli-webpack-13.x.x.js b/app/angular/src/server/angular-cli-webpack-13.x.x.js index 0455e07047f6..0b74d3af9889 100644 --- a/app/angular/src/server/angular-cli-webpack-13.x.x.js +++ b/app/angular/src/server/angular-cli-webpack-13.x.x.js @@ -5,8 +5,10 @@ const { const { getCommonConfig, getStylesConfig, + getDevServerConfig, getTypescriptWorkerPlugin, } = require('@angular-devkit/build-angular/src/webpack/configs'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const { filterOutStylingRules } = require('./utils/filter-out-styling-rules'); @@ -40,7 +42,11 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext } aot: false, }, builderContext, - (wco) => [getCommonConfig(wco), getStylesConfig(wco), getTypescriptWorkerPlugin(wco)] + (wco) => [ + getCommonConfig(wco), + getStylesConfig(wco), + getTypescriptWorkerPlugin ? getTypescriptWorkerPlugin(wco) : getDevServerConfig(wco), + ] ); /** @@ -65,6 +71,12 @@ exports.getWebpackConfig = async (baseConfig, { builderOptions, builderContext } const resolve = { ...baseConfig.resolve, modules: Array.from(new Set([...baseConfig.resolve.modules, ...cliConfig.resolve.modules])), + plugins: [ + new TsconfigPathsPlugin({ + configFile: builderOptions.tsConfig, + mainFields: ['browser', 'module', 'main'], + }), + ], }; return { diff --git a/app/angular/src/server/angular-devkit-build-webpack.ts b/app/angular/src/server/angular-devkit-build-webpack.ts index 6f2e26f05291..9d9ed78cbd0b 100644 --- a/app/angular/src/server/angular-devkit-build-webpack.ts +++ b/app/angular/src/server/angular-devkit-build-webpack.ts @@ -139,7 +139,7 @@ const buildWebpackConfigOptions = async ( // The dependency of `@angular-devkit/build-angular` to `@angular-devkit/core` is not exactly the same version as the one for storybook (node modules of node modules ^^) logger: (createConsoleLogger() as unknown) as WebpackConfigOptions['logger'], projectRoot: getSystemPath(projectRootNormalized), - sourceRoot: getSystemPath(sourceRootNormalized), + sourceRoot: sourceRootNormalized ? getSystemPath(sourceRootNormalized) : undefined, buildOptions, tsConfig, tsConfigPath, diff --git a/app/angular/src/server/framework-preset-angular-cli.test.ts b/app/angular/src/server/framework-preset-angular-cli.test.ts index 8b2c1959d621..967d85a52099 100644 --- a/app/angular/src/server/framework-preset-angular-cli.test.ts +++ b/app/angular/src/server/framework-preset-angular-cli.test.ts @@ -1,6 +1,9 @@ /* eslint-disable jest/no-interpolation-in-snapshots */ +import path from 'path'; import { Configuration } from 'webpack'; import { logger } from '@storybook/node-logger'; +import { normalize, getSystemPath } from '@angular-devkit/core'; +import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; import { webpackFinal } from './framework-preset-angular-cli'; import { PresetOptions } from './options'; @@ -20,7 +23,7 @@ afterEach(() => { }); function initMockWorkspace(name: string) { - workspaceRoot = `${testPath}/__mocks-ng-workspace__/${name}`; + workspaceRoot = path.join(__dirname, '__mocks-ng-workspace__', name); cwdSpy.mockReturnValue(workspaceRoot); } @@ -189,7 +192,11 @@ describe('framework-preset-angular-cli', () => { ...baseWebpackConfig.resolve, modules: expect.arrayContaining(baseWebpackConfig.resolve.modules), // the base resolve.plugins are not kept 🤷‍♂️ - plugins: expect.not.arrayContaining(baseWebpackConfig.resolve.plugins), + plugins: expect.arrayContaining([ + expect.objectContaining({ + absoluteBaseUrl: expect.any(String), + } as TsconfigPathsPlugin), + ]), }, resolveLoader: expect.anything(), }); @@ -258,7 +265,7 @@ describe('framework-preset-angular-cli', () => { expect(webpackFinalConfig.resolve.modules).toEqual([ ...baseWebpackConfig.resolve.modules, - `${workspaceRoot}/src`, + getSystemPath(normalize(path.join(workspaceRoot, 'src'))).replace(/\\/g, '/'), ]); }); @@ -269,7 +276,9 @@ describe('framework-preset-angular-cli', () => { expect(webpackFinalConfig.resolve.plugins).toMatchInlineSnapshot(` Array [ TsconfigPathsPlugin { - "absoluteBaseUrl": "${workspaceRoot}/src/", + "absoluteBaseUrl": "${( + getSystemPath(normalize(path.join(workspaceRoot, 'src'))) + path.sep + ).replace(/\\/g, '\\\\')}", "baseUrl": "./", "extensions": Array [ ".ts", @@ -302,8 +311,8 @@ describe('framework-preset-angular-cli', () => { ...baseWebpackConfig, entry: [ ...(baseWebpackConfig.entry as any[]), - `${workspaceRoot}/src/styles.css`, - `${workspaceRoot}/src/styles.scss`, + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), ], module: { ...baseWebpackConfig.module, rules: expect.anything() }, plugins: expect.anything(), @@ -320,45 +329,48 @@ describe('framework-preset-angular-cli', () => { it('should set webpack "module.rules"', async () => { const baseWebpackConfig = newWebpackConfiguration(); const webpackFinalConfig = await webpackFinal(baseWebpackConfig, options); - + const stylePaths = [ + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), + ]; expect(webpackFinalConfig.module.rules).toEqual([ { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.css$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.less$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.styl$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.css$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.less$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.styl$/, use: expect.anything(), }, @@ -393,8 +405,8 @@ describe('framework-preset-angular-cli', () => { ...baseWebpackConfig, entry: [ ...(baseWebpackConfig.entry as any[]), - `${workspaceRoot}/src/styles.css`, - `${workspaceRoot}/src/styles.scss`, + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), ], module: { ...baseWebpackConfig.module, rules: expect.anything() }, plugins: expect.anything(), @@ -411,45 +423,49 @@ describe('framework-preset-angular-cli', () => { it('should set webpack "module.rules"', async () => { const baseWebpackConfig = newWebpackConfiguration(); const webpackFinalConfig = await webpackFinal(baseWebpackConfig, options); + const stylePaths = [ + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), + ]; expect(webpackFinalConfig.module.rules).toEqual([ { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.css$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.less$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.styl$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.css$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.less$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.styl$/, use: expect.anything(), }, @@ -471,8 +487,8 @@ describe('framework-preset-angular-cli', () => { ...baseWebpackConfig, entry: [ ...(baseWebpackConfig.entry as any[]), - `${workspaceRoot}/src/styles.css`, - `${workspaceRoot}/src/styles.scss`, + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), ], module: { ...baseWebpackConfig.module, rules: expect.anything() }, plugins: expect.anything(), @@ -480,7 +496,11 @@ describe('framework-preset-angular-cli', () => { ...baseWebpackConfig.resolve, modules: expect.arrayContaining(baseWebpackConfig.resolve.modules), // the base resolve.plugins are not kept 🤷‍♂️ - plugins: expect.not.arrayContaining(baseWebpackConfig.resolve.plugins), + plugins: expect.arrayContaining([ + expect.objectContaining({ + absoluteBaseUrl: expect.any(String), + } as TsconfigPathsPlugin), + ]), }, resolveLoader: expect.anything(), }); @@ -489,45 +509,49 @@ describe('framework-preset-angular-cli', () => { it('should set webpack "module.rules"', async () => { const baseWebpackConfig = newWebpackConfiguration(); const webpackFinalConfig = await webpackFinal(baseWebpackConfig, options); + const stylePaths = [ + path.join(workspaceRoot, 'src', 'styles.css'), + path.join(workspaceRoot, 'src', 'styles.scss'), + ]; expect(webpackFinalConfig.module.rules).toEqual([ { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.css$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.less$/, use: expect.anything(), }, { - exclude: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + exclude: stylePaths, test: /\.styl$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.css$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.scss$|\.sass$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.less$/, use: expect.anything(), }, { - include: [`${workspaceRoot}/src/styles.css`, `${workspaceRoot}/src/styles.scss`], + include: stylePaths, test: /\.styl$/, use: expect.anything(), }, @@ -690,7 +714,10 @@ describe('framework-preset-angular-cli', () => { expect(webpackFinalConfig).toEqual({ ...baseWebpackConfig, - entry: [...(baseWebpackConfig.entry as any[]), `${workspaceRoot}/src/styles.css`], + entry: [ + ...(baseWebpackConfig.entry as any[]), + path.join(workspaceRoot, 'src', 'styles.css'), + ], module: { ...baseWebpackConfig.module, rules: expect.anything() }, plugins: expect.anything(), resolve: { diff --git a/app/angular/src/server/framework-preset-angular-cli.ts b/app/angular/src/server/framework-preset-angular-cli.ts index 6d907a8d9302..6f6b0601cf73 100644 --- a/app/angular/src/server/framework-preset-angular-cli.ts +++ b/app/angular/src/server/framework-preset-angular-cli.ts @@ -1,6 +1,6 @@ import webpack from 'webpack'; import { logger } from '@storybook/node-logger'; -import { targetFromTargetString, BuilderContext } from '@angular-devkit/architect'; +import { targetFromTargetString, BuilderContext, Target } from '@angular-devkit/architect'; import { sync as findUpSync } from 'find-up'; import semver from '@storybook/semver'; @@ -10,6 +10,11 @@ import { getWebpackConfig as getWebpackConfig12_2_x } from './angular-cli-webpac import { getWebpackConfig as getWebpackConfig13_x_x } from './angular-cli-webpack-13.x.x'; import { getWebpackConfig as getWebpackConfigOlder } from './angular-cli-webpack-older'; import { PresetOptions } from './options'; +import { + getDefaultProjectName, + findAngularProjectTarget, + readAngularWorkspaceConfig, +} from './angular-read-workspace'; export async function webpackFinal(baseConfig: webpack.Configuration, options: PresetOptions) { if (!moduleIsAvailable('@angular-devkit/build-angular')) { @@ -36,9 +41,10 @@ export async function webpackFinal(baseConfig: webpack.Configuration, options: P getWebpackConfig: async (_baseConfig, _options) => { const builderContext = getBuilderContext(_options); const builderOptions = await getBuilderOptions(_options, builderContext); + const legacyDefaultOptions = await getLegacyDefaultBuildOptions(_options); return getWebpackConfig13_x_x(_baseConfig, { - builderOptions, + builderOptions: { ...builderOptions, ...legacyDefaultOptions }, builderContext, }); }, @@ -114,14 +120,65 @@ async function getBuilderOptions( */ const builderOptions = { ...browserTargetOptions, - ...options.angularBuilderOptions, + ...(options.angularBuilderOptions as JsonObject), tsConfig: options.tsConfig ?? - options.angularBuilderOptions?.tsConfig ?? - browserTargetOptions.tsConfig ?? - findUpSync('tsconfig.json', { cwd: options.configDir }), + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserTargetOptions.tsConfig, }; logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`); return builderOptions; } + +/** + * Get options from legacy way + * /!\ This is only for backward compatibility and would be removed on Storybook 7.0 + * only work for angular.json with [defaultProject].build or "storybook.build" config + */ +async function getLegacyDefaultBuildOptions(options: PresetOptions) { + if (options.angularBrowserTarget !== undefined) { + // Not use legacy way with builder (`angularBrowserTarget` is defined or null with builder and undefined without) + return {}; + } + + // TODO ~ legacy way to get default options + // TODO ~ Add deprecation warning and ex for builder use ? `); + const dirToSearch = process.cwd(); + + // Read angular workspace + let workspaceConfig; + try { + workspaceConfig = await readAngularWorkspaceConfig(dirToSearch); + } catch (error) { + logger.error( + `=> Could not find angular workspace config (angular.json) on this path "${dirToSearch}"` + ); + logger.info(`=> Fail to load angular-cli config. Using base config`); + return {}; + } + + // Find angular project target + try { + const browserTarget = { + configuration: undefined, + project: getDefaultProjectName(workspaceConfig), + target: 'build', + } as Target; + + const { target, project } = findAngularProjectTarget( + workspaceConfig, + browserTarget.project, + browserTarget.target + ); + + logger.info( + `=> Using angular project "${browserTarget.project}:${browserTarget.target}" for configuring Storybook` + ); + return { ...target.options }; + } catch (error) { + logger.error(`=> Could not find angular project: ${error.message}`); + logger.info(`=> Fail to load angular-cli config. Using base config`); + return {}; + } +} diff --git a/app/angular/src/server/options.ts b/app/angular/src/server/options.ts index 061841dfb52a..252519968710 100644 --- a/app/angular/src/server/options.ts +++ b/app/angular/src/server/options.ts @@ -2,13 +2,16 @@ import { sync } from 'read-pkg-up'; import { LoadOptions, Options as CoreOptions } from '@storybook/core-common'; import { BuilderContext } from '@angular-devkit/architect'; -import { JsonObject } from '@angular-devkit/core'; +import { ExtraEntryPoint, StylePreprocessorOptions } from '@angular-devkit/build-angular'; export type PresetOptions = CoreOptions & { /* Allow to get the options of a targeted "browser builder" */ angularBrowserTarget?: string | null; /* Defined set of options. These will take over priority from angularBrowserTarget options */ - angularBuilderOptions?: JsonObject | null; + angularBuilderOptions?: { + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; + }; /* Angular context from builder */ angularBuilderContext?: BuilderContext | null; tsConfig?: string; diff --git a/app/angular/src/server/utils/filter-out-styling-rules.ts b/app/angular/src/server/utils/filter-out-styling-rules.ts index 46591becee5d..26489cee2ffc 100644 --- a/app/angular/src/server/utils/filter-out-styling-rules.ts +++ b/app/angular/src/server/utils/filter-out-styling-rules.ts @@ -1,4 +1,4 @@ -import { Configuration, RuleSetRule } from 'webpack'; +import type { Configuration, RuleSetRule } from 'webpack'; const isStylingRule = (rule: RuleSetRule) => { const { test } = rule; diff --git a/app/angular/standalone.d.ts b/app/angular/standalone.d.ts index 481849593fce..0349246d5392 100644 --- a/app/angular/standalone.d.ts +++ b/app/angular/standalone.d.ts @@ -1,6 +1,7 @@ import { CLIOptions, LoadOptions, BuilderOptions } from '@storybook/core-common'; import { BuilderContext } from '@angular-devkit/architect'; -import { JsonObject } from '@angular-devkit/core'; +import { JsonValue } from '@angular-devkit/core'; +import { JsonSchema } from '@angular-devkit/core/src/json/schema'; export type StandaloneOptions = Partial< CLIOptions & @@ -8,7 +9,10 @@ export type StandaloneOptions = Partial< BuilderOptions & { mode?: 'static' | 'dev'; angularBrowserTarget?: string | null; - angularBuilderOptions?: JsonObject; + angularBuilderOptions?: JsonObject & { + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; + }; angularBuilderContext?: BuilderContext | null; tsConfig?: string; } diff --git a/app/ember/package.json b/app/ember/package.json index 85e6781f3607..b1702fce6c8d 100644 --- a/app/ember/package.json +++ b/app/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/main/app/ember", "bugs": { @@ -43,9 +43,9 @@ }, "dependencies": { "@ember/test-helpers": "^2.1.4", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", "core-js": "^3.8.2", "global": "^4.4.0", "react": "16.14.0", @@ -66,6 +66,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/html/package.json b/app/html/package.json index 6884f32df0fe..a3b9f6aa81d0 100644 --- a/app/html/package.json +++ b/app/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,13 +45,14 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/client-api": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/client-api": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/preview-web": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", + "@storybook/preview-web": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -71,6 +72,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/preact/package.json b/app/preact/package.json index a5dc1dcf0d01..49b458dc893b 100644 --- a/app/preact/package.json +++ b/app/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" @@ -46,11 +46,12 @@ }, "dependencies": { "@babel/plugin-transform-react-jsx": "^7.12.12", - "@storybook/addons": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -74,6 +75,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/react/package.json b/app/react/package.json index a0bc9687b440..6dc65c1be98c 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -48,33 +48,32 @@ "dependencies": { "@babel/preset-flow": "^7.12.1", "@babel/preset-react": "^7.12.10", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", - "@storybook/addons": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/node-logger": "6.4.0-rc.6", - "@storybook/react-docgen-typescript-plugin": "1.0.2-canary.253f8c1.0", + "@storybook/node-logger": "6.5.0-alpha.7", + "@storybook/react-docgen-typescript-plugin": "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0", "@storybook/semver": "^7.3.2", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "babel-plugin-add-react-displayname": "^0.0.5", "babel-plugin-named-asset-import": "^0.3.1", "babel-plugin-react-docgen": "^4.2.1", "core-js": "^3.8.2", "global": "^4.4.0", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "prop-types": "^15.7.2", - "react-dev-utils": "^11.0.4", - "react-refresh": "^0.10.0", + "react-refresh": "^0.11.0", "read-pkg-up": "^7.0.1", "regenerator-runtime": "^0.13.7", "ts-dedent": "^2.0.0", "webpack": "4" }, "devDependencies": { - "@storybook/client-api": "6.4.0-rc.6", - "@types/node": "^14.14.20", + "@storybook/client-api": "6.5.0-alpha.7", "@types/prompts": "^2.0.9" }, "peerDependencies": { @@ -96,6 +95,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/react/src/server/cra-config.test.ts b/app/react/src/server/cra-config.test.ts index c6b1a57c30ea..3f5c8c9d361a 100644 --- a/app/react/src/server/cra-config.test.ts +++ b/app/react/src/server/cra-config.test.ts @@ -1,4 +1,5 @@ import fs from 'fs'; +import path from 'path'; import { getReactScriptsPath } from './cra-config'; jest.mock('fs', () => ({ @@ -7,7 +8,7 @@ jest.mock('fs', () => ({ existsSync: jest.fn(() => true), })); -const SCRIPT_PATH = '.bin/react-scripts'; +const SCRIPT_PATH = path.join('.bin', 'react-scripts'); describe('cra-config', () => { describe('when used with the default react-scripts package', () => { @@ -19,7 +20,7 @@ describe('cra-config', () => { it('should locate the react-scripts package', () => { expect(getReactScriptsPath({ noCache: true })).toEqual( - '/test-project/node_modules/react-scripts' + path.join(path.sep, 'test-project', 'node_modules', 'react-scripts') ); }); }); @@ -33,7 +34,7 @@ describe('cra-config', () => { it('should locate the react-scripts package', () => { expect(getReactScriptsPath({ noCache: true })).toEqual( - '/test-project/node_modules/custom-react-scripts' + path.join(path.sep, 'test-project', 'node_modules', 'custom-react-scripts') ); }); }); @@ -65,7 +66,7 @@ exit $ret` it('should locate the react-scripts package', () => { expect(getReactScriptsPath({ noCache: true })).toEqual( - '/test-project/node_modules/custom-react-scripts' + path.join(path.sep, 'test-project', 'node_modules', 'custom-react-scripts') ); }); }); diff --git a/app/react/src/typings.d.ts b/app/react/src/typings.d.ts index 26c2a8b586bf..4ff88fa9018c 100644 --- a/app/react/src/typings.d.ts +++ b/app/react/src/typings.d.ts @@ -1,4 +1,2 @@ declare module '@storybook/semver'; declare module 'global'; -// todo check for correct types -declare module 'webpack/lib/RuleSet'; diff --git a/app/server/package.json b/app/server/package.json index e3cd5ff402a7..131c878e8266 100644 --- a/app/server/package.json +++ b/app/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,15 +45,16 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/api": "6.4.0-rc.6", - "@storybook/client-api": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/api": "6.5.0-alpha.7", + "@storybook/client-api": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/node-logger": "6.4.0-rc.6", - "@storybook/preview-web": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", + "@storybook/node-logger": "6.5.0-alpha.7", + "@storybook/preview-web": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -75,6 +76,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/svelte/package.json b/app/svelte/package.json index c90a5f8199e1..fb5b8e4e5024 100644 --- a/app/svelte/package.json +++ b/app/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,11 +45,11 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", "core-js": "^3.8.2", "global": "^4.4.0", "react": "16.14.0", @@ -75,6 +75,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/vue/package.json b/app/vue/package.json index 1a26ff2cf673..ff8e8d9f6ded 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,11 +45,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -64,7 +65,6 @@ "webpack": "4" }, "devDependencies": { - "@types/node": "^14.14.20", "vue": "^2.6.12", "vue-loader": "^15.9.6", "vue-template-compiler": "^2.6.12" @@ -83,6 +83,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/vue/src/server/framework-preset-vue.ts b/app/vue/src/server/framework-preset-vue.ts index 429cc3de29d2..6eb8e75201db 100644 --- a/app/vue/src/server/framework-preset-vue.ts +++ b/app/vue/src/server/framework-preset-vue.ts @@ -1,5 +1,5 @@ /* eslint-disable no-param-reassign */ -import VueLoaderPlugin from 'vue-loader/lib/plugin'; +import { VueLoaderPlugin } from 'vue-loader'; import type { Configuration } from 'webpack'; import { findDistEsm, Options, TypescriptConfig, StorybookConfig } from '@storybook/core-common'; diff --git a/app/vue/src/typings.d.ts b/app/vue/src/typings.d.ts index 87f498f04800..4f4eff6c31cd 100644 --- a/app/vue/src/typings.d.ts +++ b/app/vue/src/typings.d.ts @@ -1,5 +1,3 @@ declare module 'global'; -// todo check for correct types -declare module 'webpack/lib/RuleSet'; declare module 'vue-loader/lib/plugin'; \ No newline at end of file diff --git a/app/vue3/package.json b/app/vue3/package.json index e8f99d97dc2a..23e1c980f7fe 100644 --- a/app/vue3/package.json +++ b/app/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" @@ -45,11 +45,12 @@ "prepare": "node ../../scripts/prepare.js" }, "dependencies": { - "@storybook/addons": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/store": "6.4.0-rc.6", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "core-js": "^3.8.2", "global": "^4.4.0", @@ -65,7 +66,6 @@ "webpack": "4" }, "devDependencies": { - "@types/node": "^14.14.20", "@vue/compiler-sfc": "^3.0.0", "vue": "^3.0.0" }, @@ -81,6 +81,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/app/vue3/src/client/preview/index.ts b/app/vue3/src/client/preview/index.ts index 6c3642f19377..2c1abba00c9e 100644 --- a/app/vue3/src/client/preview/index.ts +++ b/app/vue3/src/client/preview/index.ts @@ -7,7 +7,7 @@ import { IStorybookSection } from './types'; import { VueFramework } from './types-6-0'; import { decorateStory } from './decorateStory'; -import { renderToDOM, storybookApp } from './render'; +import { render, renderToDOM, storybookApp } from './render'; const framework = 'vue3'; @@ -22,7 +22,7 @@ interface ClientApi extends ClientStoryApi { app: App; } -const api = start(renderToDOM, { decorateStory }); +const api = start(renderToDOM, { decorateStory, render }); export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ diff --git a/app/vue3/src/client/preview/render.ts b/app/vue3/src/client/preview/render.ts index e9cc27274bce..ec1bc7ab3764 100644 --- a/app/vue3/src/client/preview/render.ts +++ b/app/vue3/src/client/preview/render.ts @@ -1,9 +1,22 @@ import dedent from 'ts-dedent'; import { createApp, h, shallowRef, ComponentPublicInstance } from 'vue'; import { RenderContext } from '@storybook/store'; +import { ArgsStoryFn } from '@storybook/csf'; + import { StoryFnVueReturnType } from './types'; import { VueFramework } from './types-6-0'; +export const render: ArgsStoryFn = (props, context) => { + const { id, component: Component } = context; + if (!Component) { + throw new Error( + `Unable to render story ${id} as the component annotation is missing from the default export` + ); + } + + return h(Component, props); +}; + export const activeStoryComponent = shallowRef(null); let root: ComponentPublicInstance | null = null; diff --git a/app/vue3/src/typings.d.ts b/app/vue3/src/typings.d.ts index 64a098b25475..35a53756975e 100644 --- a/app/vue3/src/typings.d.ts +++ b/app/vue3/src/typings.d.ts @@ -1,6 +1,4 @@ declare module 'global'; -// todo check for correct types -declare module 'webpack/lib/RuleSet'; declare module 'vue-loader' { export const VueLoaderPlugin diff --git a/app/web-components/package.json b/app/web-components/package.json index 12becc2a225f..a77fb650a63f 100644 --- a/app/web-components/package.json +++ b/app/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "6.4.0-rc.6", + "version": "6.5.0-alpha.7", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit-html", @@ -50,13 +50,14 @@ "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/preset-env": "^7.12.11", - "@storybook/addons": "6.4.0-rc.6", - "@storybook/client-api": "6.4.0-rc.6", - "@storybook/core": "6.4.0-rc.6", - "@storybook/core-common": "6.4.0-rc.6", + "@storybook/addons": "6.5.0-alpha.7", + "@storybook/client-api": "6.5.0-alpha.7", + "@storybook/core": "6.5.0-alpha.7", + "@storybook/core-common": "6.5.0-alpha.7", "@storybook/csf": "0.0.2--canary.87bc651.0", - "@storybook/preview-web": "6.4.0-rc.6", - "@storybook/store": "6.4.0-rc.6", + "@storybook/preview-web": "6.5.0-alpha.7", + "@storybook/store": "6.5.0-alpha.7", + "@types/node": "^14.14.20 || ^16.0.0", "@types/webpack-env": "^1.16.0", "babel-plugin-bundled-import-meta": "^0.3.1", "core-js": "^3.8.2", @@ -79,6 +80,6 @@ "publishConfig": { "access": "public" }, - "gitHead": "b2afbb87f07d81f5c3830748490f6dca84d8e470", + "gitHead": "eb1ec353f7fbdf90d67576d93b9a23809d6cc0e6", "sbmodern": "dist/modern/client/index.js" } diff --git a/docs/addons/addon-types.md b/docs/addons/addon-types.md index e7bf0a799010..2a5940bcdca2 100644 --- a/docs/addons/addon-types.md +++ b/docs/addons/addon-types.md @@ -47,7 +47,7 @@ Use this boilerplate code to add a new `button` to Storybook's Toolbar:
-The icon element used in the example loads the icons from the @storybook/components package. See [here](../workflows/faq.md#what-icons-are-available-for-my-toolbar-or-my-addon) for the list of available icons that you can use. +The icon element used in the example loads the icons from the @storybook/components package. See [here](../faq.md#what-icons-are-available-for-my-toolbar-or-my-addon) for the list of available icons that you can use.
diff --git a/docs/addons/addons-api.md b/docs/addons/addons-api.md index cdb4db144ea7..5ba804d8ca8a 100644 --- a/docs/addons/addons-api.md +++ b/docs/addons/addons-api.md @@ -85,7 +85,7 @@ The options to `makeDecorator` are:
-💡 Note:If the story's parameters include `{ foo: { disable: true } }` (where `foo` is the `parameterName` of your addon), your decorator will not be called. +💡 If the story's parameters include `{ foo: { disable: true } }` (where `foo` is the `parameterName` of your addon), your decorator will not be called.
@@ -286,7 +286,7 @@ This method allows you to set query string parameters. You can use that as tempo
-💡 Note: If you need to remove a query param, use `null` for that. For example, let's say we need to remove the `bbc` query param. See below how to do it: +💡 If you need to remove a query param, use `null` for that. For example, let's say we need to remove the `bbc` query param. See below how to do it:
diff --git a/docs/api/argtypes.md b/docs/api/argtypes.md index d8d5e9cf87fe..cbda5980d01b 100644 --- a/docs/api/argtypes.md +++ b/docs/api/argtypes.md @@ -4,7 +4,7 @@ title: 'ArgTypes'
-NOTE: This API is experimental and may change outside of the typical semver release cycle +This API is experimental and may change outside of the typical semver release cycle
diff --git a/docs/api/cli-options.md b/docs/api/cli-options.md index 953d12320b71..9380c20d4e17 100644 --- a/docs/api/cli-options.md +++ b/docs/api/cli-options.md @@ -35,12 +35,12 @@ Usage: start-storybook [options] | `--no-manager-cache` | Disables Storybook's manager caching mechanism. See note below.
`start-storybook --no-manager-cache` |
-💡 NOTE: The flag --no-manager-cache disables the internal caching of Storybook and can severely impact your Storybook loading time, so only use it when you need to refresh Storybook's UI, such as when editing themes. +💡 The flag --no-manager-cache disables the internal caching of Storybook and can severely impact your Storybook loading time, so only use it when you need to refresh Storybook's UI, such as when editing themes.
-💡 NOTE: Starting in 6.4 the `-s` flag is deprecated. Instead, use a configuration object in your `.storybook/main.js` file. See the [images and assets documentation](../configure/images-and-assets.md#serving-static-files-via-storybook) for more information. +💡 Starting in 6.4 the `-s` flag is deprecated. Instead, use a configuration object in your `.storybook/main.js` file. See the [images and assets documentation](../configure/images-and-assets.md#serving-static-files-via-storybook) for more information.
@@ -65,5 +65,5 @@ Usage: build-storybook [options] | `--docs` | Builds Storybook in documentation mode. Learn more about it in [here](../writing-docs/build-documentation.md#publish-storybooks-documentation)
`build-storybook --docs` |
-💡 NOTE: If you're using npm instead of yarn to publish Storybook, the commands work slightly different. For example, npm run build-storybook -- -o ./path/to/build. -
\ No newline at end of file +💡 If you're using npm instead of yarn to publish Storybook, the commands work slightly different. For example, npm run build-storybook -- -o ./path/to/build. +
diff --git a/docs/configure/environment-variables.md b/docs/configure/environment-variables.md index a4838576d895..830c94e1cc2d 100644 --- a/docs/configure/environment-variables.md +++ b/docs/configure/environment-variables.md @@ -65,7 +65,7 @@ Then you can access this environment variable anywhere, even within your stories You can also use specific files for specific modes. Add a .env.development or .env.production to apply different values to your environment variables.
-You can also pass these environment variables when you are [building your Storybook](../workflows/publish-storybook.md) with `build-storybook`. +You can also pass these environment variables when you are [building your Storybook](../sharing/publish-storybook.md) with `build-storybook`. Then they'll be hardcoded to the static version of your Storybook. @@ -82,5 +82,5 @@ The table below lists the available options: | Chromium | `BROWSER="chromium"` |
-💡 Note: By default, Storybook will open a new Chrome window as part of its startup process. If you don't have Chrome installed, make sure to include one of the following options, or set your default browser accordingly. -
\ No newline at end of file +💡 By default, Storybook will open a new Chrome window as part of its startup process. If you don't have Chrome installed, make sure to include one of the following options, or set your default browser accordingly. +
diff --git a/docs/configure/overview.md b/docs/configure/overview.md index b947d79daf50..1577221a5813 100644 --- a/docs/configure/overview.md +++ b/docs/configure/overview.md @@ -75,7 +75,7 @@ Additionally, you can also customize your Storybook configuration to load your s
-💡 Note: If you've enabled on-demand story loading, this option will not work. You must define the story's titles manually. +💡 If you've enabled on-demand story loading, this option will not work. You must define the story's titles manually.
When Storybook starts, it will look for any file containing the `stories` extension inside the `packages/stories` directory and generate the titles for your stories. diff --git a/docs/configure/sidebar-and-urls.md b/docs/configure/sidebar-and-urls.md index 62fcc0048600..774d47fabf7d 100644 --- a/docs/configure/sidebar-and-urls.md +++ b/docs/configure/sidebar-and-urls.md @@ -44,7 +44,7 @@ If you need, you can also generate automatic titles for all your stories using a ## Permalinking to stories -By default, Storybook generates an `id` for each story based on the component title and the story name. This `id` in particular is used in the URL for each story, and that URL can serve as a permalink (primarily when you [publish](../workflows/publish-storybook.md) your Storybook). +By default, Storybook generates an `id` for each story based on the component title and the story name. This `id` in particular is used in the URL for each story, and that URL can serve as a permalink (primarily when you [publish](../sharing/publish-storybook.md) your Storybook). Consider the following story: diff --git a/docs/configure/story-rendering.md b/docs/configure/story-rendering.md index b4b302810c6a..360dde244f14 100644 --- a/docs/configure/story-rendering.md +++ b/docs/configure/story-rendering.md @@ -60,4 +60,4 @@ Storybook will inject these tags into the _preview iframe_ where your components -Similarly to the preview head HTML, preview body HTML can also be updated programmtically using a preset. See [Preview/Manager templates](../addons/writing-presets.md#previewmanager-templates) for more information. \ No newline at end of file +Similarly to the preview head HTML, preview body HTML can also be updated programmatically using a preset. See [Preview/Manager templates](../addons/writing-presets.md#previewmanager-templates) for more information. diff --git a/docs/configure/theming.md b/docs/configure/theming.md index 4b79632a7b7a..e2928d0826cd 100644 --- a/docs/configure/theming.md +++ b/docs/configure/theming.md @@ -8,7 +8,7 @@ Storybook is theme-able using a lightweight theming API. It's possible to theme Storybook globally. -Storybook includes two 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. +Storybook includes two themes that look good out of the box: "light" and "dark". 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`](https://www.npmjs.com/package/@storybook/addons) and [`@storybook/theming`](https://www.npmjs.com/package/@storybook/theming) packages. diff --git a/docs/configure/webpack.md b/docs/configure/webpack.md index e39670004848..19657a0a79d2 100644 --- a/docs/configure/webpack.md +++ b/docs/configure/webpack.md @@ -109,7 +109,7 @@ The following code snippet shows how you can replace the loaders from Storybook
-💡 Note: Projects initialized via generators (e.g, Vue CLI) may require that you import their own webpack config file (i.e., /projectRoot/node_modules/@vue/cli-service/webpack.config.js) to use a certain feature with Storybook. For other generators, make sure to check the documentation for instructions. +💡 Projects initialized via generators (e.g, Vue CLI) may require that you import their own webpack config file (i.e., /projectRoot/node_modules/@vue/cli-service/webpack.config.js) to use a certain feature with Storybook. For other generators, make sure to check the documentation for instructions.
### Bundle splitting diff --git a/docs/contribute/how-to-reproduce.md b/docs/contribute/how-to-reproduce.md index 35f873c11bc9..09bfafecc0d9 100644 --- a/docs/contribute/how-to-reproduce.md +++ b/docs/contribute/how-to-reproduce.md @@ -17,7 +17,7 @@ npx sb@next repro ```
-💡 Note: You can add the --template flag to include a custom template. +💡 You can add the --template flag to include a custom template.
Next, select the framework, for example, `react`: @@ -33,7 +33,7 @@ Finally, enter a location for your reproduction: ![Storybook reproduction location](./storybook-reproduction-generator-location-optimized.png)
-💡 Note: If you don't provide a full path for the reproduction it will be generated in the current directory. +💡 If you don't provide a full path for the reproduction it will be generated in the current directory.
If everything worked as it should, you should have a fully functional Storybook set up in your local environment. @@ -68,7 +68,7 @@ An excellent way to check your reproduction is to have it deployed online. We r ### Helpful resources when working with Chromatic -- [Publish Storybook](../workflows/publish-storybook.md) +- [Publish Storybook](../sharing/publish-storybook.md) - [Setup Chromatic](https://www.chromatic.com/docs/setup) - [Automate Chromatic with continuous integration](https://www.chromatic.com/docs/ci) diff --git a/docs/contribute/new-snippets.md b/docs/contribute/new-snippets.md index 19970f08f33b..8f3aca9df89b 100644 --- a/docs/contribute/new-snippets.md +++ b/docs/contribute/new-snippets.md @@ -80,7 +80,7 @@ Create the file `ember/your-component.js.mdx`, similar to the other frameworks, ```
-💡 Note: Code snippets are divided into various file extensions, if you're contributing a TypeScript file use .ts.mdx, or if you're adding MDX files use .mdx.mdx . +💡 Code snippets are divided into various file extensions, if you're contributing a TypeScript file use .ts.mdx, or if you're adding MDX files use .mdx.mdx .
Go through the rest of the documentation and repeat the process. @@ -110,7 +110,7 @@ yarn start:skip-addons ```
-💡 Note: During the start process if there's an issue with the the documentation, the process will stop and you'll get a notification. +💡 During the start process if there's an issue with the the documentation, the process will stop and you'll get a notification.
Open a browser window to `http://localhost:8000`, click the Docs link, and select your framework from the dropdown. diff --git a/docs/essentials/actions.md b/docs/essentials/actions.md index d78d1b7f54ae..aeec1b2d1e50 100644 --- a/docs/essentials/actions.md +++ b/docs/essentials/actions.md @@ -63,7 +63,7 @@ If you need more granular control over which `argTypes` are matched, you can adj
-💡 NOTE: If you're generating argTypes with another addon (like [docs](../writing-docs/introduction.md), which is the common behavior), ensure the actions addon AFTER the other addon. You can do this by listing it later in the addons registration code in [`.storybook/main.js`](../configure/overview.md#configure-story-rendering). This is default in [essentials](./introduction.md). +💡 If you're generating argTypes with another addon (like [docs](../writing-docs/introduction.md), which is the common behavior), ensure the actions addon AFTER the other addon. You can do this by listing it later in the addons registration code in [`.storybook/main.js`](../configure/overview.md#configure-story-rendering). This is default in [essentials](./introduction.md).
diff --git a/docs/essentials/controls.md b/docs/essentials/controls.md index 17ccc30ef02d..9ee5f664cfb5 100644 --- a/docs/essentials/controls.md +++ b/docs/essentials/controls.md @@ -105,7 +105,7 @@ To do so, use the `matchers` property in the `controls` parameter: ## Fully custom args -Up until now, we only used auto-generated controls based on the component we're writing stories for. If we are writing [complex stories](../workflows/stories-for-multiple-components.md), we may want to add controls for args that aren’t part of the component. +Up until now, we only used auto-generated controls based on the component we're writing stories for. If we are writing [complex stories](../writing-stories/stories-for-multiple-components.md), we may want to add controls for args that aren’t part of the component. diff --git a/docs/essentials/interactions.md b/docs/essentials/interactions.md index a7dba42acebf..eafc1e73aeba 100644 --- a/docs/essentials/interactions.md +++ b/docs/essentials/interactions.md @@ -2,24 +2,28 @@ title: 'Interactions' --- -The `play` function in Storybook allows you to simulate user interactions to run after a story renders. With the Interactions addon, you have a way to visualize and debug these interactions. +The [`play`](../writing-stories/play-function.md) function in Storybook allows you to simulate user interactions to run after a story renders. With the [Interactions](https://storybook.js.org/addons/@storybook/addon-interactions/) addon, you have a way to visualize and debug these interactions. ## Play function for interactions -Stories isolate and capture component states in a structured manner. While developing a component, you can quickly cycle through the stories to verify the look and feel. Each story specifies all the inputs required to reproduce a specific state. You can even mock context and API calls. That allows you to handle most use cases of a component. But what about states that require user interaction? +Stories isolate and capture component states in a structured manner. While developing a component, you can quickly cycle through the stories to verify the look and feel. Each story specifies all the inputs required to reproduce a specific state. You can even mock context and API calls, allowing you to handle most use cases of a component. But what about states that require user interaction? -For example, clicking a button to open/close a dialog box, dragging a list item to reorder it or filling out a form to check for validation errors. To test those behaviours, you have to interact with the components as a user would. Interactive stories enable you to automate these interactions using a play function. These are small snippets of code that script out the exact steps a human would take to interact with the component. It's then executed as soon as the story is rendered. +For example, clicking a button to open/close a dialog box, dragging a list item to reorder it, or filling out a form to check for validation errors. To test those behaviors, you have to interact with the components as a user would. Interactive stories enable you to automate these interactions using a play function. They are small snippets of code that run once the story finishes rendering, emulating the exact steps a user would take to interact with the component. ### Powered by Testing Library and Jest -The interactions are written using a Storybook-instrumented versions of Testing Library and Jest. That gives you a familiar developer-friendly syntax to interact with the DOM and make assertions, but with extra telemetry to help with debugging. +The interactions are written using a Storybook-instrumented version of [Testing Library](https://testing-library.com/) and [Jest](https://jestjs.io/). That gives you a familiar developer-friendly syntax to interact with the DOM and make assertions, but with extra telemetry to help with debugging. ## Installation Since Interactions is still experimental, it doesn't yet ship with Storybook by default. As such, you'll have to install it. You may also want to add our wrappers for Testing Library and Jest. ```shell -yarn add -D @storybook/addon-interactions @storybook/jest @storybook/testing-library +# With npm +npm install @storybook/addon-interactions @storybook/jest @storybook/testing-library --save-dev + +# With yarn +yarn add --dev @storybook/addon-interactions @storybook/jest @storybook/testing-library ``` Next, update [`.storybook/main.js`](../configure/overview.md#configure-story-rendering) to the following: @@ -35,16 +39,16 @@ Next, update [`.storybook/main.js`](../configure/overview.md#configure-story-ren
-⚠️ Note: Make sure to list `@storybook/addon-interactions` **after** `addon-essentials` (or `addon-actions`). +💡 Make sure to list `@storybook/addon-interactions` **after** `addon-essentials` (or `addon-actions`).
-Now when you run Storybook the Interactions addon will be enabled. +Now when you run Storybook, the Interactions addon will be enabled. -![Storybook Interactions installed and registered](./storybook-interactions-installed-registered.png) +![Storybook Interactions installed and registered](./addon-interactions-installed-registered.png) ## Writing interactions -Interactions run as part of the `play` function on your stories. We rely on Testing Library to do the heavy lifting. +Interactions run as part of the `play` function of your stories. We rely on Testing Library to do the heavy lifting. Make sure to import the Storybook wrappers for Jest and Testing Library rather than importing Jest and Testing Library directly. @@ -58,8 +62,8 @@ Make sure to import the Storybook wrappers for Jest and Testing Library rather t -The above example uses the `canvasElement` to scope your element queries to the current story. This is important if you want your play functions to eventually be compatible with Storybook Docs, which renders multiple components on the same page. +The above example uses the `canvasElement` to scope your element queries to the current story. It's essential if you want your play functions to eventually be compatible with Storybook Docs, which renders multiple components on the same page. -While you can refer to the [Testing Library documentation](https://testing-library.com/docs/) for details on how to use it, there's an important detail that's different when using the Storybook wrapper: **method invocations must be `await`-ed**. This allows you to step back and forth through your interactions using the debugger. +While you can refer to the [Testing Library documentation](https://testing-library.com/docs/) for details on how to use it, there's an important detail that's different when using the Storybook wrapper: **method invocations must be `await`-ed**. It allows you to step back and forth through your interactions using the debugger. Any `args` that have been marked as an Action, either using the [argTypes annotation](./actions.md#action-argtype-annotation) or the [argTypesRegex](./actions.md#automatically-matching-args), will be automatically converted to a [Jest mock function](https://jestjs.io/docs/mock-function-api) (spy). This allows you to make assertions about calls to these functions. diff --git a/docs/essentials/measure-and-outline.md b/docs/essentials/measure-and-outline.md index b6fe17dfb804..ebb804ae27d5 100644 --- a/docs/essentials/measure-and-outline.md +++ b/docs/essentials/measure-and-outline.md @@ -15,7 +15,7 @@ With Storybook's Measure addon, you can quickly visualize each component's measu
-💡 Note: Alternatively you can press the m key on your keyboard to toggle the addon. +💡 Alternatively you can press the m key on your keyboard to toggle the addon.
## Outline addon diff --git a/docs/essentials/toolbars-and-globals.md b/docs/essentials/toolbars-and-globals.md index efa82574d2aa..99f18673735d 100644 --- a/docs/essentials/toolbars-and-globals.md +++ b/docs/essentials/toolbars-and-globals.md @@ -68,7 +68,7 @@ In your [`.storybook/preview.js`](../configure/overview.md#configure-story-rende
-💡 The icon element used in the examples loads the icons from the @storybook/components package. See [here](../workflows/faq.md#what-icons-are-available-for-my-toolbar-or-my-addon) for the list of available icons that you can use. +💡 The icon element used in the examples loads the icons from the @storybook/components package. See [here](../faq.md#what-icons-are-available-for-my-toolbar-or-my-addon) for the list of available icons that you can use.
diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 000000000000..7669ea6db84e --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,377 @@ +--- +title: 'Frequently Asked Questions' +--- + +Here are some answers to frequently asked questions. If you have a question, you can ask it by opening an issue on the [Storybook Repository](https://github.com/storybookjs/storybook/). + +### How can I opt-out of Angular Ivy? + +In case you are having trouble with Angular Ivy you can deactivate it in your `main.js`: + +```javascript +module.exports = { + stories: [ + /* ... */ + ], + addons: [ + /* ... */ + ], + angularOptions: { + enableIvy: false, + }, +}; +``` + +Please report any issues related to Ivy in our [GitHub Issue Tracker](https://github.com/storybookjs/storybook/labels/app%3A%20angular) as the support for View Engine will be dropped in a future release of Angular. + +### How can I run coverage tests with Create React App and leave out stories? + +Create React App does not allow providing options to Jest in your `package.json`, however you can run `jest` with commandline arguments: + +```sh +npm test -- --coverage --collectCoverageFrom='["src/**/*.{js,jsx}","!src/**/stories/*"]' +``` + +
+💡 If you're using yarn as a package manager, you'll need to adjust the command accordingly. +
+ +### I see `ReferenceError: React is not defined` when using Storybook with Next.js + +Next automatically defines `React` for all of your files via a babel plugin. In Storybook, you can solve this either by: + +1. Adding `import React from 'react'` to your component files. +2. Adding a `.babelrc` that includes [`babel-plugin-react-require`](https://www.npmjs.com/package/babel-plugin-react-require) + +### How do I setup Storybook to share Webpack configuration with Next.js? + +You can generally reuse webpack rules by placing them in a file that is `require()`-ed from both your `next.config.js` and your `.storybook/main.js` files. For example: + +```js +module.exports = { + webpackFinal: async (baseConfig) => { + const nextConfig = require('/path/to/next.config.js'); + + // merge whatever from nextConfig into the webpack config storybook will use + return { ...baseConfig, ...nextConfig }; + }, +}; +``` + +### How do I setup React Fast Refresh with Storybook? + +Fast refresh is an opt-in feature that can be used in Storybook React. +There are two ways that you can enable it, go ahead and pick one: + +- You can set a `FAST_REFRESH` environment variable in your `.env` file: + +``` +FAST_REFRESH=true +``` + +- Or you can set the following properties in your `.storybook/main.js` files: + +```js +module.exports = { + reactOptions: { + fastRefresh: true, + }, +}; +``` + +
+💡 Fast Refresh only works in development mode with React 16.10 or higher. +
+ +### Why is there no addons channel? + +A common error is that an addon tries to access the "channel", but the channel is not set. It can happen in a few different cases: + +1. You're trying to access addon channel (e.g., by calling `setOptions`) in a non-browser environment like Jest. You may need to add a channel mock: + + ```js + import { addons, mockChannel } from '@storybook/addons'; + + addons.setChannel(mockChannel()); + ``` + +2. In React Native, it's a special case documented in [#1192](https://github.com/storybookjs/storybook/issues/1192) + +### Can I modify React component state in stories? + +Not directly. If you control the component source, you can do something like this: + +```js +import React, { Component } from 'react'; + +export default { + title: 'MyComponent', +}; + +class MyComponent extends Component { + constructor(props) { + super(props); + + this.state = { + someVar: 'defaultValue', + ...props.initialState, + }; + } + // ... +} + +export const defaultView = () => ; +``` + +### Why aren't Controls visible in the Canvas panel but visible in the Docs panel? + +If you're adding Storybook's dependencies manually, make sure you include the [`@storybook/addon-controls`](https://www.npmjs.com/package/@storybook/addon-controls) dependency in your project and reference it in your `.storybook/main.js` as follows: + +```js +// .storybook/main.js + +module.exports = { + addons: ['@storybook/addon-controls'], +}; +``` + +### Why aren't the addons working in a composed Storybook? + +Composition is a new feature that we released with version 6.0, and there are still some limitations to it. + +For now, the addons you're using in a composed Storybook will not work. + +We're working on overcoming this limitation, and soon you'll be able to use them as if you are working with a non-composed Storybook. + +### Which community addons are compatible with the latest version of Storybook? + +Starting with Storybook version 6.0, we've introduced some great features aimed at streamlining your development workflow. + +With this, we would like to point out that if you plan on using addons created by our fantastic community, you need to consider that some of those addons might be working with an outdated version of Storybook. + +We're actively working to provide a better way to address this situation, but in the meantime, we would ask for a bit of caution on your end so that you don't run into unexpected problems. Let us know by creating an issue in the [Storybook repo](https://github.com/storybookjs/storybook/issues) so that we can gather information and create a curated list with those addons to help not only you but the rest of the community. + +### Is it possible to browse the documentation for past versions of Storybook? + +With the release of version 6.0, we updated our documentation as well. That doesn't mean that the old documentation was removed. We kept it to help you with your Storybook migration process. Use the content from the table below in conjunction with our migration guide . + +We're only covering versions 5.3 and 5.0 as they were important milestones for Storybook. If you want to go back in time a little more, you'll have to check the specific release in the monorepo. + +| Section | Page | Current Location | Version 5.3 location | Version 5.0 location | +| ---------------- | ----------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Get started | Install | [See current documentation](../get-started/install.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/guides/quick-start-guide) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/guides/quick-start-guide) | +| | What's a story | [See current documentation](../get-started/whats-a-story.md) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/guides) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/guides) | +| | Browse Stories | [See current documentation](../get-started/browse-stories.md) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/guides) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/guides) | +| | Setup | [See current documentation](../get-started/setup.md) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/guides) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/guides) | +| Write stories | Introduction | [See current documentation](../writing-stories/introduction.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/basics/writing-stories) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/basics/writing-stories) | +| | Parameters | [See current documentation](../writing-stories/parameters.md) | See versioned documentation here | Non existing feature or undocumented | +| | Decorators | [See current documentation](../writing-stories/decorators.md) | See versioned documentation here | See versioned documentation here | +| | Naming components and hierarchy | [See current documentation](../writing-stories/naming-components-and-hierarchy.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/basics/writing-stories) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/basics/writing-stories) | +| | Build pages and screens | [See current documentation](../writing-stories/build-pages-with-storybook.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | Stories for multiple components | [See current documentation](../writing-stories/stories-for-multiple-components.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| Write docs | DocsPage | [See current documentation](../writing-docs/docs-page.md) | See versioned addon documentation | Non existing feature or undocumented | +| | MDX | [See current documentation](../writing-docs/mdx.md) | See versioned addon documentation | Non existing feature or undocumented | +| | Doc Blocks | [See current documentation](../writing-docs/doc-blocks.md) | [See versioned addon documentation](https://github.com/storybookjs/storybook/tree/release/5.3/addons/docs/) | Non existing feature or undocumented | +| | Preview and build docs | [See current documentation](../writing-docs/build-documentation.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| Testing | Visual tests | [See current documentation](../writing-tests/visual-testing.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/testing/automated-visual-testing) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/testing/automated-visual-testing) | +| | Accessibility tests | [See current documentation](../writing-tests/accessibility-testing.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | Interaction tests | [See current documentation](../writing-tests/interaction-testing.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/testing/interaction-testing) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/testing/interaction-testing) | +| | Snapshot tests | [See current documentation](../writing-tests/snapshot-testing.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/testing/structural-testing) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/testing/structural-testing) | +| | Import stories in tests | [See current documentation](../writing-tests/importing-stories-in-tests.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/testing/react-ui-testing) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/testing/react-ui-testing) | +| Sharing | Publish Storybook | [See current documentation](../sharing/publish-storybook.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/basics/exporting-storybook) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/basics/exporting-storybook) | +| | Embed | [See current documentation](../sharing/embed.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | Composition | [See current documentation](../sharing/storybook-composition.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | Package Composition | [See current documentation](../sharing/package-composition.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| Essential addons | Controls | [See current documentation](../essentials/controls.md) | Controls are specific to version 6.0 see [Knobs versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/addons/knobs) | Controls are specific to version 6.0 see [Knobs versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/addons/knobs) | +| | Actions | [See current documentation](../essentials/actions.md) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/addons/actions) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/addons/actions) | +| | Viewport | [See current documentation](../essentials/viewport.md) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/addons/viewport) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/addons/viewport) | +| | Backgrounds | [See current documentation](../essentials/backgrounds.md) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/addons/backgrounds) | [See addon versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/addons/backgrounds) | +| | Toolbars and globals | [See current documentation](../essentials/toolbars-and-globals.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/basics/toolbar-guide) | Non existing feature or undocumented | +| Configure | Overview | [See current documentation](../configure/overview.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/overview) | [See versioned documentation](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/basics/writing-stories) | +| | Integration/Webpack | [See current documentation](../configure/webpack.md) | See versioned documentation | See versioned documentation | +| | Integration/Babel | [See current documentation](../configure/babel.md) | See versioned documentation here and [here](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/custom-babel-config) | See versioned documentation here and [here](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/custom-babel-config) | +| | Integration/Typescript | [See current documentation](../configure/typescript.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/typescript-config) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/typescript-config) | +| | Integration/Styling and CSS | [See current documentation](../configure/styling-and-css.md) | See versioned documentation | See versioned documentation | +| | Integration/Images and assets | [See current documentation](../configure/images-and-assets.md) | See versioned documentation | See versioned documentation | +| | Story rendering | [See current documentation](../configure/story-rendering.md) | See versioned documentation [here](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/add-custom-head-tags) and [here](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/add-custom-body) | See versioned documentation [here](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/add-custom-head-tags) | +| | Story Layout | [See current documentation](../configure/story-layout.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | User Interface/Features and behavior | [See current documentation](../configure/features-and-behavior.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/options-parameter) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/options-parameter) | +| | User Interface/Theming | [See current documentation](../configure/theming.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/theming) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/theming) | +| | User Interface/Sidebar & URLS | [See current documentation](../configure/sidebar-and-urls.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/options-parameter) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/options-parameter) | +| | Environment variables | [See current documentation](../configure/environment-variables.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/env-vars) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/env-vars) | +| Addons | Introduction | [See current documentation](../addons/introduction.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/addons/writing-addons) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/addons/writing-addons) | +| | Install addons | [See current documentation](../addons/install-addons.md) | [See versioned documentation](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/addons/using-addons/) | [See versioned documentation](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/addons/using-addons/) | +| | Writing Addons | [See current documentation](../addons/writing-addons.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/addons/writing-addons) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/addons/writing-addons) | +| | Writing Presets | [See current documentation](../addons/writing-presets.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/presets/writing-presets) | Non existing feature or undocumented | +| | Addons Knowledge Base | [See current documentation](../addons/addon-knowledge-base.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/addons/writing-addons) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/addons/writing-addons) | +| | Types of addons | [See current documentation](../addons/addon-types.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | Addons API | [See current documentation](../addons/addons-api.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/addons/api) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/addons/api) | +| API | Stories/Component Story Format | [See current documentation](../api/csf.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/formats/component-story-format) | Non existing feature or undocumented | +| | Stories/MDX syntax | [See current documentation](../api/mdx.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/formats/mdx-syntax) | Non existing feature or undocumented | +| | Stories/StoriesOF format (see note below) | [See current documentation](../../lib/core/docs/storiesOf.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/formats/storiesof-api) | Non existing feature or undocumented | +| | Frameworks | [See current documentation](../api/new-frameworks.md) | Non existing feature or undocumented | Non existing feature or undocumented | +| | CLI options | [See current documentation](../api/cli-options.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/cli-options) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/cli-options) | + +
+With the release of version 5.3, we've updated how you can write your stories more compactly and easily. It doesn't mean that the storiesOf format has been removed. For the time being, we're still supporting it, and we have documentation for it. But be advised that this is bound to change in the future. +
+ +### What icons are available for my toolbar or my addon? + +With the [`@storybook/components`](https://www.npmjs.com/package/@storybook/components) package, you get a set of icons that you can use to customize your UI. Use the table below as a reference while writing your addon or defining your Storybook global types. +Go through this [story](https://5a375b97f4b14f0020b0cda3-wbeulgbetj.chromatic.com/?path=/story/basics-icon--labels) to see how the icons look. + +| accessibility | accessibilityalt | add | admin | alert | +| -------------- | ---------------- | ------------ | ------------ | ------------- | +| arrowdown | arrowleft | arrowleftalt | arrowright | arrowrightalt | +| arrowup | back | basket | batchaccept | batchdeny | +| beaker | bell | bitbucket | book | bookmark | +| bookmarkhollow | bottombar | box | branch | browser | +| button | calendar | camera | category | certificate | +| check | chevrondown | chromatic | circle | circlehollow | +| close | closeAlt | cog | collapse | comment | +| commit | compass | component | contrast | copy | +| cpu | credit | cross | dashboard | database | +| delete | discord | docchart | doclist | document | +| download | edit | ellipsis | email | expand | +| expandalt | eye | eyeclose | facebook | facehappy | +| faceneutral | facesad | filter | flag | folder | +| form | gdrive | github | gitlab | globe | +| google | graphbar | graphline | graphql | grid | +| grow | heart | hearthollow | home | hourglass | +| info | key | lightning | lightningoff | link | +| listunordered | location | lock | markup | medium | +| memory | menu | merge | mirror | mobile | +| nut | outbox | outline | paintbrush | paperclip | +| paragraph | phone | photo | pin | play | +| plus | power | print | proceed | profile | +| pullrequest | question | redirect | redux | reply | +| repository | requestchange | rss | search | share | +| sharealt | shield | sidebar | sidebaralt | speaker | +| star | starhollow | stop | structure | subtract | +| support | switchalt | sync | tablet | thumbsup | +| time | timer | transfer | trash | twitter | +| undo | unfold | unlock | upload | user | +| useradd | useralt | users | video | watch | +| wrench | youtube | zoom | zoomout | zoomreset | + +### I see a "No Preview" error with a Storybook production build + +If you're using the `serve` package to verify your production build of Storybook, you'll get that error. It relates to how `serve` handles rewrites. For instance, `/iframe.html` is rewritten into `/iframe`, and you'll get that error. + +We recommend that you use [http-server](https://www.npmjs.com/package/http-server) instead and use the following command to preview Storybook: + +```shell +npx http-server storybook-static +``` + +
+Suppose you don't want to run the command above frequently. Add http-server as a development dependency and create a new script to preview your production build of Storybook. +
+ +### Can I use Storybook with Vue 3? + +Yes, with the release of version 6.2, Storybook now includes support for Vue 3. See the [install page](../get-started/install.md) for instructions. + +### Is snapshot testing with Storyshots supported for Vue 3? + +Yes, with the release of version 6.2, the [`Storyshots addon`](https://www.npmjs.com/package/@storybook/addon-storyshots) will automatically detect Vue 3 projects. + +If you run into a situation where this is not the case, you can adjust the `config` object and manually specify the framework (e.g., `vue3`). + +See our documentation on how to customize the [Storyshots configuration](./snapshot-testing.md). + +### Why are my MDX stories not working in IE11? + +Currently there's an issue when using MDX stories with IE11. This issue does not apply to [DocsPage](../writing-docs/docs-page.md). If you're interested in helping us fix this issue, read our Contribution guidelines and submit a pull request. + +### Why are my mocked GraphQL queries failing with Storybook's MSW addon? + +If you're working with Vue 3, you'll need to install [`@vue/apollo-composable`](https://www.npmjs.com/package/@vue/apollo-composable). With Svelte, you'll need to install [`@rollup/plugin-replace`](https://www.npmjs.com/package/@rollup/plugin-replace) and update your `rollup.config` file to the following: + +```js +// rollup.config + +// Boilerplate imports + +import replace from '@rollup/plugin-replace'; +const production = !process.env.ROLLUP_WATCH; + +// Remainder rollup.config implementation + +export default { + input: 'src/main.js', + output: { + sourcemap: true, + format: 'iife', + name: 'app', + file: 'public/build/bundle.js', + }, + plugins: [ + // Other plugins + + // Configures the replace plugin to allow Grapqhl Queries to work properly + replace({ + 'process.env.NODE_ENV': JSON.stringify('development'), + }), +}; +``` + +With Angular, the most common issue is the placement of the `mockServiceWorker.js` file. Use this [example](https://github.com/mswjs/examples/tree/master/examples/rest-angular/) as a point of reference. + +### Can I use other GraphQL providers with Storybook's MSW addon? + +Yes, check the [addon's examples](https://github.com/mswjs/msw-storybook-addon/tree/master/packages/docs/src/demos) to learn how to integrate different providers. + +### Can I mock GraphQL mutations with Storybook's MSW addon? + +No, currently, the MSW addon only has support for GraphQL queries. If you're interested in including this feature, open an issue in the [MSW addon repository](https://github.com/mswjs/msw-storybook-addon) and follow up with the maintainer. + +### Why aren't my code blocks highlighted with Storybook MDX + +Out of the box, Storybook provides syntax highlighting for a set of languages (e.g., Javascript, Markdown, CSS, HTML, Typescript, GraphQL) that you can use with your code blocks. If you're writing your custom code blocks with MDX, you'll need to import the syntax highlighter manually. For example, if you're adding a code block for SCSS, adjust your story to the following: + + + + + + + +
+💡 Check react-syntax-highlighter's documentation for a list of available languages. +
+ +Applying this small change will enable you to add syntax highlight for SCSS or any other language available. + +### How can my code detect if it is running in Storybook? + +You can do this by checking the value of `process.env.STORYBOOK`, which will +equal `'true'` when running in Storybook. Be careful — `process` may be +undefined when your code runs outside of Storybook. + +```javascript +export function isRunningInStorybook() { + try { + if (process.env.STORYBOOK) return true; + } catch { + // A ReferenceError will be thrown if process is undefined + } + + return false; +} +``` + +This works because Babel replaces `process.env.STORYBOOK` with the value of the +`STORYBOOK` environment variable. Because this is done through a Babel plugin, +the following will **NOT** work: + +```javascript +export function isRunningInStorybook() { + return typeof process?.env?.STORYBOOK !== 'undefined'; + // ReferenceError: process is not defined +} +``` \ No newline at end of file diff --git a/docs/get-started/examples.md b/docs/get-started/examples.md index bf49ab04cc8c..fea48a9db812 100644 --- a/docs/get-started/examples.md +++ b/docs/get-started/examples.md @@ -55,6 +55,8 @@ Learn how leading teams build design systems. - [Telefónica Mística UI](https://mistica-web.vercel.app/?path=/story/welcome-welcome--mistica) - [Codecademy Gamut](https://gamut.codecademy.com) - [Talend Coral](https://design.talend.com) +- [Vibe Design by monday.com](https://style.monday.com) +- [Reactstrap UI](https://reactstrap.github.io) + +```js +// oEmbed +https://5ccbc373887ca40020446347-bysekhynzd.chromatic.com/?path=/story/shadowboxcta--default + +// iframe embed + +``` + + + +
+ + + +## Embed a story without the toolbar + +To embed a plain story without Storybook's toolbar, click the "open canvas in new tab" icon in the top-right corner of Storybook to get the canvas URL. For example: + + + +```js +// oEmbed +https://5ccbc373887ca40020446347-bysekhynzd.chromatic.com/iframe.html?id=/story/shadowboxcta--default&viewMode=story + +// iframe embed + ; +``` + + +
+ + + +## Embed a docs page + +Embed a component's docs page by replacing the viewMode=story with viewMode=docs in the story URL. + + + +```js +// oEmbed +https://5ccbc373887ca40020446347-bysekhynzd.chromatic.com/iframe.html?id=/story/shadowboxcta--default&viewMode=docs + +// iframe embed + +``` + + + +
+ + + +## Embed stories on other platforms + +Every platform has different levels of embed support. Check the documentation of your service to see how they recommend embedding external content. + +
+ +How to embed in Medium + +Paste the Storybook URL into your Medium article, then press Enter. The embed will automatically resize to fit the story's height. + +While editing an article, Medium renders all embeds non-interactive. Once your article is published, it will become interactive. [Preview a demo on Medium](https://medium.com/@ghengeveld/embedding-storybook-on-medium-ce8a280c03ad). + + + +
+ +
+ +How to embed in Notion + +In your Notion document, type /embed, press Enter, and paste the story URL as the embed link. You can resize the embed as necessary. + +![Embed Notion](./embed-notion.png) + +
+ +
+ +How to embed in Ghost + +Type `/html` in your Ghost post, press Enter and paste the iframe URL. You can resize the embed via the width and height properties as required. + +![Embed Ghost](./embed-ghost.png) + +
diff --git a/docs/sharing/package-composition.md b/docs/sharing/package-composition.md new file mode 100644 index 000000000000..201dd798d3cd --- /dev/null +++ b/docs/sharing/package-composition.md @@ -0,0 +1,72 @@ +--- +title: 'Package Composition' +--- + +Storybook is widely used by component libraries and design systems. Design system authors can automatically compose their design systems inside their consumer’s Storybooks. + +For example, if you use a design system package, its stories can appear alongside your own. That makes it convenient to cross reference usage documentation without leaving Storybook. + +
+ +Composition via a package requires a secure integration between the service where you publish Storybook and Storybook’s own APIs. We recommend [publishing Storybook to Chromatic](./publish-storybook.md#publish-storybook-with-chromatic) for full support of these features. + +
+ +## For consumers + +Composition happens automatically if the package [supports](#for-authors) it. When you install the package, Storybook will load its stories alongside your own. + +![Package composition workflow](./package-composition.png) + +### Set up + +If you want to configure how the composed Storybook behaves, you can disable the `ref` element in your [`.storybook/main.js`](../configure/overview.md#configure-story-rendering) + + + + + + + +### Switching versions + +Change the version of the composed Storybook to see how the library evolves. This requires [configuration](#show-a-version-selector) from the package author. + +![Package composition workflow](./composition-versioning.png) + +## For authors + +Component library authors can expand adoption by composing their components in their consumer’s Storybooks. + +Add a `storybook` property in your published `package.json`that contains an object with a `url` field. Point the URL field to a published Storybook at the version you want. + +```json +// Your component library’s package.json +{ + "storybook": { + "url": "https://host.com/your-storybook-for-this-version" + } +} +``` + +### Automatic version selection + +If you're using [Chromatic](./publish-storybook.md#publish-storybook-with-chromatic), you can provide a single URL for your Storybook in the `storybook.url` field. You do not need to change the URL each time you publish a new version. Storybook will automatically find the correct URL for your package. For example: + +```json +{ + "storybook": { + "url": "https://master--xyz123.chromatic.com" + } +} +``` + +In this example `xyz123` is your Chromatic project id. Storybook will automatically compose in the Storybook published to that project corresponding to the version the user has installed. + +### Show a version selector + +If you're using [Chromatic](./publish-storybook.md#publish-storybook-with-chromatic), you can provide a list of versions for the user to [choose from](#switching-versions) to experiment with other versions of your package. diff --git a/docs/workflows/package-composition.png b/docs/sharing/package-composition.png similarity index 100% rename from docs/workflows/package-composition.png rename to docs/sharing/package-composition.png diff --git a/docs/sharing/prbadge-publish.png b/docs/sharing/prbadge-publish.png new file mode 100644 index 000000000000..5f4f58dae06c Binary files /dev/null and b/docs/sharing/prbadge-publish.png differ diff --git a/docs/sharing/publish-storybook.md b/docs/sharing/publish-storybook.md new file mode 100644 index 000000000000..babccd0407da --- /dev/null +++ b/docs/sharing/publish-storybook.md @@ -0,0 +1,136 @@ +--- +title: 'Publish Storybook' +--- + +Teams publish Storybook online to review and collaborate on works in progress. That allows developers, designers, PMs, and other stakeholders to check if the UI looks right without touching code or requiring a local dev environment. + + + +## Build Storybook as a static web application + +First, we'll need to build Storybook as a static web application. The functionality is already built-in and pre-configured. Run the following command inside your project's root directory: + +```shell +# With yarn +yarn build-storybook + +# With npm +npm run build-storybook +``` + +
+ +💡 You can provide additional flags to customize the command. Read more about the flag options [here](../api/cli-options.md). + +
+ +Storybook will create a static web application capable of being served by any web server. Preview it locally by running the following command: + +```shell +npx http-server ./path/to/build +``` + +## Publish Storybook with Chromatic + +Once you've built your Storybook as a static web app, you can publish it to your web host. We recommend [Chromatic](https://www.chromatic.com/), a free publishing service made for Storybook that documents, versions, and indexes your UI components securely in the cloud. + +![Storybook publishing workflow](./workflow-publish.png) + +To get started, sign up with your GitHub, GitLab, Bitbucket, or email and generate a unique _project-token_ for your project. + +Next, install the [Chromatic CLI](https://www.npmjs.com/package/chromatic) package from npm: + +```shell +# With npm +npm install --save-dev chromatic + +# With yarn +yarn add --dev chromatic +``` + +Run the following command after the package finishes installing. Make sure that you replace `your-project-token` with your own project token. + +```shell +npx chromatic --project-token= +``` + +When Chromatic finishes, you should have successfully deployed your Storybook. Preview it by clicking the link provided (i.e., https://random-uuid.chromatic.com). + +```shell +Build 1 published. + +View it online at https://www.chromatic.com/build?appId=...&number=1. +``` + +![Chromatic publish build](./build-publish-only.png) + +### Setup CI to publish automatically + +Configure your CI environment to publish your Storybook and [run Chromatic](https://www.chromatic.com/docs/ci)) whenever you push code to a repository. Let's see how to set it up using GitHub Actions. + +In your project's root directory, add a new file called `chromatic.yml` inside the `.github/workflows` directory: + + + + + + + +
+ +💡 Secrets are secure environment variables provided by GitHub so that you don't need to hard code your `project-token`. Read the [official documentation](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) to learn how to configure them. + +
+ +Commit and push the file. Congratulations, you've successfully automated publishing your Storybook. Now whenever you open a PR you’ll get a handy link to your published Storybook in your PR checks. + +![PR check publish](./prbadge-publish.png) + +### Review with your team + +Publishing Storybook as part of the development process makes it quick and easy to [gather team feedback](https://storybook.js.org/tutorials/design-systems-for-developers/react/en/review/). + +A common method to ask for review is to paste a link to the published Storybook in a pull request or Slack. + +If you publish your Storybook to Chromatic, you can use the [UI Review](https://www.chromatic.com/features/publish) feature to automatically scan your PRs for new and updated stories. That makes it easy to identify what changed and give feedback. + +![UI review in Chromatic](./workflow-uireview.png) + +## Publish Storybook to other services + +You can publish the static Storybook web app to many hosts. We maintain [`storybook-deployer`](https://github.com/storybookjs/storybook-deployer), a handy tool to help you publish to AWS or GitHub pages. + +But features like [Composition](./storybook-composition.md), [embed](./embed.md), history, and versioning require tighter integration with Storybook APIs and secure authentication. Your hosting provider may not be capable of supporting these features. Learn about the Component Publishing Protocol (CPP) to see what. + +
+ +

Component Publishing Protocol (CPP)

+ +Storybook can communicate with services that host built Storybooks online. This enables features such as [Composition](./storybook-composition.md). We categorize services via compliance with the "Component Publishing Protocol" (CPP) with various levels of support in Storybook. + +### CPP level 1 + +This level of service serves published Storybooks and makes the following available: + +- Versioned endpoints, URLs that resolve to different published Storybooks depending on a `version=x.y.z` query parameter (where `x.y.z` is the released version of the package). +- Support for `/stories.json` +- Support for `/metadata.json` and the `releases` field. + +Example: [Chromatic](https://www.chromatic.com/) + +### CPP level 0 + +This level of service can serve published Storybooks but has no further integration with Storybook’s APIs. + +Examples: [Netlify](https://www.netlify.com/), [S3](https://aws.amazon.com/en/s3/) + +
diff --git a/docs/workflows/reference-external-storybooks-composition.jpg b/docs/sharing/reference-external-storybooks-composition.jpg similarity index 100% rename from docs/workflows/reference-external-storybooks-composition.jpg rename to docs/sharing/reference-external-storybooks-composition.jpg diff --git a/docs/sharing/storybook-composition.md b/docs/sharing/storybook-composition.md new file mode 100644 index 000000000000..10a7ae7b2a07 --- /dev/null +++ b/docs/sharing/storybook-composition.md @@ -0,0 +1,112 @@ +--- +title: 'Storybook Composition' +--- + +Composition allows you to browse components from any Storybook accessible via URL inside your local Storybook. You can compose any [Storybook published online](./storybook-publish.md) or running locally no matter the view layer, tech stack, or dependencies. + +![Storybook reference external](./reference-external-storybooks-composition.jpg) + +You’ll see the composed Storybook’s stories in the sidebar alongside your own. This unlocks common workflows that teams often struggle with: + +- 👩‍💻 UI developers can quickly reference prior art without switching between Storybooks. +- 🎨 Design systems can expand adoption by composing themselves into their users’ Storybooks. +- 🛠 Frontend platform can audit how components are used across projects. +- 📚 View multiple Storybooks with different tech stacks in one place + +![Storybook composition](./combine-storybooks.png) + +## Compose published Storybooks + +In your [`.storybook/main.js`](../configure/overview.md#configure-story-rendering) file add a `refs` field with information about the reference Storybook. Pass in a URL to a statically built Storybook. + + + + + + + +
+ 💡 Limitation: Addons in composed Storybooks will not work as they normally do in non-composed Storybook. +
+ +## Compose local Storybooks + +You can also compose multiple Storybooks that are running locally. For instance, if you have a React Storybook and a Angular Storybook running on different ports you can update your configuration file (i.e., `.storybook/main.js`) and reference them; + + + + + + + +Adding this configuration will combine both the React and Angular Storybooks into your current one. When either of these changes, you’ll see the changes being applied automatically. Enabling you to develop both frameworks in sync. + +## Compose Storybooks per environment + +You can also compose Storybooks based on the current development environment (e.g, development, staging, production). For instance if the project you're working on has already a published Storybook, but also includes a version with cutting edge features not yet released you can adjust the composition based on that. For example: + + + + + + + +
+ +💡 Similar to the other fields available in Storybook’s configuration file, the `refs` field can also be a function that accepts a config parameter containing Storybook’s configuration object. Check the [webpack documentation](../configure/webpack.md#extending-storybooks-webpack-config) to learn more about it. + +
+ +### Improve your Storybook composition + +So far we've seen how we can use composition with local or published Storybooks. One thing worth mentioning as your Storybook will grow in time with your own stories, or through composition with other Storybooks, is that you can optimize the deployment process by including the following command in your workflow, run from your project root: + +```shell +npx sb extract +``` + +
+ +`sb extract` uses [Puppeteer](https://www.npmjs.com/package/puppeteer), which downloads and installs Chromium. Set the environment `SB_CHROMIUM_PATH` to configure your local Chromium installation. + +
+ +Running this command will generate a `stories.json` file in the default build directory (`storybook-static`) with the information related to your Storybook. Here’s an example of the file contents: + + + + + + + +Linking to a Storybook deployed using this approach will yield all the stories and other relevant information displayed in the UI. + +If you need, you can also add additional arguments to this command. For instance, if you want to generate the stories.json file into a custom directory you can use the following: + +```shell +npx sb extract my-built-storybook-directory my-other-directory/stories.json +``` + +When executed it will lookup a built Storybook in the `my-built-storybook-directory` and create the `stories.json` file in the `my-other-directory` with all the necessary information. + +
+ +💡 If you need to use the arguments, you’ll need to include both of them or the command will fail. + +
diff --git a/docs/workflows/storybook-publish-review-optimized.mp4 b/docs/sharing/storybook-publish-review-optimized.mp4 similarity index 100% rename from docs/workflows/storybook-publish-review-optimized.mp4 rename to docs/sharing/storybook-publish-review-optimized.mp4 diff --git a/docs/workflows/template-component-with-controls-optimized.mp4 b/docs/sharing/template-component-with-controls-optimized.mp4 similarity index 100% rename from docs/workflows/template-component-with-controls-optimized.mp4 rename to docs/sharing/template-component-with-controls-optimized.mp4 diff --git a/docs/workflows/workflow-publish.png b/docs/sharing/workflow-publish.png similarity index 100% rename from docs/workflows/workflow-publish.png rename to docs/sharing/workflow-publish.png diff --git a/docs/sharing/workflow-uireview.png b/docs/sharing/workflow-uireview.png new file mode 100644 index 000000000000..21274e32290d Binary files /dev/null and b/docs/sharing/workflow-uireview.png differ diff --git a/docs/snippets/angular/badge-story-starter-example.mdx.mdx b/docs/snippets/angular/badge-story-starter-example.mdx.mdx index 068128139499..87b96b0ee968 100644 --- a/docs/snippets/angular/badge-story-starter-example.mdx.mdx +++ b/docs/snippets/angular/badge-story-starter-example.mdx.mdx @@ -42,19 +42,16 @@ with unique URLs and isolated snapshot tests. template: `Warning`, }} - {{ template: `Neutral`, }} - {{ template: `Error`, }} - -``` \ No newline at end of file +``` diff --git a/docs/snippets/angular/badge-story.mdx.mdx b/docs/snippets/angular/badge-story.mdx.mdx index 9d99343f6015..e903fc6b8e56 100644 --- a/docs/snippets/angular/badge-story.mdx.mdx +++ b/docs/snippets/angular/badge-story.mdx.mdx @@ -13,7 +13,7 @@ export const Template = (args) => ({ props: args }); Let's define a story for our `Badge` component: - {Template.bind({})} - {Template.bind({})} - {Template.bind({})} - -``` \ No newline at end of file +``` diff --git a/docs/snippets/angular/mdx-canvas-multiple-stories.mdx.mdx b/docs/snippets/angular/mdx-canvas-multiple-stories.mdx.mdx index 9c803f6368d7..8f34d74ce589 100644 --- a/docs/snippets/angular/mdx-canvas-multiple-stories.mdx.mdx +++ b/docs/snippets/angular/mdx-canvas-multiple-stories.mdx.mdx @@ -7,7 +7,6 @@ import { Badge } from './badge.component'; - export const Template = (args) => ({ props: args }); @@ -19,7 +18,6 @@ export const Template = (args) => ({ props: args }); }}> {Template.bind({})} - ({ props: args }); }}> {Template.bind({})} - ({ props: args }); {Template.bind({})} -``` \ No newline at end of file +``` diff --git a/docs/snippets/common/chromatic-github-action.js.mdx b/docs/snippets/common/chromatic-github-action.js.mdx new file mode 100644 index 000000000000..a83501f7b140 --- /dev/null +++ b/docs/snippets/common/chromatic-github-action.js.mdx @@ -0,0 +1,26 @@ +```shell +# .github/workflows/chromatic.yml + +# Workflow name +name: 'Chromatic Publish' + +# Event for the workflow +on: push + +# List of jobs +jobs: + test: + # Operating System + runs-on: ubuntu-latest + # Job steps + steps: + - uses: actions/checkout@v1 + - run: yarn + #👇 Adds Chromatic as a step in the workflow + - uses: chromaui/action@v1 + # Options required for Chromatic's GitHub Action + with: + #👇 Chromatic projectToken, + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} +``` diff --git a/docs/snippets/common/storybook-main-ts-module-resolution.js.mdx b/docs/snippets/common/storybook-main-ts-module-resolution.js.mdx index 20fbcf426975..032e0aa9005a 100644 --- a/docs/snippets/common/storybook-main-ts-module-resolution.js.mdx +++ b/docs/snippets/common/storybook-main-ts-module-resolution.js.mdx @@ -1,6 +1,8 @@ ```js // .storybook/main.js +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); + module.exports = { webpackFinal: async (config) => { config.resolve.plugins = [ @@ -12,4 +14,4 @@ module.exports = { return config; }, }; -``` \ No newline at end of file +``` diff --git a/docs/snippets/html/button-story-with-args.js.mdx b/docs/snippets/html/button-story-with-args.js.mdx new file mode 100644 index 000000000000..3466f20cbcd8 --- /dev/null +++ b/docs/snippets/html/button-story-with-args.js.mdx @@ -0,0 +1,27 @@ +```js +export default { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Button', +}; + +//👇 We create a “template” of how args map to rendering +const Template = (args) => { + const btn = document.createElement('button'); + btn.innerText = args.label; + + const mode = args.primary ? 'storybook-button--primary' : 'storybook-button--secondary'; + btn.className = ['storybook-button', 'storybook-button--medium', mode].join(' '); + + return btn; +}; + +//👇 Each story then reuses that template +export const Primary = Template.bind({}); +Primary.args = { + primary: true, + label: 'Button', +}; +``` diff --git a/docs/snippets/html/button-story-with-args.ts.mdx b/docs/snippets/html/button-story-with-args.ts.mdx new file mode 100644 index 000000000000..e3f866a17886 --- /dev/null +++ b/docs/snippets/html/button-story-with-args.ts.mdx @@ -0,0 +1,29 @@ +```ts +import { Meta, StoryFn } from '@storybook/html'; + +export default { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Button', +} as Meta; + +//👇 We create a “template” of how args map to rendering +const Template: StoryFn = (args): HTMLButtonElement => { + const btn = document.createElement('button'); + btn.innerText = args.label; + + const mode = args.primary ? 'storybook-button--primary' : 'storybook-button--secondary'; + btn.className = ['storybook-button', 'storybook-button--medium', mode].join(' '); + + return btn; +}; + +//👇 Each story then reuses that template +export const Primary = Template.bind({}); +Primary.args = { + primary: true, + label: 'Button', +}; +``` diff --git a/docs/snippets/html/button-story.js.mdx b/docs/snippets/html/button-story.js.mdx new file mode 100644 index 000000000000..743c489eff80 --- /dev/null +++ b/docs/snippets/html/button-story.js.mdx @@ -0,0 +1,22 @@ +```js +export default { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/html/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Button', +}; + +export const Primary = () => { + const btn = document.createElement('button'); + btn.innerText = 'Button'; + + btn.className = [ + 'storybook-button', + 'storybook-button--medium', + 'storybook-button--primary', + ].join(' '); + + return btn; +}; +``` diff --git a/docs/snippets/html/button-story.ts.mdx b/docs/snippets/html/button-story.ts.mdx new file mode 100644 index 000000000000..104aa6ccbd19 --- /dev/null +++ b/docs/snippets/html/button-story.ts.mdx @@ -0,0 +1,24 @@ +```ts +import { Meta, StoryFn } from '@storybook/html'; + +export default { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/html/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Button', +} as Meta; + +export const Primary: StoryFn = (): HTMLButtonElement => { + const btn = document.createElement('button'); + btn.innerText = 'Button'; + + btn.className = [ + 'storybook-button', + 'storybook-button--medium', + 'storybook-button--primary', + ].join(' '); + + return btn; +}; +``` diff --git a/docs/snippets/react/badge-story-starter-example.mdx.mdx b/docs/snippets/react/badge-story-starter-example.mdx.mdx index ad80c445a81f..5a025429d643 100644 --- a/docs/snippets/react/badge-story-starter-example.mdx.mdx +++ b/docs/snippets/react/badge-story-starter-example.mdx.mdx @@ -32,15 +32,12 @@ with unique URLs and isolated snapshot tests. Warning - Neutral - Error - @@ -48,4 +45,4 @@ with unique URLs and isolated snapshot tests. -``` \ No newline at end of file +``` diff --git a/docs/snippets/react/badge-story.mdx.mdx b/docs/snippets/react/badge-story.mdx.mdx index f17f1c53dbfa..5a7139b754ac 100644 --- a/docs/snippets/react/badge-story.mdx.mdx +++ b/docs/snippets/react/badge-story.mdx.mdx @@ -14,7 +14,7 @@ export const Template = (args) => Let's define a story for our `Badge` component: - {Template.bind({})} - {Template.bind({})} - {Template.bind({})} - -``` \ No newline at end of file +``` diff --git a/docs/snippets/react/checkbox-story-starter-example.mdx.mdx b/docs/snippets/react/checkbox-story-starter-example.mdx.mdx index e87c1b9ed624..438833d3ba88 100644 --- a/docs/snippets/react/checkbox-story-starter-example.mdx.mdx +++ b/docs/snippets/react/checkbox-story-starter-example.mdx.mdx @@ -11,7 +11,7 @@ import { Checkbox } from './Checkbox'; With `MDX`, we can define a story for `Checkbox` right in the middle of our Markdown documentation. -Canvas> +
@@ -20,4 +20,4 @@ Canvas>
-``` \ No newline at end of file +``` diff --git a/docs/snippets/react/mdx-canvas-multiple-stories.mdx.mdx b/docs/snippets/react/mdx-canvas-multiple-stories.mdx.mdx index 7cbbc3ceea4f..65bc82c2e41f 100644 --- a/docs/snippets/react/mdx-canvas-multiple-stories.mdx.mdx +++ b/docs/snippets/react/mdx-canvas-multiple-stories.mdx.mdx @@ -18,7 +18,6 @@ export const Template = (args) => ; }}> {Template.bind({})}
- ; }}> {Template.bind({})} - ; {Template.bind({})} -``` \ No newline at end of file +``` diff --git a/docs/snippets/svelte/badge-story.mdx.mdx b/docs/snippets/svelte/badge-story.mdx.mdx index 6ca4071526d0..06a6c95b9386 100644 --- a/docs/snippets/svelte/badge-story.mdx.mdx +++ b/docs/snippets/svelte/badge-story.mdx.mdx @@ -16,7 +16,7 @@ export const Template = (args) => ({ Let's define a story for our `Badge` component: - {Template.bind({})} - {Template.bind({})} - {Template.bind({})} - -``` \ No newline at end of file +``` diff --git a/docs/snippets/svelte/mdx-canvas-multiple-stories.mdx.mdx b/docs/snippets/svelte/mdx-canvas-multiple-stories.mdx.mdx index 338eadfbfe43..2b5c5d2ee1a4 100644 --- a/docs/snippets/svelte/mdx-canvas-multiple-stories.mdx.mdx +++ b/docs/snippets/svelte/mdx-canvas-multiple-stories.mdx.mdx @@ -21,7 +21,6 @@ export const Template = (args) => ({ }}> {Template.bind({})} - ({ }}> {Template.bind({})} - ({ {Template.bind({})} -``` \ No newline at end of file +``` diff --git a/docs/snippets/vue/badge-story-starter-example.mdx.mdx b/docs/snippets/vue/badge-story-starter-example.mdx.mdx index 01cdacad932b..1905cd91cac4 100644 --- a/docs/snippets/vue/badge-story-starter-example.mdx.mdx +++ b/docs/snippets/vue/badge-story-starter-example.mdx.mdx @@ -47,7 +47,6 @@ with unique URLs and isolated snapshot tests. }; }}
- {() => { return { @@ -56,7 +55,6 @@ with unique URLs and isolated snapshot tests. }; }} - {() => { return { @@ -65,7 +63,6 @@ with unique URLs and isolated snapshot tests. }; }} - {() => { return { @@ -79,4 +76,4 @@ with unique URLs and isolated snapshot tests. }} -``` \ No newline at end of file +``` diff --git a/docs/snippets/vue/badge-story.mdx-2.mdx.mdx b/docs/snippets/vue/badge-story.mdx-2.mdx.mdx index d80187bccec8..fb34bac4968d 100644 --- a/docs/snippets/vue/badge-story.mdx-2.mdx.mdx +++ b/docs/snippets/vue/badge-story.mdx-2.mdx.mdx @@ -13,12 +13,11 @@ export const Template = (args, { argTypes }) => ({ template: '', }); - # Badge Let's define a story for our `Badge` component: - {Template.bind({})} - {Template.bind({})} - -``` \ No newline at end of file +``` diff --git a/docs/snippets/vue/mdx-canvas-multiple-stories.mdx-2.mdx.mdx b/docs/snippets/vue/mdx-canvas-multiple-stories.mdx-2.mdx.mdx index 929bbb2bef45..32349dcf0594 100644 --- a/docs/snippets/vue/mdx-canvas-multiple-stories.mdx-2.mdx.mdx +++ b/docs/snippets/vue/mdx-canvas-multiple-stories.mdx-2.mdx.mdx @@ -22,7 +22,6 @@ export const Template = (args, { argTypes }) => ({ }}> {Template.bind({})} - ({ }}> {Template.bind({})} - ({ }}> {Template.bind({})} - ({ props: Object.keys(argTypes), template: ` -