Merge branch 'next' into pr/fraincs/8897

This commit is contained in:
Norbert de Langen 2020-01-21 11:54:13 +01:00
commit ae27b715f0
No known key found for this signature in database
GPG Key ID: 976651DA156C2825
841 changed files with 16829 additions and 10575 deletions

View File

@ -48,13 +48,6 @@ module.exports = {
test: withTests,
},
},
{
test: './examples/rax-kitchen-sink',
presets: [
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }],
['babel-preset-rax', { development: process.env.BABEL_ENV === 'development' }],
],
},
{
test: './lib',
presets: [

View File

@ -51,7 +51,55 @@ jobs:
command: yarn chromatic --storybook-build-dir="built-storybooks/official-storybook" --exit-zero-on-changes --app-code="ab7m45tp9p"
- run:
name: Run chromatic on the pre-built angular example
command: yarn chromatic --storybook-build-dir="built-storybooks/angular-cli" --exit-zero-on-changes --app-code="tl92yzsj6w"
command: yarn chromatic --storybook-build-dir="built-storybooks/angular-cli" --app-code="tl92yzsj6w"
- run:
name: Run chromatic on the pre-built cra-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/cra-kitchen-sink" --app-code="tg55gajmdt"
- run:
name: Run chromatic on the pre-built cra-react15 example
command: yarn chromatic --storybook-build-dir="built-storybooks/cra-react15" --app-code="gxk7iqej3wt"
- run:
name: Run chromatic on the pre-built cra-ts-essentials example
command: yarn chromatic --storybook-build-dir="built-storybooks/cra-ts-essentials" --app-code="b311ypk6of"
- run:
name: Run chromatic on the pre-built cra-ts-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/cra-ts-kitchen-sink" --app-code="19whyj1tlac"
- run:
name: Run chromatic on the pre-built dev-kits example
command: yarn chromatic --storybook-build-dir="built-storybooks/dev-kits" --app-code="7yykp9ifdxx"
- run:
name: Run chromatic on the pre-built ember-cli example
command: yarn chromatic --storybook-build-dir="built-storybooks/ember-cli" --app-code="19z23qxndju"
- run:
name: Run chromatic on the pre-built html-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/html-kitchen-sink" --app-code="e8zolxoyg8o"
- run:
name: Run chromatic on the pre-built marko-cli example
command: yarn chromatic --storybook-build-dir="built-storybooks/marko-cli" --app-code="qaegx64axu"
- run:
name: Run chromatic on the pre-built mithril-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/mithril-kitchen-sink" --app-code="8adgm46jzk8"
- run:
name: Run chromatic on the pre-built polymer-cli example
command: yarn chromatic --storybook-build-dir="built-storybooks/polymer-cli" --app-code="o6jl9kmh0qd"
- run:
name: Run chromatic on the pre-built preact-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/preact-kitchen-sink" --app-code="ls0ikhnwqt"
- run:
name: Run chromatic on the pre-built rax-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/rax-kitchen-sink" --app-code="4co6vptx8qo"
- run:
name: Run chromatic on the pre-built riot-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/riot-kitchen-sink" --app-code="g2dp3lnr34a"
- run:
name: Run chromatic on the pre-built svelte-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/svelte-kitchen-sink" --app-code="8ob73wgl995"
- run:
name: Run chromatic on the pre-built vue-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/vue-kitchen-sink" --app-code="cyxj0e38bqj"
- run:
name: Run chromatic on the pre-built web-components-kitchen-sink example
command: yarn chromatic --storybook-build-dir="built-storybooks/web-components-kitchen-sink" --app-code="npm5gsofwkf"
packtracker:
<<: *defaults

View File

@ -15,9 +15,11 @@ scripts/storage
examples/ember-cli/.storybook/preview-head.html
examples/official-storybook/tests/addon-jest.test.js
examples/cra-ts-kitchen-sink/*.json
examples/cra-ts-kitchen-sink/public/*.json
examples/cra-ts-kitchen-sink/public/*.html
examples/cra-ts-kitchen-sink/public/*
examples/cra-ts-essentials/*.json
examples/cra-ts-essentials/public/*
examples/rax-kitchen-sink/src/document/*
.yarn
!.remarkrc.js
!.babelrc.js
!.eslintrc.js

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
.yarn/releases/yarn-*.js linguist-generated=true

View File

@ -15,6 +15,7 @@
'app: angular': ["app/angular/**"]
'app: polymer ': ["app/polymer/**"]
'app: preact': ["app/preact/**"]
'app: rax': ["app/rax/**"]
'app: react-native': ["app/react-native/**"]
'app: react': ["app/react/**"]
'app: vue': ["app/vue/**"]

View File

@ -4,10 +4,11 @@
'app: marko': ['nm123github']
'app: polymer': ['stijnkoopal', 'ndelangen']
'app: preact': ['BartWaardenburg']
'app: rax': ['SoloJiang']
'app: react-native': ['benoitdion', 'gongreg']
'app: react-native-server': ['benoitdion', 'gongreg']
'app: svelte': ['rixo', 'cam-stitt', 'plumpNation']
'app: vue': ['backbone87', 'elevatebart', 'pksunkara', 'Aaron-Pool']
'app: vue': ['backbone87', 'elevatebart', 'pksunkara', 'Aaron-Pool', 'pocka']
'app: web-components': ['daKmoR']
'api: addons': ['ndelangen']
'addon: a11y': ['CodeByAlex', 'Armanio', 'jsomsanith']

32
.github/workflows/tests-puppeteer.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Puppeteer & A11y tests
on: [push]
jobs:
build:
name: Puppeteer & A11y tests
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: '10.x'
- uses: actions/checkout@v1
- name: Cache node modules
uses: actions/cache@v1
with:
path: node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.OS }}-build-${{ env.cache-name }}-
${{ runner.OS }}-build-
${{ runner.OS }}-
- name: install, bootstrap
run: |
yarn bootstrap --core
- name: build storybook
run: |
yarn --cwd examples/official-storybook build-storybook
- name: test
run: |
yarn test --puppeteer

3
.gitignore vendored
View File

@ -2,7 +2,8 @@ node_modules
*.log
.idea
*.iml
.vscode
.vscode/*
!.vscode/launch.json
*.sw*
npm-shrinkwrap.json
dist

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
.yarn

38
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,38 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [{
"type": "node",
"request": "launch",
"name": "official-storybook",
"runtimeExecutable": "npm",
"cwd": "${workspaceFolder}/examples/official-storybook",
"runtimeArgs": [
"run-script",
"debug"
],
"port": 9229,
"skipFiles": [
"<node_internals>/**"
]
}, {
"type": "node",
"request": "launch",
"name": "cli html",
"cwd": "${workspaceFolder}/lib/cli/stories",
"runtimeArgs": [
"--inspect-brk",
"${workspaceFolder}/lib/cli/bin/index.js",
"init",
"--type",
"html"
],
"port": 9229,
"skipFiles": [
"<node_internals>/**"
]
}
]
}

View File

@ -1,25 +1,25 @@
## Addon / Framework Support Table
| | [React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| [Svelte](app/svelte)| [Riot](app/riot)| [Ember](app/ember)| [Preact](app/preact)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[a11y](addons/a11y) |+| |+|+|+|+|+|+|+|+|+|+|
|[actions](addons/actions) |+|+*|+|+|+|+|+|+|+|+|+|+|
|[backgrounds](addons/backgrounds) |+|*|+|+|+|+|+|+|+|+|+|+|
|[centered](addons/centered) |+| |+|+| |+|+| |+| |+|+|
|[contexts](addons/contexts) |+| |+| | | | | | | | |+|
|[events](addons/events) |+| |+|+|+|+|+|+| | |+|+|
|[design assets](addons/design-assets) |+| |+|+|+|+|+|+|+|+|+|+|
|[graphql](addons/graphql) |+| | | | | | | | | | | |
|[google-analytics](addons/google-analytics) |+|+|+|+|+|+|+|+|+|+|+|+|
|[info](addons/info) |+| | | | | | | | | | | |
|[jest](addons/jest) |+| | |+| | |+| | | | | |
|[knobs](addons/knobs) |+|+*|+|+|+|+|+|+|+|+|+|+|
|[links](addons/links) |+|+|+|+|+|+|+| |+|+|+|+|
|[notes](addons/notes) |+|+*|+|+|+|+|+| |+|+|+|+|
|[options](addons/options) |+|+|+|+|+|+|+| |+|+|+|+|
|[cssresources](addons/cssresources) |+| |+|+|+|+|+|+|+|+|+|+|
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |+|+| |+|
|[storysource](addons/storysource) |+| |+|+|+|+|+|+|+|+|+|+|
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|+|+|+|+|
| | [React](app/react) | [React Native](app/react-native) | [Vue](app/vue) | [Angular](app/angular) | [Polymer](app/polymer) | [Mithril](app/mithril) | [HTML](app/html) | [Marko](app/marko) | [Svelte](app/svelte) | [Riot](app/riot) | [Ember](app/ember) | [Preact](app/preact) | [Rax](app/rax) |
| ------------------------------------------- | :----------------: | :------------------------------: | :------------: | :--------------------: | :--------------------: | :--------------------: | :--------------: | :----------------: | :------------------: | :--------------: | :----------------: | :------------------: | -------------- |
| [a11y](addons/a11y) | + | | + | + | + | + | + | + | + | + | + | + | + |
| [actions](addons/actions) | + | +\* | + | + | + | + | + | + | + | + | + | + | + |
| [backgrounds](addons/backgrounds) | + | \* | + | + | + | + | + | + | + | + | + | + | + |
| [centered](addons/centered) | + | | + | + | | + | + | | + | | + | + | + |
| [contexts](addons/contexts) | + | | + | | | | | | | | | + | + |
| [events](addons/events) | + | | + | + | + | + | + | + | | | + | + | + |
| [design assets](addons/design-assets) | + | | + | + | + | + | + | + | + | + | + | + | + |
| [graphql](addons/graphql) | + | | | | | | | | | | | | |
| [google-analytics](addons/google-analytics) | + | + | + | + | + | + | + | + | + | + | + | + | + |
| [info](addons/info) | + | | | | | | | | | | | | |
| [jest](addons/jest) | + | + | + | + | + | + | + | + | + | + | + | + | + |
| [knobs](addons/knobs) | + | +\* | + | + | + | + | + | + | + | + | + | + | + |
| [links](addons/links) | + | + | + | + | + | + | + | | + | + | + | + | + |
| [notes](addons/notes) | + | +\* | + | + | + | + | + | | + | + | + | + | + |
| [options](addons/options) | + | + | + | + | + | + | + | | + | + | + | + | + |
| [cssresources](addons/cssresources) | + | | + | + | + | + | + | + | + | + | + | + | + |
| [storyshots](addons/storyshots) | + | + | + | + | | | + | | + | + | | + | + |
| [storysource](addons/storysource) | + | | + | + | + | + | + | + | + | + | + | + | + |
| [viewport](addons/viewport) | + | | + | + | + | + | + | + | + | + | + | + | + |
`*` - React Native on device addon (addons/onDevice-\<name>)
`*` - React Native on device addon (addons/onDevice-\<name>)

View File

@ -1,3 +1,563 @@
## 6.0.0-alpha.0 (January 21, 2020)
### Features
* Addon-a11y: Support manual run ([#8883](https://github.com/storybookjs/storybook/pull/8883))
* Addon-cssresources: Disable SyntaxHighlighter for long code ([#9360](https://github.com/storybookjs/storybook/pull/9360))
* Core: Improve monorepo support ([#8822](https://github.com/storybookjs/storybook/pull/8822))
### Bug Fixes
* Fixed Angular button example story ([#9540](https://github.com/storybookjs/storybook/pull/9540))
* Core: Fix generated entry to import at top of file ([#9398](https://github.com/storybookjs/storybook/pull/9398))
* Preact: Fix story function typescript type ([#9123](https://github.com/storybookjs/storybook/pull/9123))
* UI: Make canvas link a link ([#9257](https://github.com/storybookjs/storybook/pull/9257))
### Maintenance
* Typescript: Migrate ember ([#9020](https://github.com/storybookjs/storybook/pull/9020))
* Next 6.0.0 ([#9212](https://github.com/storybookjs/storybook/pull/9212))
* Lock yarn version ([#9138](https://github.com/storybookjs/storybook/pull/9138))
* REMOVE subscription_store ([#9228](https://github.com/storybookjs/storybook/pull/9228))
### Dependency Upgrades
* Update husky to v4 ([#9509](https://github.com/storybookjs/storybook/pull/9509))
## 5.3.7 (January 20, 2020)
### Bug Fixes
* Node-logger: Move `@types/npmlog` to dependencies ([#9538](https://github.com/storybookjs/storybook/pull/9538))
* Core: Fix legacy story URLs ([#9545](https://github.com/storybookjs/storybook/pull/9545))
* Addon-docs: Convert default prop value to string ([#9525](https://github.com/storybookjs/storybook/pull/9525))
* Addon-docs: Preserve Source indentation by default ([#9513](https://github.com/storybookjs/storybook/pull/9513))
## 5.3.6 (January 17, 2020)
### Bug Fixes
* Source-loader: Bypass if file has no exports ([#9505](https://github.com/storybookjs/storybook/pull/9505))
* Core: Fix default sorting of docs-only stories ([#9504](https://github.com/storybookjs/storybook/pull/9504))
## 5.3.5 (January 17, 2020)
### Bug Fixes
* Core: Fix typo for loading addon-notes/register-panel ([#9497](https://github.com/storybookjs/storybook/pull/9497))
* Source-loader: Add imports to top of file ([#9492](https://github.com/storybookjs/storybook/pull/9492))
## 5.3.4 (January 16, 2020)
### Bug Fixes
* Core: Fix presets register panel ([#9486](https://github.com/storybookjs/storybook/pull/9486))
* Core: Fix addon/preset detection for local addons ([#9485](https://github.com/storybookjs/storybook/pull/9485))
* Core: Fix default story sort ([#9482](https://github.com/storybookjs/storybook/pull/9482))
## 5.3.3 (January 14, 2020)
### Bug Fixes
* UI: Fix edge case where only one legacy separator is defined ([#9425](https://github.com/storybookjs/storybook/pull/9425))
* Core: Preserve kind load order on HMR when no sortFn is provided ([#9424](https://github.com/storybookjs/storybook/pull/9424))
* Angular: Fix missing architect properties ([#9390](https://github.com/storybookjs/storybook/pull/9390))
* Addon-knobs: Fix null knob values in select ([#9416](https://github.com/storybookjs/storybook/pull/9416))
* Source-loader: Disable linting altogether ([#9417](https://github.com/storybookjs/storybook/pull/9417))
## 5.3.2 (January 13, 2020)
### Bug Fixes
* Source-loader: Disable eslint entirely for generated code ([#9410](https://github.com/storybookjs/storybook/pull/9410))
## 5.3.1 (January 12, 2020)
### Bug Fixes
* Core: Fix generated entry to import at top of file ([#9398](https://github.com/storybookjs/storybook/pull/9398))
## 5.3.0 (January 11, 2020)
Storybook 5.3 is here!
- 📝 [Custom documentation in MDX](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc)
- 🎨 [Multi-framework SB Docs (React, Vue, Angular, WC, Ember)](https://medium.com/storybookjs/storybook-docs-for-new-frameworks-b1f6090ee0ea)
- 📦 [Web-components framework support](https://dev.to/open-wc/storybook-for-web-components-on-steroids-4h29)
- 🔼 [Main.js declarative configuration](https://medium.com/storybookjs/declarative-storybook-configuration-49912f77b78)
5.3 contains hundreds more fixes, features, and tweaks. Browse the changelogs matching `5.3.0-alpha.*`, `5.3.0-beta.*`, and `5.3.0-rc.*` for the full list of changes. See [MIGRATION.md](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) to upgrade from `5.0` or earlier.
## 5.3.0-rc.14 (January 11, 2020)
* Merge `master` into `next` for 5.3.0 release ([#9388](https://github.com/storybookjs/storybook/pull/9388))
## 5.3.0-rc.13 (January 11, 2020)
### Bug Fixes
* Addon-docs: Fix link CORS errors using channel navigate event ([#9381](https://github.com/storybookjs/storybook/pull/9381))
* CLI: Fix `sb init` to use spawn.sync if creating package.json ([#9359](https://github.com/storybookjs/storybook/pull/9359))
### Maintenance
* Official-storybook: Prop table example for multiple named exports ([#9364](https://github.com/storybookjs/storybook/pull/9364))
* Addon-docs / web-components: Rename 'props' to 'properties' in props table ([#9362](https://github.com/storybookjs/storybook/pull/9362))
### Dependency Upgrades
* Upgrade @types/webpack-env and @types/node to fix conflicting types ([#9365](https://github.com/storybookjs/storybook/pull/9365))
## 5.3.0-rc.12 (January 8, 2020)
### Bug Fixes
* Nav UI: Nodes are components only if they contain ALL leaf nodes ([#9356](https://github.com/storybookjs/storybook/pull/9356))
* Core: Fix HMR for global decorators in main.js config ([#9354](https://github.com/storybookjs/storybook/pull/9354))
* Presets: Fix register.js addons entry ([#9347](https://github.com/storybookjs/storybook/pull/9347))
* React: Check CRA is installed before showing warning ([#9346](https://github.com/storybookjs/storybook/pull/9346))
## 5.3.0-rc.11 (January 7, 2020)
### Bug Fixes
* Addon-Docs: Handle leaf/non-leaf mixture in docs-mode navigation ([#9321](https://github.com/storybookjs/storybook/pull/9321))
### Dependency Upgrades
* Axe storyshots: move to original @wordpress/jest-puppeteer-axe package ([#9337](https://github.com/storybookjs/storybook/pull/9337))
## 5.3.0-rc.10 (January 6, 2020)
### Bug Fixes
* Revert "Source-loader: Disable no-implicit-any linting" ([#9333](https://github.com/storybookjs/storybook/pull/9333))
* Addon-docs: Fix scroll behavior on page navigation ([#9331](https://github.com/storybookjs/storybook/pull/9331))
## 5.3.0-rc.9 (January 4, 2020)
### Features
* CSF: Use `__namedExportsOrder` array in loader if provided ([#9315](https://github.com/storybookjs/storybook/pull/9315))
### Bug Fixes
* Router: Add storyNameFromExport to avoid breaking changes ([#9320](https://github.com/storybookjs/storybook/pull/9320))
## 5.3.0-rc.8 (January 3, 2020)
### Bug Fixes
* Addon-docs: Tweak props table paragraph spacing ([#9307](https://github.com/storybookjs/storybook/pull/9307))
### Maintenance
* Add minimal typescript component to official-storybook ([#9308](https://github.com/storybookjs/storybook/pull/9308))
### Dependency Upgrades
* React: Upgrade babel-plugin-react-docgen to 4.0.0 ([#9303](https://github.com/storybookjs/storybook/pull/9303))
## 5.3.0-rc.7 (January 2, 2020)
### Bug Fixes
* Core: Fix babel.js to disable simplify ([#9280](https://github.com/storybookjs/storybook/pull/9280))
* Storyshots-Puppeteer: Don't infer story ID from its name ([#9291](https://github.com/storybookjs/storybook/pull/9291))
## 5.3.0-rc.6 (December 31, 2019)
This is significant change to `main.js` aka tri-config, dramatically simplifying how addons and presets are registered. See the maintenannce PR for details.
### Maintenance
* Main.js: Combine presets/registers in `addons` field ([#9246](https://github.com/storybookjs/storybook/pull/9246))
## 5.3.0-rc.5 (December 31, 2019)
### Bug Fixes
* Addon-docs: Hide stories block when there are no stories ([#9271](https://github.com/storybookjs/storybook/pull/9271))
* Source-loader: Disable no-implicit-any linting ([#9272](https://github.com/storybookjs/storybook/pull/9272))
## 5.3.0-rc.4 (December 28, 2019)
### Bug Fixes
* Addon-docs: Fix MDX story rendering with dynamic component titles ([#9248](https://github.com/storybookjs/storybook/pull/9248))
### Maintenance
* Ignore testfixtures directory in storybook publish ([#9244](https://github.com/storybookjs/storybook/pull/9244))
## 5.3.0-rc.3 (December 26, 2019)
### Bug Fixes
* Addon-docs: Include ember files in addon-docs publish ([#9230](https://github.com/storybookjs/storybook/pull/9230))
### Maintenance
* Standalone CSF example ([#9223](https://github.com/storybookjs/storybook/pull/9223))
### Dependency Upgrades
* Addon-info: Upgrade marksy for security ([#9234](https://github.com/storybookjs/storybook/pull/9234))
## 5.3.0-rc.2 (December 26, 2019)
Failed NPM publish
## 5.3.0-rc.1 (December 23, 2019)
### Bug Fixes
* Angular: Add default value to the budgets property ([#9207](https://github.com/storybookjs/storybook/pull/9207))
* DocsPage: Fix title with new path separator scheme ([#9204](https://github.com/storybookjs/storybook/pull/9204))
### Maintenance
* CLI: Make template `stories` glob more permissive ([#9224](https://github.com/storybookjs/storybook/pull/9224))
## 5.3.0-rc.0 (December 19, 2019)
### Features
* CSF: Use `__orderedExports` in loader if provided ([#9181](https://github.com/storybookjs/storybook/pull/9181))
### Bug Fixes
* Addon-a11y: Fix selected blindness color filter ([#9179](https://github.com/storybookjs/storybook/pull/9179))
### Maintenance
* Addon-essentials: Remove actions, links, knobs ([#9184](https://github.com/storybookjs/storybook/pull/9184))
## 5.3.0-beta.31 (December 16, 2019)
### Features
* React: Add support for CRA without overrides ([#9157](https://github.com/storybookjs/storybook/pull/9157))
* Addon-docs: Add fontFamily prop to Typeset component ([#9158](https://github.com/storybookjs/storybook/pull/9158))
### Bug Fixes
* Core: Emit store render event synchronously if we can ([#9087](https://github.com/storybookjs/storybook/pull/9087))
## 5.3.0-beta.30 (December 16, 2019)
Failed NPM publish
## 5.3.0-beta.29 (December 16, 2019)
Failed NPM publish
## 5.3.0-beta.28 (December 16, 2019)
Failed NPM publish
## 5.3.0-beta.27 (December 16, 2019)
Failed NPM publish
## 5.3.0-beta.26 (December 16, 2019)
Failed NPM publish
## 5.3.0-beta.25 (December 15, 2019)
### Bug Fixes
* Addon-docs: Hide addons on docs-only stories ([#9125](https://github.com/storybookjs/storybook/pull/9125))
### Dependency Upgrades
* Upgrade vue-docgen-loader to 1.3.0-beta.0 ([#9155](https://github.com/storybookjs/storybook/pull/9155))
## 5.3.0-beta.24 (December 15, 2019)
Failed NPM publish
## 5.3.0-beta.23 (December 14, 2019)
### Features
* Addon-docs: Render components as leaves in `--docs` mode ([#7700](https://github.com/storybookjs/storybook/pull/7700))
### Bug Fixes
* Addon-viewport: Allow viewports config to be optional ([#9137](https://github.com/storybookjs/storybook/pull/9137))
## 5.3.0-beta.22 (December 12, 2019)
### Bug Fixes
* React: Fix CRA preset check ([#9142](https://github.com/storybookjs/storybook/pull/9142))
### Maintenance
* Build: Change CI to chromatic on all examples ([#9114](https://github.com/storybookjs/storybook/pull/9114))
* Web-components: Clean up example `custom-elements.json` and expose `defaultValue` ([#9107](https://github.com/storybookjs/storybook/pull/9107))
### Dependency Upgrades
* Restore main jscodeshift package ([#9140](https://github.com/storybookjs/storybook/pull/9140))
## 5.3.0-beta.21 (December 11, 2019)
### Features
* CLI: Add Yarn workspaces support for init command ([#9104](https://github.com/storybookjs/storybook/pull/9104))
### Bug Fixes
* Addon-docs: Update MDX compiler to fix knobs ([#9118](https://github.com/storybookjs/storybook/pull/9118))
* CLI: Add web-components to sb init ([#9106](https://github.com/storybookjs/storybook/pull/9106))
### Maintenance
* UI: Remove css usage ([#9003](https://github.com/storybookjs/storybook/pull/9003))
## 5.3.0-beta.20 (December 9, 2019)
### Features
* Addon-essentials: Remove docs from essentials ([#9093](https://github.com/storybookjs/storybook/pull/9093))
### Bug Fixes
* Source-loader: Handle includeStories/excludeStories in CSF ([#9100](https://github.com/storybookjs/storybook/pull/9100))
* Source-loader: Support function declaration story exports ([#9092](https://github.com/storybookjs/storybook/pull/9092))
### Maintenance
* CSF: Refactor router utils into CSF library ([#9099](https://github.com/storybookjs/storybook/pull/9099))
## 5.3.0-beta.19 (December 7, 2019)
### Features
* Addon-essentials ([#9019](https://github.com/storybookjs/storybook/pull/9019))
### Bug Fixes
* Addon-docs: Fix prop table default value for web-components ([#9086](https://github.com/storybookjs/storybook/pull/9086))
## 5.3.0-beta.18 (December 6, 2019)
### Features
* CLI: Change generators to Triconfig ([#9075](https://github.com/storybookjs/storybook/pull/9075))
* Addon-docs: Add Props for Ember ([#9067](https://github.com/storybookjs/storybook/pull/9067))
* MDX: Handle quotes / template literals in title ([#9069](https://github.com/storybookjs/storybook/pull/9069))
### Bug Fixes
* Addon-docs: MDX Octicon anchors should not be tabbable ([#9063](https://github.com/storybookjs/storybook/pull/9063))
### Dependency Upgrades
* Addon-docs: Upgrade vue-docgen-loader ([#9082](https://github.com/storybookjs/storybook/pull/9082))
## 5.3.0-beta.17 (December 6, 2019)
NPM publish failed
## 5.3.0-beta.16 (December 5, 2019)
### Features
* Addon-docs: DocsPage Heading and Subheading anchor links ([#9060](https://github.com/storybookjs/storybook/pull/9060))
### Bug Fixes
* Core: Fix `api.selectStory` for component permalinks ([#9054](https://github.com/storybookjs/storybook/pull/9054))
* Storyshots: Escape Windows fileNames ([#9061](https://github.com/storybookjs/storybook/pull/9061))
### Dependency Upgrades
* Addon-docs: Upgrade vue-docgen-api ([#9066](https://github.com/storybookjs/storybook/pull/9066))
## 5.3.0-beta.15 (December 4, 2019)
### Features
* Addon-docs: MDX Linking ([#9051](https://github.com/storybookjs/storybook/pull/9051))
## 5.2.8 (December 2, 2019)
### Bug Fixes
* UI: Fix layout of Preview container ([#8628](https://github.com/storybookjs/storybook/pull/8628))
* Core: Use `stable` package to ensure story sorting is stable ([#8795](https://github.com/storybookjs/storybook/pull/8795))
* Source-loader: Warn if applied to non-stories file ([#8773](https://github.com/storybookjs/storybook/pull/8773))
## 5.3.0-beta.14 (December 2, 2019)
### Features
* Addon-docs: Increase Props summary and func length ([#8998](https://github.com/storybookjs/storybook/pull/8998))
### Bug Fixes
* Addon-docs: Restore IE11 compat by transpiling acorn-jsx ([#9021](https://github.com/storybookjs/storybook/pull/9021))
* Source-loader: Handle template strings in CSF title ([#8995](https://github.com/storybookjs/storybook/pull/8995))
* CLI: Fix various storiesof-to-csf cases based on chromatic stories upgrade ([#9013](https://github.com/storybookjs/storybook/pull/9013))
## 5.2.7 (November 30, 2019)
### Bug Fixes
* Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001))
* Addon-viewports: Fix missing TypeScript types ([#8848](https://github.com/storybookjs/storybook/pull/8848))
* Addon-A11y: Show errors, reset config properly ([#8779](https://github.com/storybookjs/storybook/pull/8779))
* UI: Store layout state in sessionStorage ([#8786](https://github.com/storybookjs/storybook/pull/8786))
* UI: Fix MobileLayout component error on master ([#8941](https://github.com/storybookjs/storybook/pull/8941))
* Addon-analytics: Fix 'path is required in .pageview()' ([#8468](https://github.com/storybookjs/storybook/pull/8468))
## 5.3.0-beta.13 (November 30, 2019)
### Bug Fixes
* Addon-contexts: Fix 'cannot read property h of undefined' in preact ([#9001](https://github.com/storybookjs/storybook/pull/9001))
### Maintenance
* CLI: Code cleanup ([#9004](https://github.com/storybookjs/storybook/pull/9004))
## 5.3.0-beta.12 (November 29, 2019)
### Features
* Storyshots: Support a11y tests, generic tests ([#8934](https://github.com/storybookjs/storybook/pull/8934))
### Maintenance
* Dev: Add vscode launch.json for debugging ([#8993](https://github.com/storybookjs/storybook/pull/8993))
* UI: viewMode proptypes changed to any string ([#8994](https://github.com/storybookjs/storybook/pull/8994))
* Addon-docs: Remove deprecated framework-specific docs presets ([#8985](https://github.com/storybookjs/storybook/pull/8985))
### Dependency Upgrades
* Addon-docs: Upgrade MDX dependencies ([#8991](https://github.com/storybookjs/storybook/pull/8991))
## 5.3.0-beta.11 (November 28, 2019)
### Features
* UI: Escape hatch CSS on for "active" tablist buttons ([#8989](https://github.com/storybookjs/storybook/pull/8989))
* Addon-docs: Added dark theme option to source component ([#8732](https://github.com/storybookjs/storybook/pull/8732))
* Triconfig: Configure UI options overhaul ([#8871](https://github.com/storybookjs/storybook/pull/8871))
### Bug Fixes
* Addon-docs: Fix vertical alignment of props expandable ([#8953](https://github.com/storybookjs/storybook/pull/8953))
* Addon-links: Fix return type of linkTo and examples ([#8975](https://github.com/storybookjs/storybook/pull/8975))
## 5.3.0-beta.10 (November 27, 2019)
### Features
* MDX: Allow user to override `docs.container` parameter ([#8968](https://github.com/storybookjs/storybook/pull/8968))
* Addon-docs: Increase docs content wrapper max-width to 1000px ([#8970](https://github.com/storybookjs/storybook/pull/8970))
* Addon-docs: Prop table support for Angular directives ([#8922](https://github.com/storybookjs/storybook/pull/8922))
* Addon-docs: Increase width of props table type column ([#8950](https://github.com/storybookjs/storybook/pull/8950))
### Bug Fixes
* Addon-docs: Fix `Preview` theming escape hatch ([#8969](https://github.com/storybookjs/storybook/pull/8969))
* Core: Don't try to require .ts files from dist ([#8971](https://github.com/storybookjs/storybook/pull/8971))
* Core: Use logger in base-webpack.config.js ([#8966](https://github.com/storybookjs/storybook/pull/8966))
### Maintenance
* Examples: Add "debug" script for storybook-official ([#8973](https://github.com/storybookjs/storybook/pull/8973))
* Build: Upgrade to node 10 on netlify ([#8967](https://github.com/storybookjs/storybook/pull/8967))
* Core/triconfig everywhere: migrate examples ([#8942](https://github.com/storybookjs/storybook/pull/8942))
## 5.3.0-beta.9 (November 26, 2019)
### Features
* Storyshots: Remove abandoned storyshots when run with `-u` flag ([#8889](https://github.com/storybookjs/storybook/pull/8889))
### Bug Fixes
* Addon-docs: Support subcomponents as a top-level default export ([#8931](https://github.com/storybookjs/storybook/pull/8931))
### Dependency Upgrades
* Core: Add missing dependencies ([#8945](https://github.com/storybookjs/storybook/pull/8945))
## 5.3.0-beta.8 (November 26, 2019)
### Features
* Storyshots-puppeteer: Add afterScreenshot handler ([#8092](https://github.com/storybookjs/storybook/pull/8092))
### Bug Fixes
* Core: Upgrade telejson to fix cross-origin frame error ([#8940](https://github.com/storybookjs/storybook/pull/8940))
### Maintenance
* Build: Fix image snapshots setup in official-storybook ([#8932](https://github.com/storybookjs/storybook/pull/8932))
### Dependency Upgrades
* Core: Add @babel/core peer dependency to @storybook/core ([#8933](https://github.com/storybookjs/storybook/pull/8933))
## 5.3.0-beta.7 (November 26, 2019)
Failed npm publish
## 5.3.0-beta.6 (November 24, 2019)
### Features
* Presets: dynamic preset injection ([#8921](https://github.com/storybookjs/storybook/pull/8921))
### Bug Fixes
* Revert "feat: use `puppeteer-core` instead of `puppeteer`" ([#8925](https://github.com/storybookjs/storybook/pull/8925))
* Addon-docs: Fix props detail tooltip to prevent cutting end of content ([#8923](https://github.com/storybookjs/storybook/pull/8923))
### Maintenance
* Addon-docs: Base code to improve the props table for TS ([#8905](https://github.com/storybookjs/storybook/pull/8905))
* Build: Fix now deploy ([#8929](https://github.com/storybookjs/storybook/pull/8929))
### Dependency Upgrades
* Miscellaneous upgrades ([#8912](https://github.com/storybookjs/storybook/pull/8912))
## 5.3.0-beta.5 (November 23, 2019)
Failed npm publish
## 5.3.0-beta.4 (November 23, 2019)
Failed npm publish
## 5.3.0-beta.3 (November 21, 2019)
### Features
* Addon-docs: Rich props table UI ([#8887](https://github.com/storybookjs/storybook/pull/8887))
* Addon-docs: Improve basic support for Flow props ([#8890](https://github.com/storybookjs/storybook/pull/8890))
* CLI: Avoid id changes after `storiesof-to-csf` migration ([#8856](https://github.com/storybookjs/storybook/pull/8856))
### Bug Fixes
* Addon-docs: Fix props table for sections props ([#8904](https://github.com/storybookjs/storybook/pull/8904))
* Addon-docs: Fix Description block when no component provided ([#8902](https://github.com/storybookjs/storybook/pull/8902))
* Angular: Fix project without `architect.build` option ([#6737](https://github.com/storybookjs/storybook/pull/6737))
### Maintenance
* Addon-docs: Docgen lib maintenance ([#8896](https://github.com/storybookjs/storybook/pull/8896))
* Examples: Fix stories glob in official-storybook ([#8888](https://github.com/storybookjs/storybook/pull/8888))
## 5.3.0-beta.2 (November 19, 2019)
### Features

View File

@ -4,7 +4,7 @@ Thanks for your interest in improving Storybook! We are a community-driven proje
Please review this document to help to streamline the process and save everyone's precious time.
This repo uses yarn workspaces, so you should install `yarn@1.3.2` or higher as a package manager. See [installation guide](https://yarnpkg.com/en/docs/install).
This repo uses yarn workspaces, so you should install `yarn` as the package manager. See [installation guide](https://yarnpkg.com/en/docs/install).
## Issues
@ -187,7 +187,7 @@ If you've made a change to storybook's codebase and would want this change to be
### Updating Tests
Before any contributes are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
Before any contributions are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
When creating new unit test files, the tests should adhere to a particular folder structure and naming convention, as defined below.
```sh
@ -205,7 +205,7 @@ Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if
### Reviewing PRs
**As a PR submitter**, you should reference the issue if there is one, include a short description of what you contributed and, if it is a code change, instructions for how to manually test out the change. This is informally enforced by our [PR template](https://github.com/storybookjs/storybook/blob/master/.github/PULL_REQUEST_TEMPLATE.md). If your PR is reviewed as only needing trivial changes (e.g. small typos etc), and you have commit access, then you can merge the PR after making those changes.
**As a PR submitter**, you should reference the issue if there is one, include a short description of what you contributed and, if it is a code change, instructions for how to manually test out the change. This is informally enforced by our [PR template](https://github.com/storybookjs/storybook/blob/master/.github/PULL_REQUEST_TEMPLATE.md). If your PR is reviewed as only needing trivial changes (e.g. small typos etc), and you have commit access then you can merge the PR after making those changes.
**As a PR reviewer**, you should read through the changes and comment on any potential problems. If you see something cool, a kind word never hurts either! Additionally, you should follow the testing instructions and manually test the changes. If the instructions are missing, unclear, or overly complex, feel free to request better instructions from the submitter. Unless the PR is tagged with the `do not merge` label, if you approve the review and there is no other required discussion or changes, you should also go ahead and merge the PR.
@ -215,7 +215,7 @@ If you are looking for a way to help the project, triaging issues is a great pla
### Responding to issues
Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions).
Issues that are tagged `question / support` or `needs reproduction` are great places to help. If you can answer a question, it will help the asker as well as anyone who has a similar question. Also in the future if anyone has that same question they can easily find it by searching. If an issue needs reproduction, you may be able to guide the reporter toward one, or even reproduce it yourself using [this technique](https://github.com/storybookjs/storybook/blob/master/CONTRIBUTING.md#reproductions).
### Triaging issues
@ -273,7 +273,7 @@ If you run into trouble here, make sure your node, npm, and **_yarn_** are on th
_This method is slow_
1. `yarn bootstrap --all`
2. Have a beer 🍺
2. Take a break 🍵
3. `yarn test` (to verify everything worked)
### Working with the kitchen sink apps
@ -283,9 +283,10 @@ Within the `examples` folder of the Storybook repo, you will find kitchen sink e
Not only do these show many of the options and add-ons available, they are also automatically linked to all the development packages. We highly encourage you to use these to develop/test contributions on.
#### React and Vue
1. `cd examples/official-storybook`
2. `yarn storybook`
3. Verify that your local version works
2. `yarn storybook`
3. Verify that your local version works
### Working with your own app

View File

@ -1,25 +0,0 @@
## v.Next
- Deprecated `{ linkTo, action }` as built-in addons: <https://github.com/storybookjs/storybook/issues/1017>. From 3.0 use them as you would [any other addon](https://storybook.js.org/addons/using-addons/).
Before:
```js
// .storybook/addons.js
import '@kadira/storybook/addons'
// *.stories.js
import { linkTo, action } from '@kadira/storybook'
```
After:
```js
// .storybook/addons.js
import '@storybook/addon-actions/register'
import '@storybook/addon-links/register'
// *.stories.js
import { action } from '@storybook/addon-actions'
import { linkTo } from '@storybook/addon-links'
```

View File

@ -2,12 +2,14 @@
- [Migration](#migration)
- [From version 5.2.x to 5.3.x](#from-version-52x-to-53x)
- [To main.js configuration](#to-mainjs-configuration)
- [Create React App preset](#create-react-app-preset)
- [Description doc block](#description-doc-block)
- [React Native Async Storage](#react-native-async-storage)
- [Deprecate displayName parameter](#deprecate-displayname-parameter)
- [Unified docs preset](#unified-docs-preset)
- [Simplified hierarchy separators](#simplified-hierarchy-separators)
- [Addon StoryShots Puppeteer uses external puppeteer](#addon-storyshots-puppeteer-uses-external-puppeteer)
- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x)
- [Source-loader](#source-loader)
- [Default viewports](#default-viewports)
@ -76,6 +78,80 @@
## From version 5.2.x to 5.3.x
### To main.js configuration
In storybook 5.3 3 new files for configuration were introduced, that replaced some previous files.
These files are now soft-deprecated, (_they still work, but over time we will promote users to migrate_):
- `presets.js` has been renamed to `main.js`. `main.js` is the main point of configuration for storybook.
- `config.js` has been renamed to `preview.js`. `preview.js` configures the "preview" iframe that renders your components.
- `addons.js` has been renamed to `manager.js`. `manager.js` configures Storybook's "manager" UI that wraps the preview, and also configures addons panel.
#### Using main.js
`main.js` is now the main point of configuration for Storybook. This is what a basic `main.js` looks like:
```js
module.exports = {
stories: ['../**/*.stories.js'],
addons: ['@storybook/addon-knobs'],
};
```
You remove all "register" import from `addons.js` and place them inside the array. You can also safely remove the `/register` suffix from these entries, for a cleaner, more readable configuration. If this means `addons.js` is now empty for you, it's safe to remove.
Next you remove the code that imports/requires all your stories from `config.js`, and change it to a glob-pattern and place that glob in the `stories` array. If this means `config.js` is empty, it's safe to remove.
If you had a `presets.js` file before you can add the array of presets to the main.js file and remove `presets.js` like so:
```js
module.exports = {
stories: ['../**/*.stories.js'],
addons: [
'@storybook/preset-create-react-app'
{
name: '@storybook/addon-docs',
options: { configureJSX: true }
}
],
};
```
By default, adding a package to the `addons` array will first try to load its `preset` entry, then its `register` entry, and finally, it will just assume the package itself is a preset.
If you want to load a specific package entry, for example you want to use `@storybook/addon-docs/register`, you can also include that in the addons array and Storybook will do the right thing.
#### Using preview.js
If after migrating the imports/requires of your stories to `main.js` you're left with some code in `config.js` it's likely the usage of `addParameters` & `addDecorator`.
This is fine, rename `config.js` to `preview.js`.
This file can also be used to inject global stylesheets, fonts etc, into the preview bundle.
#### Using manager.js
If you are setting storybook options in `config.js`, especially `theme`, you should migrate it to `manager.js`:
```js
import { addons } from '@storybook/addons';
import { create } from '@storybook/theming/create';
const theme = create({
base: 'light',
brandTitle: 'My custom title',
});
addons.setConfig({
panelPosition: 'bottom',
theme,
});
```
This makes storybook load and use the theme in the manager directly.
This allows for richer theming in the future, and has a much better performance!
### Create React App preset
You can now move to the new preset for [Create React App](https://create-react-app.dev/). The in-built preset for Create React App will be disabled in Storybook 6.0.
@ -111,6 +187,8 @@ getStorybookUI({
});
```
The benefit of using Async Storage is so that when users refresh the app, Storybook can open their last visited story.
### Deprecate displayName parameter
In 5.2, the story parameter `displayName` was introduced as a publicly visible (but internal) API. Storybook's Component Story Format (CSF) loader used it to modify a story's display name independent of the story's `name`/`id` (which were coupled).
@ -125,15 +203,34 @@ Addon-docs configuration gets simpler in 5.3. In 5.2, each framework had its own
We've deprecated the ability to specify the hierarchy separators (how you control the grouping of story kinds in the sidebar). From Storybook 6.0 we will have a single separator `/`, which cannot be configured.
If you are currently using using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, (we will soon provide) a codemod that can be used to rename all your components.
If you are currently using using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, we provide a codemod, [`upgrade-hierarchy-separators`](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#upgrade-hierarchy-separators), that can be used to rename all your components.
If you were using `|` and wish to keep the "root" behaviour, use the `showRoots: true` option to re-enable roots:
```js
addParameters({ options: { showRoots: true } });
```
yarn sb migrate upgrade-hierarchy-separators --glob="*.stories.js"
```
NOTE: it is no longer possible to have some stories with roots and others without. If you want to keep the old behaviour, simply add a root called "Others" to all your previously unrooted stories.
If you were using `|` and wish to keep the "root" behavior, use the `showRoots: true` option to re-enable roots:
```js
addParameters({
options: {
showRoots: true,
},
});
```
NOTE: it is no longer possible to have some stories with roots and others without. If you want to keep the old behavior, simply add a root called "Others" to all your previously unrooted stories.
### Addon StoryShots Puppeteer uses external puppeteer
To give you more control on the Chrome version used when running StoryShots Puppeteer, `puppeteer` is no more included in the addon dependencies. So you can now pick the version of `puppeteer` you want and set it in your project.
If you want the latest version available just run:
```sh
yarn add puppeteer --dev
OR
npm install puppeteer --save-dev
```
## From version 5.1.x to 5.2.x
@ -183,7 +280,7 @@ For example, here's how to sort by story ID using `storySort`:
addParameters({
options: {
storySort: (a, b) =>
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, { numeric: true }),
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }),
},
});
```

View File

@ -46,7 +46,7 @@ Storybook is a development environment for UI components.
It allows you to browse a component library, view the different states of each component, and interactively develop and test components.
<center>
<img src="media/storybook-intro.gif" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/media/storybook-intro.gif" width="100%" />
</center>
<p align="center">
@ -91,7 +91,7 @@ If you'd rather set up your project manually, take a look at our [Slow Start Gui
Once it's installed, you can `npm run storybook` and it will run the development server on your local machine, and give you a URL to browse some sample stories.
**Storybook v2.x migration note**:
**Storybook v2.x migration note**:
If you're using Storybook v2.x and want to shift to 4.x version the easiest way is:
```sh
@ -116,6 +116,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
| [Vue](app/vue) | [v5.1.0](https://storybooks-vue.netlify.com/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) |
| [Angular](app/angular) | [v5.1.0](https://storybooks-angular.netlify.com/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) |
| [Polymer](app/polymer) | [v5.1.0](https://storybooks-polymer.netlify.com/) | [![Polymer](https://img.shields.io/npm/dm/@storybook/polymer.svg)](app/polymer) |
| [Marionette.js](app/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) |
| [Mithril](app/mithril) | [v5.1.0](https://storybooks-mithril.netlify.com/) | [![Mithril](https://img.shields.io/npm/dm/@storybook/mithril.svg)](app/mithril) |
| [Marko](app/marko) | [v5.1.0](https://storybooks-marko.netlify.com/) | [![Marko](https://img.shields.io/npm/dm/@storybook/marko.svg)](app/marko) |
| [HTML](app/html) | [v5.1.0](https://storybooks-html.netlify.com/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) |
@ -123,6 +124,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
| [Riot](app/riot) | [v5.1.0](https://storybooks-riot.netlify.com/) | [![Riot](https://img.shields.io/npm/dm/@storybook/riot.svg)](app/riot) |
| [Ember](app/ember) | [v5.1.0](https://storybooks-ember.netlify.com/) | [![Ember](https://img.shields.io/npm/dm/@storybook/ember.svg)](app/ember) |
| [Preact](app/preact) | [v5.1.0](https://storybooks-preact.netlify.com/) | [![Preact](https://img.shields.io/npm/dm/@storybook/preact.svg)](app/preact) |
| [Rax](app/rax) | [v5.1.0](https://storybooks-rax.netlify.com/) | [![Rax](https://img.shields.io/npm/dm/@storybook/rax.svg)](app/rax) |
### Sub Projects

View File

@ -3,7 +3,9 @@ import { action } from "@storybook/addon-actions";
import { Button } from "@storybook/react/demo";
export default {
title: "Button"
title: "Button",
excludeStories: ["text"],
includeStories: /emoji.*/
};
export const text = () => (
@ -17,3 +19,13 @@ export const emoji = () => (
</span>
</Button>
);
export function emojiFn() {
return (
<Button onClick={action("clicked")}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
)
};

View File

@ -14,37 +14,42 @@ First, install the addon.
$ yarn add @storybook/addon-a11y --dev
```
Add this line to your `addons.js` file (create this file inside your storybook config directory if needed).
Add this line to your `main.js` file (create this file inside your storybook config directory if needed).
```js
import '@storybook/addon-a11y/register';
module.exports = {
addons: ['@storybook/addon-a11y/register']
}
```
import the `withA11y` decorator to check your stories for violations within your components.
```js
import React from 'react';
import { storiesOf, addDecorator } from '@storybook/react';
import { withA11y } from '@storybook/addon-a11y';
// should only be added once
// best place is in config.js
addDecorator(withA11y)
export default {
title: 'button',
decorators: [withA11y],
};
storiesOf('button', module)
.add('Accessible', () => (
<button>
Accessible button
</button>
))
.add('Inaccessible', () => (
<button style={{ backgroundColor: 'red', color: 'darkRed', }}>
Inaccessible button
</button>
));
export const accessible = () => (
<button>
Accessible button
</button>
);
export const inaccessible = () => (
<button style={{ backgroundColor: 'red', color: 'darkRed', }}>
Inaccessible button
</button>
);
```
For more customizability. Use the `addParameters` function to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure).
## Parameters
For more customizability use parameters to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure).
You can override these options [at story level too](https://storybook.js.org/docs/configurations/options-parameter/#per-story-options).
```js
@ -53,26 +58,34 @@ import { storiesOf, addDecorator, addParameters } from '@storybook/react';
import { withA11y } from '@storybook/addon-a11y';
addDecorator(withA11y)
addParameters({
a11y: {
element: '#root', // optional selector which element to inspect
config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)
export default {
title: 'button',
decorators: [withA11y],
parameters: {
a11y: {
// optional selector which element to inspect
element: '#root',
// axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
config: {},
// axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)
options: {},
// optional flag to prevent the automatic check
manual: true,
},
},
});
};
storiesOf('button', module)
.add('Accessible', () => (
<button style={{ backgroundColor: 'black', color: 'white', }}>
Accessible button
</button>
))
.add('Inaccessible', () => (
<button style={{ backgroundColor: 'black', color: 'black', }}>
Inaccessible button
</button>
));
export const accessible = () => (
<button>
Accessible button
</button>
);
export const inaccessible = () => (
<button style={{ backgroundColor: 'red', color: 'darkRed', }}>
Inaccessible button
</button>
);
```
## Roadmap

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "a11y addon for storybook",
"keywords": [
"a11y",
@ -33,12 +33,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/client-logger": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/theming": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/client-logger": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/theming": "5.3.7",
"axe-core": "^3.3.2",
"core-js": "^3.0.1",
"global": "^4.3.2",
@ -51,9 +51,11 @@
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/react-redux": "^7.0.6"
"@types/react-redux": "^7.0.6",
"@types/webpack-env": "^1.15.0"
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -1,19 +1,18 @@
import React from 'react';
import { mount } from 'enzyme';
import { EventEmitter } from 'events';
import { ThemeProvider, themes, convert } from '@storybook/theming';
import { STORY_RENDERED } from '@storybook/core-events';
import { ScrollArea } from '@storybook/components';
import { A11YPanel } from './A11YPanel';
import { EVENTS } from '../constants';
function createApi() {
return {
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
};
const emitter = new EventEmitter();
jest.spyOn(emitter, 'emit');
jest.spyOn(emitter, 'on');
jest.spyOn(emitter, 'off');
return emitter;
}
const axeResult = {
@ -63,7 +62,7 @@ function ThemedA11YPanel(props) {
}
describe('A11YPanel', () => {
it('should register STORY_RENDERED, RESULT and ERROR updater on mount', () => {
it('should register event listener on mount', () => {
// given
const api = createApi();
expect(api.on).not.toHaveBeenCalled();
@ -73,59 +72,75 @@ describe('A11YPanel', () => {
// then
expect(api.on.mock.calls.length).toBe(3);
expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED);
expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT);
expect(api.on.mock.calls[2][0]).toBe(EVENTS.ERROR);
expect(api.on.mock.calls[0][0]).toBe(EVENTS.RESULT);
expect(api.on.mock.calls[1][0]).toBe(EVENTS.ERROR);
expect(api.on.mock.calls[2][0]).toBe(EVENTS.MANUAL);
});
it('should request a run on tab activation', () => {
it('should deregister event listener on unmount', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} />);
expect(api.emit).not.toHaveBeenCalled();
// when
wrapper.setProps({ active: true });
wrapper.update();
// then
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
expect(wrapper.find(ScrollArea).length).toBe(0);
});
it('should deregister STORY_RENDERED, RESULT and ERROR updater on unmount', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} />);
expect(api.off).not.toHaveBeenCalled();
// when
const wrapper = mount(<ThemedA11YPanel api={api} />);
wrapper.unmount();
// then
expect(api.off.mock.calls.length).toBe(3);
expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED);
expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT);
expect(api.off.mock.calls[2][0]).toBe(EVENTS.ERROR);
expect(api.off.mock.calls[0][0]).toBe(EVENTS.RESULT);
expect(api.off.mock.calls[1][0]).toBe(EVENTS.ERROR);
expect(api.off.mock.calls[2][0]).toBe(EVENTS.MANUAL);
});
it('should update run result', () => {
it('should handle "initial" status', () => {
// given
const api = createApi();
// when
const wrapper = mount(<ThemedA11YPanel api={api} active />);
// then
expect(api.emit).not.toHaveBeenCalled();
expect(wrapper.text()).toMatch(/Initializing/);
});
it('should handle "manual" status', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
const onUpdate = api.on.mock.calls.find(([event]) => event === EVENTS.RESULT)[1];
expect(
wrapper
.find('button')
.last()
.text()
.trim()
).toBe('Rerun tests');
// when
onUpdate(axeResult);
api.emit(EVENTS.MANUAL, true);
wrapper.update();
// then
expect(wrapper.text()).toMatch(/Manually run the accessibility scan/);
expect(api.emit).not.toHaveBeenCalledWith(EVENTS.REQUEST);
});
it('should handle "running" status', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
// when
api.emit(EVENTS.MANUAL, false);
wrapper.update();
// then
expect(wrapper.text()).toMatch(/Please wait while the accessibility scan is running/);
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
});
it('should handle "ran" status', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
// when
api.emit(EVENTS.RESULT, axeResult);
wrapper.update();
// then
expect(
@ -135,82 +150,13 @@ describe('A11YPanel', () => {
.text()
.trim()
).toBe('Tests completed');
expect(wrapper.find('Tabs').prop('tabs').length).toBe(3);
expect(wrapper.find('Tabs').prop('tabs')[0].label.props.children).toEqual([1, ' Violations']);
expect(wrapper.find('Tabs').prop('tabs')[1].label.props.children).toEqual([1, ' Passes']);
expect(wrapper.find('Tabs').prop('tabs')[2].label.props.children).toEqual([1, ' Incomplete']);
});
it('should request run', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
expect(
wrapper
.find('button')
.last()
.text()
.trim()
).toBe('Rerun tests');
expect(api.emit).not.toHaveBeenCalled();
// when
request();
// then
expect(
wrapper
.find('button')
.last()
.text()
.trim()
).toBe('Running test');
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
});
it('should NOT request run on inactive tab', () => {
// given
const api = createApi();
mount(<ThemedA11YPanel api={api} active={false} />);
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
expect(api.emit).not.toHaveBeenCalled();
// when
request();
// then
expect(api.emit).not.toHaveBeenCalled();
});
it('should render report', () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
const onUpdate = api.on.mock.calls.find(([event]) => event === EVENTS.RESULT)[1];
// when
onUpdate(axeResult);
// then
expect(wrapper.find(A11YPanel)).toMatchSnapshot();
});
it("should render loader when it's running", () => {
// given
const api = createApi();
const wrapper = mount(<ThemedA11YPanel api={api} active />);
const request = api.on.mock.calls.find(([event]) => event === STORY_RENDERED)[1];
// when
request();
wrapper.update();
// then
expect(wrapper.find('ScrollArea').length).toBe(0);
expect(wrapper.find('Loader').length).toBe(1);
expect(wrapper.find('ActionBar').length).toBe(1);
expect(wrapper.find('Loader')).toMatchSnapshot();
});
it('should NOT anything when tab is not active', () => {
it('should handle inactive state', () => {
// given
const api = createApi();
@ -218,7 +164,7 @@ describe('A11YPanel', () => {
const wrapper = mount(<ThemedA11YPanel api={api} active={false} />);
// then
expect(wrapper.find('ScrollArea').length).toBe(0);
expect(wrapper.find('ActionBar').length).toBe(0);
expect(wrapper.text()).toBe('');
expect(api.emit).not.toHaveBeenCalled();
});
});

View File

@ -1,8 +1,8 @@
/* eslint-disable react/destructuring-assignment,default-case,consistent-return,no-case-declarations */
import React, { Component, Fragment } from 'react';
import { styled } from '@storybook/theming';
import { STORY_RENDERED } from '@storybook/core-events';
import { ActionBar, Icons, ScrollArea } from '@storybook/components';
import { AxeResults, Result } from 'axe-core';
@ -20,19 +20,15 @@ export enum RuleType {
INCOMPLETION,
}
const Icon = styled(Icons)(
{
height: '12px',
width: '12px',
marginRight: '4px',
},
({ status, theme }: any) =>
status === 'running'
? {
animation: `${theme.animation.rotate360} 1s linear infinite;`,
}
: {}
);
const Icon = styled(Icons)({
height: 12,
width: 12,
marginRight: 4,
});
const RotatingIcon = styled(Icon)(({ theme }) => ({
animation: `${theme.animation.rotate360} 1s linear infinite;`,
}));
const Passes = styled.span<{}>(({ theme }) => ({
color: theme.color.positive,
@ -46,34 +42,51 @@ const Incomplete = styled.span<{}>(({ theme }) => ({
color: theme.color.warning,
}));
const centeredStyle = {
const Centered = styled.span<{}>({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
};
});
const Loader = styled(({ className }) => (
<div className={className}>
<Icon inline icon="sync" status="running" /> Please wait while the accessibility scan is running
...
</div>
))(centeredStyle);
Loader.displayName = 'Loader';
interface InitialState {
status: 'initial';
}
interface A11YPanelNormalState {
status: 'ready' | 'ran' | 'running';
interface ManualState {
status: 'manual';
}
interface RunningState {
status: 'running';
}
interface RanState {
status: 'ran';
passes: Result[];
violations: Result[];
incomplete: Result[];
}
interface A11YPanelErrorState {
interface ReadyState {
status: 'ready';
passes: Result[];
violations: Result[];
incomplete: Result[];
}
interface ErrorState {
status: 'error';
error: unknown;
}
type A11YPanelState = A11YPanelNormalState | A11YPanelErrorState;
type A11YPanelState =
| InitialState
| ManualState
| RunningState
| RanState
| ReadyState
| ErrorState;
interface A11YPanelProps {
active: boolean;
@ -82,18 +95,15 @@ interface A11YPanelProps {
export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
state: A11YPanelState = {
status: 'ready',
passes: [],
violations: [],
incomplete: [],
status: 'initial',
};
componentDidMount() {
const { api } = this.props;
api.on(STORY_RENDERED, this.request);
api.on(EVENTS.RESULT, this.onUpdate);
api.on(EVENTS.RESULT, this.onResult);
api.on(EVENTS.ERROR, this.onError);
api.on(EVENTS.MANUAL, this.onManual);
}
componentDidUpdate(prevProps: A11YPanelProps) {
@ -103,18 +113,18 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
if (!prevProps.active && active) {
// removes all elements from the redux map in store from the previous panel
store.dispatch(clearElements());
this.request();
}
}
componentWillUnmount() {
const { api } = this.props;
api.off(STORY_RENDERED, this.request);
api.off(EVENTS.RESULT, this.onUpdate);
api.off(EVENTS.RESULT, this.onResult);
api.off(EVENTS.ERROR, this.onError);
api.off(EVENTS.MANUAL, this.onManual);
}
onUpdate = ({ passes, violations, incomplete }: AxeResults) => {
onResult = ({ passes, violations, incomplete }: AxeResults) => {
this.setState(
{
status: 'ran',
@ -142,64 +152,67 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
});
};
request = () => {
const { api, active } = this.props;
if (active) {
this.setState(
{
status: 'running',
},
() => {
api.emit(EVENTS.REQUEST);
// removes all elements from the redux map in store from the previous panel
store.dispatch(clearElements());
}
);
onManual = (manual: boolean) => {
if (manual) {
this.setState({
status: 'manual',
});
} else {
this.request();
}
};
request = () => {
const { api } = this.props;
this.setState(
{
status: 'running',
},
() => {
api.emit(EVENTS.REQUEST);
// removes all elements from the redux map in store from the previous panel
store.dispatch(clearElements());
}
);
};
render() {
const { active } = this.props;
if (!active) return null;
// eslint-disable-next-line react/destructuring-assignment
if (this.state.status === 'error') {
const { error } = this.state;
return (
<div style={centeredStyle}>
The accessibility scan encountered an error.
<br />
{error}
</div>
);
}
const { passes, violations, incomplete, status } = this.state;
let actionTitle;
if (status === 'ready') {
actionTitle = 'Rerun tests';
} else if (status === 'running') {
actionTitle = (
<Fragment>
<Icon inline icon="sync" status={status} /> Running test
</Fragment>
);
} else if (status === 'ran') {
actionTitle = (
<Fragment>
<Icon inline icon="check" /> Tests completed
</Fragment>
);
}
return (
<Fragment>
<Provider store={store}>
{status === 'running' ? (
<Loader />
switch (this.state.status) {
case 'initial':
return <Centered>Initializing...</Centered>;
case 'manual':
return (
<Fragment>
<Centered>Manually run the accessibility scan.</Centered>
<ActionBar
key="actionbar"
actionItems={[{ title: 'Run test', onClick: this.request }]}
/>
</Fragment>
);
case 'running':
return (
<Centered>
<RotatingIcon inline icon="sync" /> Please wait while the accessibility scan is running
...
</Centered>
);
case 'ready':
case 'ran':
const { passes, violations, incomplete, status } = this.state;
const actionTitle =
status === 'ready' ? (
'Rerun tests'
) : (
<Fragment>
<Icon inline icon="check" /> Tests completed
</Fragment>
);
return (
<Provider store={store}>
<ScrollArea vertical horizontal>
<Tabs
key="tabs"
@ -243,13 +256,21 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
]}
/>
</ScrollArea>
)}
<ActionBar
key="actionbar"
actionItems={[{ title: actionTitle, onClick: this.request }]}
/>
</Provider>
</Fragment>
);
<ActionBar
key="actionbar"
actionItems={[{ title: actionTitle, onClick: this.request }]}
/>
</Provider>
);
case 'error':
const { error } = this.state;
return (
<Centered>
The accessibility scan encountered an error.
<br />
{error}
</Centered>
);
}
}
}

View File

@ -18,7 +18,7 @@ const getFilter = (filter: string | null) => {
return `url('#${filter}')`;
};
const ColorIcon = styled.span(
const ColorIcon = styled.span<{ filter: string | null }>(
{
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
borderRadius: '1rem',
@ -26,7 +26,7 @@ const ColorIcon = styled.span(
height: '1rem',
width: '1rem',
},
({ filter }: { filter: string | null }) => ({
({ filter }) => ({
filter: getFilter(filter),
}),
({ theme }) => ({
@ -80,16 +80,14 @@ const getColorList = (active: string | null, set: (i: string | null) => void): L
];
export const ColorBlindness: FunctionComponent = () => {
const [active, setActiveState] = useState(null);
const [active, setActiveState] = useState<string | null>(null);
const setActive = (activeState: string | null): void => {
const iframe = getIframe();
if (iframe) {
iframe.style.filter = getFilter(activeState);
setActiveState({
active: activeState,
});
setActiveState(activeState);
} else {
logger.error('Cannot find Storybook iframe');
}

View File

@ -15,15 +15,15 @@ const ItemTitle = styled.span<{}>(({ theme }) => ({
borderBottom: `1px solid ${theme.appBorderColor}`,
width: '100%',
display: 'flex',
paddingBottom: '6px',
marginBottom: '6px',
paddingBottom: 6,
marginBottom: 6,
justifyContent: 'space-between',
}));
const HighlightToggleElement = styled.span({
fontWeight: 'normal',
alignSelf: 'center',
paddingRight: '15px',
paddingRight: 15,
input: { margin: 0 },
});

View File

@ -5,24 +5,24 @@ import { styled, themes, convert } from '@storybook/theming';
import memoize from 'memoizerific';
import { NodeResult } from 'axe-core';
import { Dispatch } from 'redux';
import { RuleType } from '../A11YPanel';
import { addElement } from '../../redux-config';
import { IFRAME } from '../../constants';
export class HighlightedElementData {
export interface HighlightedElementData {
originalOutline: string;
isHighlighted: boolean;
}
interface ToggleProps {
elementsToHighlight: NodeResult[];
type: RuleType;
addElement?: (data: any) => void;
highlightedElementsMap?: Map<HTMLElement, HighlightedElementData>;
addElement: (data: any) => void;
highlightedElementsMap: Map<HTMLElement, HighlightedElementData>;
isToggledOn?: boolean;
toggleId?: string;
indeterminate?: boolean;
indeterminate: boolean;
}
enum CheckBoxStates {
@ -48,7 +48,7 @@ function getElementBySelectorPath(elementPath: string): HTMLElement {
if (iframe && iframe.contentDocument && elementPath) {
return iframe.contentDocument.querySelector(elementPath);
}
return null;
return (null as unknown) as HTMLElement;
}
function setElementOutlineStyle(targetElement: HTMLElement, outlineStyle: string): void {
@ -64,7 +64,7 @@ function areAllRequiredElementsHighlighted(
const targetElement = getElementBySelectorPath(item.target[0]);
return (
highlightedElementsMap.has(targetElement) &&
highlightedElementsMap.get(targetElement).isHighlighted
(highlightedElementsMap.get(targetElement) as HighlightedElementData).isHighlighted
);
}).length;
@ -76,7 +76,7 @@ function areAllRequiredElementsHighlighted(
: CheckBoxStates.INDETERMINATE;
}
function mapDispatchToProps(dispatch: any) {
function mapDispatchToProps(dispatch: Dispatch) {
return {
addElement: (data: { element: HTMLElement; data: HighlightedElementData }) =>
dispatch(addElement(data)),
@ -112,7 +112,7 @@ class HighlightToggle extends Component<ToggleProps> {
});
}
componentDidUpdate(prevProps: Readonly<ToggleProps>): void {
componentDidUpdate(): void {
const { indeterminate } = this.props;
if (this.checkBoxRef.current) {
this.checkBoxRef.current.indeterminate = indeterminate;
@ -126,8 +126,9 @@ class HighlightToggle extends Component<ToggleProps> {
if (!highlightedElementsMap.has(targetElement)) {
return;
}
const { originalOutline } = highlightedElementsMap.get(targetElement);
const { isHighlighted } = highlightedElementsMap.get(targetElement);
const { originalOutline, isHighlighted } = highlightedElementsMap.get(
targetElement
) as HighlightedElementData;
const { isToggledOn } = this.props;
if ((isToggledOn && isHighlighted) || (!isToggledOn && !isHighlighted)) {
const addHighlight = !isToggledOn && !isHighlighted;
@ -151,7 +152,7 @@ class HighlightToggle extends Component<ToggleProps> {
if (highlightedElementsMap.has(targetElement)) {
setElementOutlineStyle(
targetElement,
highlightedElementsMap.get(targetElement).originalOutline
highlightedElementsMap.get(targetElement)!.originalOutline
);
}
}
@ -162,9 +163,7 @@ class HighlightToggle extends Component<ToggleProps> {
originalOutline: string
): void {
const { addElement: localAddElement } = this.props;
const data: HighlightedElementData = new HighlightedElementData();
data.isHighlighted = isHighlighted;
data.originalOutline = originalOutline;
const data: HighlightedElementData = { isHighlighted, originalOutline };
const payload = { element: targetElement, highlightedElementData: data };
localAddElement(payload);
}
@ -185,4 +184,7 @@ class HighlightToggle extends Component<ToggleProps> {
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HighlightToggle);
export default connect(
mapStateToProps,
mapDispatchToProps
)(HighlightToggle);

View File

@ -4,14 +4,14 @@ import { styled } from '@storybook/theming';
import { Result } from 'axe-core';
const Wrapper = styled.div({
padding: '12px',
marginBottom: '10px',
padding: 12,
marginBottom: 10,
});
const Help = styled.p({
margin: '0 0 12px',
});
const Link = styled.a({
marginTop: '12px',
marginTop: 12,
textDecoration: 'underline',
color: 'inherit',
display: 'block',

View File

@ -24,7 +24,7 @@ const Icon = styled<any, any>(Icons)(({ theme }) => ({
width: 10,
minWidth: 10,
color: theme.color.mediumdark,
marginRight: '10px',
marginRight: 10,
transition: 'transform 0.1s ease-in-out',
alignSelf: 'center',
display: 'inline-flex',
@ -49,7 +49,7 @@ const HeaderBar = styled.div<{}>(({ theme }) => ({
const HighlightToggleElement = styled.span({
fontWeight: 'normal',
float: 'right',
marginRight: '15px',
marginRight: 15,
alignSelf: 'center',
input: { margin: 0 },
});

View File

@ -1,60 +1,40 @@
import React, { FunctionComponent } from 'react';
import { styled } from '@storybook/theming';
import { Badge, Icons } from '@storybook/components';
import { Badge } from '@storybook/components';
import { CheckResult } from 'axe-core';
import { SizeMe } from 'react-sizeme';
import { RuleType } from '../A11YPanel';
const impactColors = {
minor: '#f1c40f',
moderate: '#e67e22',
serious: '#e74c3c',
critical: '#c0392b',
success: '#2ecc71',
};
const List = styled.div({
display: 'flex',
flexDirection: 'column',
paddingBottom: '4px',
paddingRight: '4px',
paddingTop: '4px',
paddingBottom: 4,
paddingRight: 4,
paddingTop: 4,
fontWeight: '400',
} as any);
const Item = styled.div(({ elementWidth }: { elementWidth: number }) => {
const Item = styled.div<{ elementWidth: number }>(({ elementWidth }) => {
const maxWidthBeforeBreak = 407;
return {
flexDirection: elementWidth > maxWidthBeforeBreak ? 'row' : 'inherit',
marginBottom: elementWidth > maxWidthBeforeBreak ? '6px' : '12px',
marginBottom: elementWidth > maxWidthBeforeBreak ? 6 : 12,
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
};
});
const StyledBadge = styled(Badge)(({ status }: { status: string }) => ({
const StyledBadge = styled(Badge)({
padding: '2px 8px',
marginBottom: '3px',
minWidth: '65px',
marginBottom: 3,
minWidth: 65,
maxWidth: 'fit-content',
width: '100%',
textAlign: 'center',
}));
const Message = styled.div({
paddingLeft: '6px',
paddingRight: '23px',
});
const Status = styled.div(({ passes, impact }: { passes: boolean; impact: string }) => ({
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
color: passes ? impactColors.success : (impactColors as any)[impact],
'& > svg': {
height: '16px',
width: '16px',
},
}));
const Message = styled.div({
paddingLeft: 6,
paddingRight: 23,
});
export enum ImpactValue {
MINOR = 'minor',
@ -94,7 +74,7 @@ const Rule: FunctionComponent<RuleProps> = ({ rule }) => {
}
return (
<SizeMe refreshMode="debounce">
{({ size }: { size: any }) => (
{({ size }: { size: { width: number; height: number } }) => (
<Item elementWidth={size.width}>
<StyledBadge status={badgeType}>{formatSeverityText(rule.impact)}</StyledBadge>
<Message>{rule.message}</Message>

View File

@ -11,7 +11,7 @@ const Wrapper = styled.div({
const Item = styled.div<{}>(({ theme }) => ({
margin: '0 6px',
padding: '5px',
padding: 5,
border: `1px solid ${theme.appBorderColor}`,
borderRadius: theme.appBorderRadius,
}));

View File

@ -1,6 +1,6 @@
import React, { Component, SyntheticEvent } from 'react';
import { styled, themes } from '@storybook/theming';
import { styled } from '@storybook/theming';
import { NodeResult, Result } from 'axe-core';
import { SizeMe } from 'react-sizeme';
import store, { clearElements } from '../redux-config';
@ -18,12 +18,12 @@ const Container = styled.div({
const HighlightToggleLabel = styled.label<{}>(({ theme }) => ({
cursor: 'pointer',
userSelect: 'none',
marginBottom: '3px',
marginRight: '3px',
marginBottom: 3,
marginRight: 3,
color: theme.color.dark,
}));
const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => {
const GlobalToggle = styled.div<{ elementWidth: number }>(({ elementWidth }) => {
const maxWidthBeforeBreak = 450;
return {
cursor: 'pointer',
@ -31,7 +31,7 @@ const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) =>
padding: elementWidth > maxWidthBeforeBreak ? '12px 15px 10px 0' : '12px 0px 3px 12px',
height: '40px',
border: 'none',
marginTop: elementWidth > maxWidthBeforeBreak ? '-40px' : '0px',
marginTop: elementWidth > maxWidthBeforeBreak ? -40 : 0,
float: elementWidth > maxWidthBeforeBreak ? 'right' : 'left',
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
alignItems: 'center',
@ -39,15 +39,15 @@ const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) =>
borderBottom: elementWidth > maxWidthBeforeBreak ? 'none' : '1px solid rgba(0,0,0,.1)',
input: {
marginLeft: '10',
marginRight: '0',
marginTop: '0',
marginBottom: '0',
marginLeft: 10,
marginRight: 0,
marginTop: 0,
marginBottom: 0,
},
};
});
const Item = styled.button(
const Item = styled.button<{ active?: boolean }>(
({ theme }) => ({
textDecoration: 'none',
padding: '10px 15px',
@ -66,7 +66,7 @@ const Item = styled.button(
borderBottom: `3px solid ${theme.color.secondary}`,
},
}),
({ active, theme }: any) =>
({ active, theme }) =>
active
? {
opacity: 1,
@ -99,7 +99,7 @@ interface TabsState {
}
function retrieveAllNodesFromResults(items: Result[]): NodeResult[] {
return items.reduce((acc, item) => acc.concat(item.nodes), []);
return items.reduce((acc, item) => acc.concat(item.nodes), [] as NodeResult[]);
}
export class Tabs extends Component<TabsProps, TabsState> {
@ -109,7 +109,7 @@ export class Tabs extends Component<TabsProps, TabsState> {
onToggle = (event: SyntheticEvent) => {
this.setState({
active: parseInt(event.currentTarget.getAttribute('data-index'), 10),
active: parseInt(event.currentTarget.getAttribute('data-index') || '', 10),
});
// removes all elements from the redux map in store from the previous panel
store.dispatch(clearElements());

File diff suppressed because it is too large Load Diff

View File

@ -7,5 +7,6 @@ export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS';
const RESULT = `${ADDON_ID}/result`;
const REQUEST = `${ADDON_ID}/request`;
const ERROR = `${ADDON_ID}/error`;
const MANUAL = `${ADDON_ID}/manual`;
export const EVENTS = { RESULT, REQUEST, ERROR };
export const EVENTS = { RESULT, REQUEST, ERROR, MANUAL };

View File

@ -11,8 +11,10 @@ interface Setup {
element?: ElementContext;
config: Spec;
options: RunOptions;
manual: boolean;
}
let setup: Setup = { element: null, config: {}, options: {} };
let setup: Setup = { element: undefined, config: {}, options: {}, manual: false };
const getElement = () => {
const storyRoot = document.getElementById('story-root');
@ -58,12 +60,16 @@ export const withA11y = makeDecorator({
if (storedDefaultSetup === null) {
storedDefaultSetup = { ...setup };
}
Object.assign(setup, parameters as Setup);
Object.assign(setup, parameters as Partial<Setup>);
} else if (storedDefaultSetup !== null) {
Object.assign(setup, storedDefaultSetup);
storedDefaultSetup = null;
}
addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options));
addons
.getChannel()
.on(EVENTS.REQUEST, () => run(setup.element as ElementContext, setup.config, setup.options));
addons.getChannel().emit(EVENTS.MANUAL, setup.manual);
return getStory(context);
},

View File

@ -2,7 +2,13 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"types": ["webpack-env"]
"types": ["webpack-env"],
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*"

View File

@ -14,10 +14,12 @@ Install:
npm i -D @storybook/addon-actions
```
Then, add following content to `.storybook/addons.js`
Then, add following content to `.storybook/main.js`
```js
import '@storybook/addon-actions/register';
module.exports = {
addons: ['@storybook/addon-actions/register']
}
```
Import the `action` function and use it to create actions handlers. When creating action handlers, provide a **name** to make it easier to identify.
@ -25,14 +27,17 @@ Import the `action` function and use it to create actions handlers. When creatin
> _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybookjs/storybook-addon-actions/issues/29#issuecomment-288274794)_
```js
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from './button';
storiesOf('Button', module).add('default view', () => (
export default {
title: 'Button',
component: Button,
};
export const defaultView = () => (
<Button onClick={action('button-click')}>Hello World!</Button>
));
);
```
## Multiple actions
@ -40,22 +45,27 @@ storiesOf('Button', module).add('default view', () => (
If your story requires multiple actions, it may be convenient to use `actions` to create many at once:
```js
import { storiesOf } from '@storybook/react';
import { actions } from '@storybook/addon-actions';
import Button from './button';
export default {
title: 'Button',
component: Button,
};
// This will lead to { onClick: action('onClick'), ... }
const eventsFromNames = actions('onClick', 'onMouseOver');
// This will lead to { onClick: action('clicked'), ... }
const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' });
storiesOf('Button', module)
.add('default view', () => <Button {...eventsFromNames}>Hello World!</Button>)
.add('default view, different actions', () => (
<Button {...eventsFromObject}>Hello World!</Button>
));
export const first = () => (
<Button {...eventsFromNames}>Hello World!</Button>
);
export const second = () => (
<Button {...eventsFromObject}>Hello World!</Button>
);
```
## Action Decorators
@ -66,14 +76,18 @@ If you wish to process action data before sending them over to the logger, you c
```js
import { decorate } from '@storybook/addon-actions';
import Button from './button';
export default {
title: 'Button',
component: Button,
};
const firstArg = decorate([args => args.slice(0, 1)]);
storiesOf('Button', module).add('default view', () => (
export const first = () => (
<Button onClick={firstArg.action('button-click')}>Hello World!</Button>
));
);
```
## Configuration
@ -88,7 +102,7 @@ The action logger, by default, will log all actions fired during the lifetime of
this can make the storybook laggy. As a workaround, you can configure an upper limit to how many actions should
be logged.
To apply the configuration globally use the `configureActions` function in your `config.js` file.
To apply the configuration globally use the `configureActions` function in your `preview.js` file.
```js
import { configureActions } from '@storybook/addon-actions';
@ -97,14 +111,14 @@ configureActions({
depth: 100,
// Limit the number of items logged into the actions panel
limit: 20,
})
});
```
To apply the configuration per action use:
```js
action('my-action', {
depth: 5,
})
});
```
### Available Options
@ -121,15 +135,15 @@ You can define action handles in a declarative way using `withActions` decorator
Keys have `'<eventName> <selector>'` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`.
```js
import { storiesOf } from '@storybook/html';
import { withActions } from '@storybook/addon-actions';
import Button from './button';
storiesOf('button', module)
// Log mouseovers on entire story and clicks on .btn
.addDecorator(withActions('mouseover', 'click .btn'))
.add('with actions', () => `
<div>
Clicks on this button will be logged: <button className="btn" type="button">Button</button>
</div>
`);
export default {
title: 'Button',
decorators: [withActions('mouseover', 'click .btn')]
};
export const first = () => (
<Button className="btn">Hello World!</Button>
);
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
@ -28,12 +28,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/client-api": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/theming": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/client-api": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/theming": "5.3.7",
"core-js": "^3.0.1",
"fast-deep-equal": "^2.0.1",
"global": "^4.3.2",
@ -44,10 +44,12 @@
"uuid": "^3.3.2"
},
"devDependencies": {
"@types/lodash": "^4.14.129",
"@types/uuid": "^3.4.4"
"@types/lodash": "^4.14.149",
"@types/uuid": "^3.4.4",
"@types/webpack-env": "^1.15.0"
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -3,7 +3,7 @@ import { opacify } from 'polished';
export const Action = styled.div({
display: 'flex',
padding: '0',
padding: 0,
borderLeft: '5px solid transparent',
borderBottom: '1px solid transparent',
transition: 'all 0.1s',
@ -17,7 +17,7 @@ export const Counter = styled.div<{}>(({ theme }) => ({
fontWeight: theme.typography.weight.bold,
lineHeight: 1,
padding: '1px 5px',
borderRadius: '20px',
borderRadius: 20,
margin: '2px 0px',
}));

View File

@ -14,12 +14,14 @@ npm i -D @storybook/addon-backgrounds
## Configuration
Then create a file called `addons.js` in your storybook config.
Then create a file called `main.js` in your storybook config.
Add following content to it:
```js
import '@storybook/addon-backgrounds/register';
module.exports = {
addons: ['@storybook/addon-backgrounds/register']
}
```
## Usage
@ -28,19 +30,23 @@ Then write your stories like this:
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
storiesOf('Button', module)
.addParameters({
export default {
title: 'Button',
parameters: {
backgrounds: [
{ name: 'twitter', value: '#00aced', default: true },
{ name: 'facebook', value: '#3b5998' },
],
})
.add('with text', () => <button>Click me</button>);
]
},
};
export const defaultView = () => (
<button>Click me</button>
);
```
You can add the backgrounds to all stories with `addParameters` in `.storybook/config.js`:
You can add the backgrounds to all stories with `addParameters` in `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/react'; // <- or your storybook framework
@ -51,48 +57,53 @@ addParameters({
{ name: 'facebook', value: '#3b5998' },
],
});
// should be before configure()
```
If you want to override backgrounds for a single story or group of stories, pass the `backgrounds` parameter:
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
storiesOf('Button', module)
.add('with text', () => <button>Click me</button>, {
backgrounds: [{
name: 'red', value: 'rgba(255, 0, 0)',
}]
});
export default {
title: 'Button',
}
export const defaultView = () => (
<button>Click me</button>
);
defaultView.story = {
parameters: {
backgrounds: [
{ name: 'red', value: 'rgba(255, 0, 0)' },
],
},
};
```
If you don't want to use backgrounds for a story, you can set the `backgrounds` parameter to `[]`, or use `{ disable: true }` to skip the addon:
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
storiesOf('Button', module)
.add('example 1', () => <button>Click me</button>, {
export default {
title: 'Button',
}
export const noBackgrounds = () => (
<button>Click me</button>
);
noBackgrounds.story = {
parameters: {
backgrounds: [],
});
},
};
storiesOf('Button', module)
.add('example 2', () => <button>Click me</button>, {
backgrounds: { disable: true },
});
```
## Events
If you want to react to a background change—for instance to implement some custom logic in your Storybook—you can subscribe to the `storybook/background/update` event. It will be emitted when the user changes the background.
```js
addonAPI.getChannel().on('storybook/background/update', (bg) => {
console.log('Background color', bg.selected);
console.log('Background name', bg.name);
});
export const disabledBackgrounds = () => (
<button>Click me</button>
);
disabledBackgrounds.story = {
parameters: {
backgrounds: { disabled: true },
},
};
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "A storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@ -32,21 +32,23 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/client-logger": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/theming": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/client-logger": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/theming": "5.3.7",
"core-js": "^3.0.1",
"memoizerific": "^1.11.3",
"react": "^16.8.3",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/util-deprecate": "^1.0.0"
"@types/util-deprecate": "^1.0.0",
"@types/webpack-env": "^1.15.0"
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "Storybook decorator to center components",
"keywords": [
"addon",
@ -29,15 +29,17 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"core-js": "^3.0.1",
"global": "^4.3.2",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/mithril": "^1.1.16",
"@types/webpack-env": "^1.15.0",
"mithril": "*",
"preact": "*",
"react": "*"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -47,14 +47,15 @@ To get it started, add this package into your project:
yarn add -D @storybook/addon-contexts
```
Then, register the addon by adding the following line into your `addon.js` file (you should be able to find the file
under the storybook config directory of your project):
within `.storybook/main.js`:
```js
import '@storybook/addon-contexts/register';
module.exports = {
addons: ['@storybook/addon-contexts/register']
}
```
To load your contextual setups for your stories globally, add the following lines into `config.js` file (you should
To load your contextual setups for your stories globally, add the following lines into `preview.js` file (you should
see it near your `addon.js` file):
```js
@ -68,28 +69,24 @@ addDecorator(withContexts(contexts));
Alternatively, like other addons, you can use this addon only for a given set of stories:
```js
import { storiesOf } from '@storybook/[framework]';
import { withContexts } from '@storybook/addon-contexts/[framework]';
import { contexts } from './configs/contexts';
const story = storiesOf('Component With Contexts', module).addDecorator(withContexts(contexts)); // use this addon with a default contextual environment setups
export default {
title: 'Component With Contexts',
decorators: [withContexts(contexts)],
};
```
Finally, you may want to modify the default setups at per story level. Here is how you can do this:
```js
story.add(
() => {
/* some stories */
},
{
contexts: [
{
/* the modified setup goes here, sharing the same API signatures */
},
],
export const defaultView = () => <div />;
defaultView.story = {
parameters: {
context: [{}]
}
);
};
```
## ⚙️ Setups
@ -112,8 +109,8 @@ export const contexts = [
],
params: [
// an array of params contains a set of predefined `props` for `components`
{ name: 'Light Theme', props: { theme /* : your dark theme */ } },
{ name: 'Dark Theme', props: { theme /* : your light theme */ }, default: true },
{ name: 'Light Theme', props: { theme /* : your light theme */ } },
{ name: 'Dark Theme', props: { theme /* : your dark theme */ }, default: true },
],
options: {
deep: true, // pass the `props` deeply into all wrapping components

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-contexts",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "Storybook Addon Contexts",
"keywords": [
"preact",
@ -27,10 +27,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"core-js": "^3.0.1",
"global": "^4.3.2",
"qs": "^6.6.0"
@ -39,10 +39,12 @@
"global": "*",
"preact": "*",
"qs": "*",
"rax": "*",
"react": "*",
"vue": "*"
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

4
addons/contexts/rax.js Normal file
View File

@ -0,0 +1,4 @@
import { withContexts } from './dist/preview/frameworks/rax';
export { withContexts };
export default withContexts;

View File

@ -27,10 +27,7 @@ describe('Tests on addon-contexts component: ToolBar', () => {
icon: 'box' as const,
nodeId: 'Some Context B',
options: { cancelable: true, deep: false, disable: false },
params: [
{ name: 'Some Param X', props: {} },
{ name: 'Some Param Y', props: {} },
],
params: [{ name: 'Some Param X', props: {} }, { name: 'Some Param Y', props: {} }],
title: 'Some Context B',
},
];

View File

@ -9,10 +9,7 @@ describe('Tests on addon-contexts component: ToolBarControl', () => {
icon: 'box' as const,
nodeId: 'Some Context',
options: { cancelable: true, deep: false, disable: false },
params: [
{ name: 'A', props: {} },
{ name: 'B', props: {} },
],
params: [{ name: 'A', props: {} }, { name: 'B', props: {} }],
title: 'Some Context',
selected: '',
setSelected: jest.fn,

View File

@ -1,4 +1,4 @@
import Preact from 'preact';
import { h, VNode } from 'preact';
import { createAddonDecorator, Render } from '../../index';
import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
@ -6,9 +6,9 @@ import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
* This is the framework specific bindings for Preact.
* '@storybook/preact' expects the returning object from a decorator to be a 'Preact vNode'.
*/
export const renderPreact: Render<Preact.VNode> = (contextNodes, propsMap, getStoryVNode) => {
export const renderPreact: Render<VNode> = (contextNodes, propsMap, getStoryVNode) => {
const { getRendererFrom } = ContextsPreviewAPI();
return getRendererFrom(Preact.h)(contextNodes, propsMap, getStoryVNode);
return getRendererFrom(h)(contextNodes, propsMap, getStoryVNode);
};
export const withContexts = createAddonDecorator(renderPreact);

View File

@ -0,0 +1,14 @@
import { createElement, RaxElement } from 'rax';
import { createAddonDecorator, Render } from '../../index';
import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
/**
* This is the framework specific bindings for Rax.
* '@storybook/rax' expects the returning object from a decorator to be a 'Rax Element' (vNode).
*/
export const renderRax: Render<RaxElement<any>> = (contextNodes, propsMap, getStoryVNode) => {
const { getRendererFrom } = ContextsPreviewAPI();
return getRendererFrom(createElement)(contextNodes, propsMap, getStoryVNode);
};
export const withContexts = createAddonDecorator(renderRax);

View File

@ -14,49 +14,37 @@ yarn add -D @storybook/addon-cssresources
## Configuration
Then create a file called `addons.js` in your storybook config.
Then create a file called `main.js` in your storybook config.
Add following content to it:
```js
import '@storybook/addon-cssresources/register';
module.exports = {
addons: ['@storybook/addon-cssresources/register']
}
```
## Usage
You need add the all the css resources at compile time using the `withCssResources` decorator. They can be added globally or per story. You can then choose which ones to load from the cssresources addon ui:
You need add the all the css resources at compile time using the `withCssResources` decorator. They can be added globally or per story. You can then choose which ones to load from the cssresources addon UI:
```js
// Import from @storybook/X where X is your framework
import { configure, addDecorator, addParameters, storiesOf } from '@storybook/react';
import { withCssResources } from '@storybook/addon-cssresources';
// global
addDecorator(withCssResources)
addParameters({
cssresources: [{
id: `bluetheme`,
code: `<style>body { background-color: lightblue; }</style>`,
picked: false,
},
],
});
export default {
title: 'CssResources',
parameters: {
cssresources: [{
id: `bluetheme`,
code: `<style>body { background-color: lightblue; }</style>`,
picked: false,
},
],
},
decorators: [withCssResources],
};
You can use the `cssresources` parameter to override resources on each story individually:
// per story
storiesOf('Addons|Cssresources', module)
.add('Camera Icon', () => <i className="fa fa-camera-retro"> Camera Icon</i>, {
cssresources: [
{
id: `fontawesome`,
code: `<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"></link>`,
picked: true,
}, {
id: `whitetheme`,
code: `<style>.fa { color: #fff }</style>`,
picked: true,
},
],
});
export const defaultView = () => (
<div />
);
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-cssresources",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "A storybook addon to switch between css resources at runtime for your story",
"keywords": [
"addon",
@ -32,18 +32,23 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/theming": "5.3.7",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3"
},
"devDependencies": {
"@types/webpack-env": "^1.15.0"
},
"peerDependencies": {
"react": "*"
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -1,7 +1,8 @@
import React, { Component, Fragment } from 'react';
import { SyntaxHighlighter } from '@storybook/components';
import { SyntaxHighlighter, Placeholder, Spaced, Icons } from '@storybook/components';
import { STORY_RENDERED } from '@storybook/core-events';
import { API } from '@storybook/api';
import { styled } from '@storybook/theming';
import { EVENTS, PARAM_KEY } from './constants';
import { CssResource } from './CssResource';
@ -20,6 +21,27 @@ interface CssResourceLookup {
[key: string]: CssResource;
}
const maxLimitToUseSyntaxHighlighter = 100000;
const PlainCode = styled.pre({
textAlign: 'left',
fontWeight: 'normal',
});
const Warning = styled.div({
display: 'flex',
padding: '10px',
justifyContent: 'center',
alignItems: 'center',
background: '#fff3cd',
fontSize: 12,
'& svg': {
marginRight: 10,
width: 24,
height: 24,
},
});
export class CssResourcePanel extends Component<Props, State> {
constructor(props: Props) {
super(props);
@ -96,7 +118,20 @@ export class CssResourcePanel extends Component<Props, State> {
<input type="checkbox" checked={picked} onChange={this.onChange} id={id} />
<span>#{id}</span>
</label>
{code ? <SyntaxHighlighter language="html">{code}</SyntaxHighlighter> : null}
{code && code.length < maxLimitToUseSyntaxHighlighter && (
<SyntaxHighlighter language="html">{code}</SyntaxHighlighter>
)}
{code && code.length >= maxLimitToUseSyntaxHighlighter && (
<Placeholder>
<Spaced row={1}>
<PlainCode>{code.substring(0, maxLimitToUseSyntaxHighlighter)} ...</PlainCode>
<Warning>
<Icons icon="alert" />
Rest of the content cannot be displayed
</Warning>
</Spaced>
</Placeholder>
)}
</div>
))}
</div>

View File

@ -11,45 +11,32 @@ npm install @storybook/addon-design-assets
```
## Usage
within `addons.js`:
within `.storybook/main.js`:
```js
import '@storybook/addon-design-assets/register';
module.exports = {
addons: ['@storybook/addon-design-assets/register']
}
```
within your stories:
```js
import { storiesOf } from '@storybook/react';
import React from 'react';
import imageUrl from './images/my-image.jpg';
storiesOf('root|group/component', module)
.addParameters({
export default {
title: 'Design Assets',
parameters: {
assets: [
imageUrl, // link to a file imported
'https://via.placeholder.com/300/09f/fff.png', // link to an external image
'https://www.example.com', // link to a webpage
'https://www.example.com?id={id}', // link to a webpage with the current story's id in the url
],
})
.add('variant', () => <div>your story here</div>);
```
},
};
If you have a set of different assets on 1 story, you might want to name then:
```js
import { storiesOf } from '@storybook/react';
import imageUrl from './images/my-image.jpg';
storiesOf('root|group/component', module)
.addParameters({
assets: [{
url: 'https://via.placeholder.com/300/09f/fff.png', // link to an external image
name: 'blue',
}, {
url: 'https://via.placeholder.com/300/f90/fff.png', // link to an external image
name: 'orange',
}],
})
.add('variant', () => <div>your story here</div>);
```
export const defaultView = () => (
<div>your story here</div>
);

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-design-assets",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "Design asset preview for storybook",
"keywords": [
"addon",
@ -34,12 +34,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/client-logger": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/core-events": "5.3.0-beta.2",
"@storybook/theming": "5.3.0-beta.2",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/client-logger": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/theming": "5.3.7",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3",
@ -48,5 +48,6 @@
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

View File

@ -1,5 +1,5 @@
<center>
<img src="docs/media/hero.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/hero.png" width="100%" />
</center>
# Storybook Docs
@ -32,7 +32,7 @@ When you [install Docs](#installation), every story gets a `DocsPage`. `DocsPage
Click on the `Docs` tab to see it:
<center>
<img src="docs/media/docs-tab.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/docs-tab.png" width="100%" />
</center>
For more information on how it works, see the [`DocsPage` reference](./docs/docspage.md).
@ -68,7 +68,7 @@ markdown documentation.
And here's how that's rendered in Storybook:
<center>
<img src="docs/media/mdx-simple.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-simple.png" width="100%" />
</center>
For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md).
@ -77,16 +77,16 @@ For more information on `MDX`, see the [`MDX` reference](./docs/mdx.md).
Storybook Docs supports all view layers that Storybook supports except for React Native (currently). There are some framework-specific features as well, such as props tables and inline story rendering. This chart captures the current state of support:
| | React | Vue | Angular | HTML | [Web Components](./web-components) | Svelte | Polymer | Marko | Mithril | Riot | Ember | Preact |
| ----------------- | :---: | :-: | :-----: | :--: | :--------------------------------: | :----: | :-----: | :---: | :-----: | :--: | :---: | :----: |
| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + |
| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + |
| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + |
| Source | + | + | + | + | + | + | + | + | + | + | + | + |
| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + |
| Props table | + | + | # | | + | | | | | | | |
| Description | + | + | # | | + | | | | | | | |
| Inline stories | + | + | | | + | | | | | | | |
| | React | Vue | Angular | Ember | Web Components | HTML | Svelte | Preact | Polymer | Riot | Mithril | Marko |
| ----------------- | :---: | :-: | :-----: | :---: | :------------: | :--: | :----: | :----: | :-----: | :--: | :-----: | :---: |
| MDX stories | + | + | + | + | + | + | + | + | + | + | + | + |
| CSF stories | + | + | + | + | + | + | + | + | + | + | + | + |
| StoriesOf stories | + | + | + | + | + | + | + | + | + | + | + | + |
| Source | + | + | + | + | + | + | + | + | + | + | + | + |
| Notes / Info | + | + | + | + | + | + | + | + | + | + | + | + |
| Props table | + | + | + | + | + | | | | | | | |
| Description | + | + | + | + | + | | | | | | | |
| Inline stories | + | + | | | + | | | | | | | |
**Note:** `#` = WIP support
@ -106,22 +106,15 @@ Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you wa
yarn add -D react react-is babel-loader
```
Then add the following to your `.storybook/presets.js` exports:
Then add the following to your `.storybook/main.js`:
```js
module.exports = ['@storybook/addon-docs/preset'];
module.exports = {
stories: ['../src/**/*.stories.(js|mdx)'],
addons: ['@storybook/addon-docs'],
};
```
**Configure.** If you're migrating from an earlier version of Storybook and want to use `MDX`, you need to upgrade your Storybook config:
```js
import { configure } from '@storybook/react';
configure(require.context('../src', true, /\.stories\.(js|mdx)$/), module);
```
For more information on the new `configure`, see ["Loading stories"](https://github.com/storybookjs/storybook/blob/next/docs/src/pages/basics/writing-stories/index.md#loading-stories) in the Storybook documentation.
If using in conjunction with the [storyshots add-on](../storyshots/storyshots-core/README.md), you will need to
configure Jest to transform MDX stories into something Storyshots can understand:
@ -138,8 +131,10 @@ Add the following to your Jest configuration:
### Be sure to check framework specific installation needs
- [Angular](./angular)
- [React](./react) (covered here)
- [Vue](./vue)
- [Angular](./angular)
- [Ember](./ember)
- [Web Components](./web-components)
## Preset options
@ -147,65 +142,71 @@ Add the following to your Jest configuration:
The `addon-docs` preset has a few configuration options that can be used to configure its babel/webpack loading behavior. Here's an example of how to use the preset with options:
```js
module.exports = [
{
name: '@storybook/addon-docs/react/preset',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
module.exports = {
addons: [
{
name: '@storybook/addon-docs',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
},
},
},
];
],
};
```
The `configureJsx` option is useful when you're writing your docs in MDX and your project's babel config isn't already set up to handle JSX files. `babelOptions` is a way to further configure the babel processor when you're using `configureJSX`.
The `configureJSX` option is useful when you're writing your docs in MDX and your project's babel config isn't already set up to handle JSX files. `babelOptions` is a way to further configure the babel processor when you're using `configureJSX`.
`sourceLoaderOptions` is an object for configuring `@storybook/source-loader`. When set to `null` it tells docs not to run the `source-loader` at all, which can be used as an optimization, or if you're already using `source-loader` in your `webpack.config.js`.
`sourceLoaderOptions` is an object for configuring `@storybook/source-loader`. When set to `null` it tells docs not to run the `source-loader` at all, which can be used as an optimization, or if you're already using `source-loader` in your `main.js`.
## Manual configuration
If you don't want to use the preset, and prefer to configure "the long way", first register the addon in `.storybook/addons.js`:
If you don't want to use the preset, and prefer to configure "the long way", first register the addon in `.storybook/main.js`:
```js
import '@storybook/addon-docs/register';
module.exports = {
addons: ['@storybook/addon-docs/register'],
};
```
Then configure Storybook's webpack loader in `.storybook/webpack.config.js` to understand MDX story files and annotate TS/JS story files with source code using `source-loader`:
Then configure Storybook's webpack loader in `.storybook/main.js` to understand MDX story files and annotate TS/JS story files with source code using `source-loader`:
```js
const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin');
module.exports = async ({ config }) => {
config.module.rules.push({
test: /\.(stories|story)\.mdx$/,
use: [
{
loader: 'babel-loader',
// may or may not need this line depending on your app's setup
options: {
plugins: ['@babel/plugin-transform-react-jsx'],
module.exports = {
webpackFinal: async config => {
config.module.rules.push({
test: /\.(stories|story)\.mdx$/,
use: [
{
loader: 'babel-loader',
// may or may not need this line depending on your app's setup
options: {
plugins: ['@babel/plugin-transform-react-jsx'],
},
},
},
{
loader: '@mdx-js/loader',
options: {
compilers: [createCompiler({})],
{
loader: '@mdx-js/loader',
options: {
compilers: [createCompiler({})],
},
},
},
],
});
config.module.rules.push({
test: /\.(stories|story)\.[tj]sx?$/,
loader: require.resolve('@storybook/source-loader'),
exclude: [/node_modules/],
enforce: 'pre',
});
return config;
],
});
config.module.rules.push({
test: /\.(stories|story)\.[tj]sx?$/,
loader: require.resolve('@storybook/source-loader'),
exclude: [/node_modules/],
enforce: 'pre',
});
return config;
},
};
```
Finally, you'll need to set up DocsPage in `.storybook/config.js`:
Finally, you'll need to set up DocsPage in `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/react';

View File

@ -22,10 +22,12 @@ First add the package. Make sure that the versions for your `@storybook/*` packa
yarn add -D @storybook/addon-docs@next
```
Then add the following to your `.storybook/presets.js` exports:
Then add the following to your `.storybook/main.js` exports:
```js
module.exports = ['@storybook/addon-docs/preset'];
module.exports = {
addons: ['@storybook/addon-docs'],
};
```
## DocsPage
@ -55,7 +57,7 @@ Then you'll need to configure Compodoc to generate a `documentation.json` file.
Unfortunately, it's not currently possible to update this dynamically as you edit your components, but [there's an open issue](https://github.com/storybookjs/storybook/issues/8672) to support this with improvements to Compodoc.
Next, add the following to your `.storybook/config.json` to load the Compodoc-generated file:
Next, add the following to `.storybook/preview.ts` to load the Compodoc-generated file:
```js
import { setCompodocJson } from '@storybook/addon-docs/angular';
@ -95,10 +97,12 @@ Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you wa
yarn add -D react react-is babel-loader
```
Then update your `.storybook/config.ts` to make sure you load MDX files:
Then update your `.storybook/main.js` to make sure you load MDX files:
```ts
configure(require.context('../src/stories', true, /\.stories\.(ts|mdx)$/), module);
module.exports = {
stories: ['../src/stories/**/*.stories.(js|mdx)'],
};
```
Finally, you can create MDX files like this:
@ -131,7 +135,7 @@ Also, to use the `Props` doc block, you need to set up Compodoc, [as described a
Storybook Docs renders all Angular stories inside IFrames, with a default height of `60px`. You can update this default globally, or modify the IFrame height locally per story in `DocsPage` and `MDX`.
To update the global default, modify `.storybook/config.ts`:
To update the global default, modify `.storybook/preview.ts`:
```ts
import { addParameters } from '@storybook/angular';

View File

@ -1,18 +1,28 @@
<center>
<img src="./media/docspage-hero.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/docspage-hero.png" width="100%" />
</center>
# Storybook DocsPage
When you install [Storybook Docs](../README.md), `DocsPage` is the zero-config default documentation that all stories get out of the box. It aggregates your stories, text descriptions, docgen comments, props tables, and code examples into a single page for each component.
- [Motivation](#motivation)
- [Component parameter](#component-parameter)
- [Subcomponents parameter](#subcomponents-parameter)
- [DocsPage slots](#docspage-slots)
- [Replacing DocsPage](#replacing-docspage)
- [Story file names](#story-file-names)
- [More resources](#more-resources)
- [Storybook DocsPage](#storybook-docspage)
- [Motivation](#motivation)
- [Component parameter](#component-parameter)
- [Subcomponents parameter](#subcomponents-parameter)
- [DocsPage slots](#docspage-slots)
- [Slot values](#slot-values)
- [Title](#title)
- [Subtitle](#subtitle)
- [Description](#description)
- [Primary](#primary)
- [Props](#props)
- [Stories](#stories)
- [Slot functions](#slot-functions)
- [Replacing DocsPage](#replacing-docspage)
- [Story file names](#story-file-names)
- [Inline stories vs. Iframe stories](#inline-stories-vs-iframe-stories)
- [More resources](#more-resources)
## Motivation
@ -80,7 +90,7 @@ If you want organize your documentation differently for groups of components, we
`DocsPage` is organized into a series of "slots" including Title, Subtitle, Description, Props, and Story. Each of these slots pulls information from your project and formats it for the screen.
<center>
<img style="padding: 30px; border: 3px solid #eee;" src="./media/docspage-slots.png" width="100%" />
<img style="padding: 30px; border: 3px solid #eee;" src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/docspage-slots.png" width="100%" />
</center>
## Slot values
@ -225,7 +235,7 @@ You can replace DocsPage at any level by overriding the `docs.page` parameter:
- [With MDX](./recipes.md#csf-stories-with-mdx-docs) docs
- With a custom React component
**Globally (config.js)**
**Globally (preview.js)**
```js
import { addParameters } from '@storybook/react';

View File

@ -19,7 +19,7 @@ Currently we hide the addons panel when docs is visible. It's tricky because all
## How do I debug my MDX story?
<center>
<img src="./media/faq-debug.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/faq-debug.png" width="100%" />
</center>
> "My story renders in docs, but doesnt show up the way Id expect in the Canvas”
@ -37,7 +37,7 @@ For example, the following MDX story:
Shows up in the dev tools as follows:
<center>
<img src="./media/faq-devtools.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/faq-devtools.png" width="100%" />
</center>
This is [Component Story Format (CSF)](https://medium.com/storybookjs/component-story-format-66f4c32366df), so there are ways to debug. You can copy and paste this code into a new `.stories.js` file and play around with it at a lower level to understand what's going wrong.

View File

@ -1,5 +1,5 @@
<center>
<img src="./media/mdx-hero.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-hero.png" width="100%" />
</center>
# Storybook Docs MDX
@ -8,14 +8,15 @@
`MDX` is the syntax [Storybook Docs](../README.md) uses to capture long-form markdown documentation and stories in one file. You can also write pure documentation pages in `MDX` and add them to Storybook alongside your stories.
- [Basic example](#basic-example)
- [MDX-Flavored CSF](#mdx-flavored-csf)
- [Writing stories](#writing-stories)
- [Embedding stories](#embedding-stories)
- [Decorators and parameters](#decorators-and-parameters)
- [Documentation-only MDX](#documentation-only-mdx)
- [MDX file names](#mdx-file-names)
- [More resources](#more-resources)
- [Storybook Docs MDX](#storybook-docs-mdx)
- [Basic example](#basic-example)
- [MDX-Flavored CSF](#mdx-flavored-csf)
- [Writing stories](#writing-stories)
- [Embedding stories](#embedding-stories)
- [Decorators and parameters](#decorators-and-parameters)
- [Documentation-only MDX](#documentation-only-mdx)
- [MDX file names](#mdx-file-names)
- [More resources](#more-resources)
## Basic example
@ -46,7 +47,7 @@ markdown documentation.
And here's how that's rendered in Storybook:
<center>
<img src="./media/mdx-simple.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-simple.png" width="100%" />
</center>
As you can see there's a lot going on here. We're writing Markdown, we're writing JSX, and somehow we're also defining Storybook stories that are drop-in compatible with the entire Storybook ecosystem.
@ -130,7 +131,7 @@ with unique URLs and isolated snapshot tests.
And here's how that gets rendered in Storybook:
<center>
<img src="./media/mdx-page.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-page.png" width="100%" />
</center>
## Embedding stories
@ -165,7 +166,7 @@ To add [decorators](https://github.com/storybookjs/storybook/blob/next/docs/src/
</Story>
```
In addition, global decorators work just like before, e.g. adding the following to your `.storybook/config.js`:
In addition, global decorators work just like before, e.g. adding the following to your `.storybook/preview.js`:
```js
import { addDecorator, addParameters } from '@storybook/react';
@ -185,7 +186,7 @@ If you don't define a `Meta`, you can write Markdown and associate with an exist
To get a "documentation-only story", in your UI, define a `<Meta>` as you normally would, but don't define any stories. It will show up in your UI as a documentation node:
<center>
<img src="./media/mdx-documentation-only.png" width="100%" />
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-documentation-only.png" width="100%" />
</center>
## MDX file names

View File

@ -1,51 +1,42 @@
# Storybook Docs framework dev guide
Storybook Docs [provides basic support for all non-RN Storybook view layers](../README.md#framework-support) out of the box. However, some frameworks have been docs-optimized, adding features like automatic props table generation and inline story rendering. This document is a dev guide for how to set up a new framework in docs.
Storybook Docs [provides basic support for all non-RN Storybook view layers](../README.md#framework-support) out of the box. However, some frameworks have been docs-optimized, adding features like automatic props table generation and inline story rendering. This document is a dev guide for how to optimize a new framework in docs.
- [Adding a preset](#adding-a-preset)
- [Framework-specific configuration](#framework-specific-configuration)
- [Props tables](#props-tables)
- [Component descriptions](#component-descriptions)
- [Inline story rendering](#inline-story-rendering)
## Adding a preset
## Framework-specific configuration
To get basic support, you need to add a [preset](https://storybook.js.org/docs/presets/introduction). By default this doesn't need to do much.
Your framework might need framework-specific configuration. This could include adding extra webpack loaders or global decorators/story parameters.
Here's a basic preset for `@storybook/html` in `addons/docs/html/preset.js`:
Addon-docs handles this kind of customization by file naming convention. Its [common preset](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/common/preset.ts) does this by looking for files `../<framework>/{preset,config}.[tj]sx?`, where `<framework>` is the framework identifier, e.g. `vue`, `angular`, `react`, etc.
```js
module.exports = require('../dist/frameworks/common/makePreset').default('html');
For example, consider Storybook Docs for Vue, which needs `vue-docgen-loader` in its webpack config, and also has custom extraction functions for [props tables](#props-tables) and [component descriptions](#component-descriptions).
For webpack configuration, Docs for Vue defines [preset.ts](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/preset.ts), which follows the [preset](https://storybook.js.org/docs/presets/introduction) file structure:
```
export function webpack(webpackConfig: any = {}, options: any = {}) {
webpackConfig.module.rules.push({
test: /\.vue$/,
loader: 'vue-docgen-loader',
enforce: 'post',
});
return webpackConfig;
}
```
This automatically adds [DocsPage](./docspage.md) for each story, as well as webpack/babel settings for MDX support.
This appends `vue-docgen-loader` to the existing configuration, which at this point will also include modifications made by the common preset.
There is also a little hoop-jumping that will hopefully be unnecessary soon.
`addons/docs/src/frameworks/html/config.js`
```js
import { addParameters } from '@storybook/html';
import { DocsPage, DocsContainer } from '@storybook/addon-docs/blocks';
addParameters({
docs: {
container: DocsContainer,
page: DocsPage,
},
});
```
`addons/docs/html/config.js`
```js
module.exports = require('../dist/frameworks/html/config');
```
For props tables and descriptions, both of which are described in more detail below, it defines a file [config.tsx](https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/config.tsx).
## Props tables
Props tables are enabled by the framework-specific `docs.extractProps` parameter, which extracts a component's props into a common data structure.
Here's how it's done in Vue's framework-specific `config.js`:
Here's how it's done in Vue's framework-specific `preview.js`:
```js
import { extractProps } from './extractProps';
@ -58,7 +49,7 @@ addParameters({
});
```
The `extractProps`function receives a component as an argument, and returns an object of type [`PropsTableProps`](https://github.com/storybookjs/storybook/blob/next/lib/components/src/blocks/PropsTable/PropsTable.tsx#L147), which can either be a array of `PropDef` rows (React), or a mapping of section name to an array of `PropDef` rows (e.g. `Props`/`Events`/`Slots` in Vue).
The `extractProps`function receives a component as an argument, and returns an object of type [`PropsTableProps`](https://github.com/storybookjs/storybook/blob/next/lib/components/src/blocks/PropsTable/PropsTable.tsx#L147), which can either be an array of `PropDef` rows (React), or a mapping of section name to an array of `PropDef` rows (e.g. `Props`/`Events`/`Slots` in Vue).
```ts
export interface PropDef {
@ -88,7 +79,7 @@ It follows the pattern of [Props tables](#props-tables) above, only it's even si
Inline story rendering is another framework specific optimization, made possible by the `docs.prepareForInline` parameter.
Again let's look at Vue's framework-specific `config.js`:
Again let's look at Vue's framework-specific `preview.js`:
```js
import toReact from '@egoist/vue-to-react';

View File

@ -157,7 +157,7 @@ We made this error explicit to make sure you know what you're doing when you mix
If you're currently using the notes/info addons, you can upgrade to DocsPage by providing a custom `docs.extractComponentDescription` parameter. There are different ways to use each addon, so you can adapt this recipe according to your use case.
Suppose you've added a `notes` parameter to each component in your library, containing markdown text, and you want that to show up at the top of the page in the `Description` slot. You could do that by adding the following snippet to `.storybook/config.js`:
Suppose you've added a `notes` parameter to each component in your library, containing markdown text, and you want that to show up at the top of the page in the `Description` slot. You could do that by adding the following snippet to `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/client-api';

View File

@ -10,7 +10,7 @@
Storybook theming is the **recommended way** to theme your docs. If you update your storybook theme according to [the documentation](https://storybook.js.org/docs/configurations/theming/), Storybook Docs should adapt in reasonable ways.
For example, here's how to change your docs (and Storybook) to the dark theme, by modifying `.storybook/config.js`:
For example, here's how to change your docs (and Storybook) to the dark theme, by modifying `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/react';
@ -45,7 +45,7 @@ You can style these classes in `.storybook/preview-head.html`. For example, here
If you're using MDX, there's one more level of themability. MDX allows you to [completely override the components](https://mdxjs.com/advanced/components) that are rendered from markdown using a `components` parameter. This is an advanced usage that we don't officially support in Storybook, but it's a powerful mechanism if you need it.
Here's how you might insert a custom code renderer for `code` blocks on the page, in `.storybook/config.js`:
Here's how you might insert a custom code renderer for `code` blocks on the page, in `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/react';
@ -60,6 +60,22 @@ addParameters({
});
```
You can even override a Storybook *block* component.
Here's how you might insert a custom `<Preview />` block:
```js
import { MyPreview } from './MyPreview';
addParameters({
docs: {
components: {
Preview: MyPreview,
},
},
});
```
## More resources
Want to learn more? Here are some more articles on Storybook Docs:

150
addons/docs/ember/README.md Normal file
View File

@ -0,0 +1,150 @@
# Storybook Docs for Ember
Storybook Docs transforms your Storybook stories into world-class component documentation. Storybook Docs for Ember supports [DocsPage](../docs/docspage.md) for auto-generated docs, and [MDX](../docs/mdx.md) for rich long-form docs.
To learn more about Storybook Docs, read the [general documentation](../README.md). To learn the Ember specifics, read on!
- [Installation](#installation)
- [DocsPage](#docspage)
- [MDX](#mdx)
- [IFrame height](#iframe-height)
- [More resources](#more-resources)
## Installation
First add the package. Make sure that the versions for your `@storybook/*` packages match:
```sh
yarn add -D @storybook/addon-docs@next
```
Then add the following to your `.storybook/main.js` addons:
```js
module.exports = {
addons: ['@storybook/addon-docs'],
};
```
## DocsPage
When you [install docs](#installation) you should get basic [DocsPage](../docs/docspage.md) documentation automagically for all your stories, available in the `Docs` tab of the Storybook UI.
Props tables for your components requires a few more steps. Docs for Ember relies on [@storybook/ember-cli-storybook addon](https://github.com/storybookjs/ember-cli-storybook), to extract documentation comments from your component source files. If you're using Storybook with Ember, you should already have this addon installed, you will just need to enable it by adding the following config block in your `ember-cli-build.js` file:
```js
let app = new EmberApp(defaults, {
'ember-cli-storybook': {
enableAddonDocsIntegration: true,
},
});
```
Now, running the ember-cli server will generate a JSON documentation file at `/storybook-docgen/index.json`. Since generation of this file is tied into the ember-cli build, it will get regenerated everytime component files are saved. For details on documenting your components, check out the examples in the addon that powers the generation [ember-cli-addon-docs-yuidoc](https://github.com/ember-learn/ember-cli-addon-docs-yuidoc#documenting-components).
Next, add the following to your `.storybook/preview.js` to load the generated json file:
```js
import { setJSONDoc } from '@storybook/addon-docs/ember';
import docJson from '../storybook-docgen/index.json';
setJSONDoc(docJson);
```
Finally, be sure to fill in the `component` field in your story metadata. This should be a string that matches the name of the `@class` used in your souce comments:
```ts
export default {
title: 'App Component',
component: 'AppComponent',
};
```
If you haven't upgraded from `storiesOf`, you can use a parameter to do the same thing:
```ts
import { storiesOf } from '@storybook/angular';
storiesOf('App Component', module)
.addParameters({ component: 'AppComponent' })
.add( ... );
```
## MDX
[MDX](../docs/mdx.md) is a convenient way to document your components in Markdown and embed documentation components, such as stories and props tables, inline.
Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you'll need to add these dependencies as well:
```sh
yarn add -D react react-is babel-loader
```
Then update your `.storybook/main.js` to make sure you load MDX files:
```js
module.exports = {
stories: ['../src/stories/**/*.stories.(js|mdx)'],
};
```
Finally, you can create MDX files like this:
```md
import { Meta, Story, Props } from '@storybook/addon-docs/blocks';
import hbs from 'htmlbars-inline-precompile'
<Meta title='App Component' component='AppComponent' />
# App Component
Some **markdown** description, or whatever you want.
<Story name='basic' height='400px'>{{
template: hbs`<AppComponent @title={{title}} />`,
context: { title: "Title" },
}}</Story>
## Props
<Props of='AppComponent' />
```
Yes, it's redundant to declare `component` twice. [Coming soon](https://github.com/storybookjs/storybook/issues/8673).
Also, to use the `Props` doc block, you need to set up documentation generation, [as described above](#docspage).
## IFrame height
Storybook Docs renders all Ember stories inside `iframe`s, with a default height of `60px`. You can update this default globally, or modify the `iframe` height locally per story in `DocsPage` and `MDX`.
To update the global default, modify `.storybook/preview.js`:
```ts
import { addParameters } from '@storybook/ember';
addParameters({ docs: { iframeHeight: 400 } });
```
For `DocsPage`, you need to update the parameter locally in a story:
```ts
export const basic = () => ...
basic.story = {
parameters: { docs: { iframeHeight: 400 } }
}
```
And for `MDX` you can modify it as an attribute on the `Story` element:
```md
<Story name='basic' height='400px'>{...}</Story>
```
## More resources
Want to learn more? Here are some more articles on Storybook Docs:
- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md)
- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a)
- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf)
- Example: [Storybook Design System](https://github.com/storybookjs/design-system)

View File

@ -0,0 +1 @@
module.exports = require('../dist/frameworks/ember');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "5.3.0-beta.2",
"version": "5.3.7",
"description": "Superior documentation for your components",
"keywords": [
"addon",
@ -22,6 +22,7 @@
"docs/**/*",
"angular/**/*",
"common/**/*",
"ember/**/*",
"html/**/*",
"postinstall/**/*",
"react/**/*",
@ -42,16 +43,17 @@
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@egoist/vue-to-react": "^1.1.0",
"@jest/transform": "^24.9.0",
"@mdx-js/loader": "^1.1.0",
"@mdx-js/mdx": "^1.1.0",
"@mdx-js/react": "^1.0.27",
"@storybook/addons": "5.3.0-beta.2",
"@storybook/api": "5.3.0-beta.2",
"@storybook/components": "5.3.0-beta.2",
"@storybook/postinstall": "5.3.0-beta.2",
"@storybook/router": "5.3.0-beta.2",
"@storybook/source-loader": "5.3.0-beta.2",
"@storybook/theming": "5.3.0-beta.2",
"@mdx-js/loader": "^1.5.1",
"@mdx-js/mdx": "^1.5.1",
"@mdx-js/react": "^1.5.1",
"@storybook/addons": "5.3.7",
"@storybook/api": "5.3.7",
"@storybook/components": "5.3.7",
"@storybook/core-events": "5.3.7",
"@storybook/csf": "0.0.1",
"@storybook/postinstall": "5.3.7",
"@storybook/source-loader": "5.3.7",
"@storybook/theming": "5.3.7",
"acorn": "^7.1.0",
"acorn-jsx": "^5.1.0",
"acorn-walk": "^7.0.0",
@ -63,10 +65,13 @@
"js-string-escape": "^1.0.1",
"lodash": "^4.17.15",
"prop-types": "^15.7.2",
"react-element-to-jsx-string": "^14.1.0",
"remark-external-links": "^5.0.0",
"remark-slug": "^5.1.2",
"ts-dedent": "^1.1.0",
"util-deprecate": "^1.0.2",
"vue-docgen-api": "^3.26.0",
"vue-docgen-loader": "^1.0.1"
"vue-docgen-api": "^4.1.0",
"vue-docgen-loader": "^1.3.0-beta.0"
},
"devDependencies": {
"@types/doctrine": "^0.0.3",
@ -74,7 +79,6 @@
"@types/jest": "^24.0.11",
"@types/prop-types": "^15.5.9",
"@types/util-deprecate": "^1.0.0",
"@types/webpack-env": "^1.14.0",
"jest-specific-snapshot": "^2.0.0"
},
"peerDependencies": {
@ -84,5 +88,6 @@
},
"publishConfig": {
"access": "public"
}
},
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
}

120
addons/docs/react/README.md Normal file
View File

@ -0,0 +1,120 @@
<center>
<img src="../docs/media/docspage-hero.png" width="100%" />
</center>
# Storybook Docs for React
Storybook Docs transforms your Storybook stories into world-class component documentation. Storybook Docs for React supports [DocsPage](../docs/docspage.md) for auto-generated docs, and [MDX](../docs/mdx.md) for rich long-form docs.
To learn more about Storybook Docs, read the [general documentation](../README.md). To learn the React specifics, read on!
- [Installation](#installation)
- [DocsPage](#docspage)
- [MDX](#mdx)
- [Inline stories](#inline-stories)
- [More resources](#more-resources)
## Installation
First add the package. Make sure that the versions for your `@storybook/*` packages match:
```sh
yarn add -D @storybook/addon-docs@next
```
Then add the following to your `.storybook/main.js` list of `addons`:
```js
module.exports = {
// other settings
addons: ['@storybook/addon-docs'];
}
```
## DocsPage
When you [install docs](#installation) you should get basic [DocsPage](../docs/docspage.md) documentation automagically for all your stories, available in the `Docs` tab of the Storybook UI.
To show the props table for your component, be sure to fill in the `component` field in your story metadata:
```ts
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
};
```
If you haven't upgraded from `storiesOf`, you can use a parameter to do the same thing:
```ts
import { storiesOf } from '@storybook/react';
import { Button } from './Button';
storiesOf('InfoButton', module)
.addParameters({ component: Button })
.add( ... );
```
## MDX
[MDX](../docs/mdx.md) is a convenient way to document your components in Markdown and embed documentation components, such as stories and props tables, inline.
Docs has peer dependencies on `react`, `react-is`, and `babel-loader`. If you want to write stories in MDX, you may need to add these dependencies as well:
```sh
yarn add -D react react-is babel-loader
```
Then update your `.storybook/main.js` to make sure you load MDX files:
```js
module.exports = {
stories: ['../src/stories/**/*.stories.(js|mdx)'],
};
```
Finally, you can create MDX files like this:
```md
import { Meta, Story, Props } from '@storybook/addon-docs/blocks';
import { Button } from './Button';
<Meta title='Button' component={Button} />
# Button
Some **markdown** description, or whatever you want.
<Story name='basic' height='400px'>
<Button>Label</Button>
</Story>
## Props
<Props of={Button} />
```
## Inline Stories
Storybook Docs renders all React stories inline on the page by default. If you want to render stories in an `iframe` so that they are better isolated. To do this, update `.storybook/preview.js`:
```js
import { addParameters } from '@storybook/react';
addParameters({
docs: {
inlineStories: false,
},
});
```
## More resources
Want to learn more? Here are some more articles on Storybook Docs:
- References: [DocsPage](../docs/docspage.md) / [MDX](../docs/mdx.md) / [FAQ](../docs/faq.md) / [Recipes](../docs/recipes.md) / [Theming](../docs/theming.md)
- Vision: [Storybook Docs sneak peak](https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a)
- Announcement: [DocsPage](https://medium.com/storybookjs/storybook-docspage-e185bc3622bf)
- Example: [Storybook Design System](https://github.com/storybookjs/design-system)

View File

@ -1,4 +1,4 @@
import React, { FunctionComponent } from 'react';
import React, { FC } from 'react';
export const anchorBlockIdFromId = (storyId: string) => `anchor--${storyId}`;
@ -6,6 +6,6 @@ export interface AnchorProps {
storyId: string;
}
export const Anchor: FunctionComponent<AnchorProps> = ({ storyId, children }) => (
export const Anchor: FC<AnchorProps> = ({ storyId, children }) => (
<div id={anchorBlockIdFromId(storyId)}>{children}</div>
);

View File

@ -2,7 +2,7 @@ import React, { FunctionComponent, useContext } from 'react';
import { Description, DescriptionProps as PureDescriptionProps } from '@storybook/components';
import { DocsContext, DocsContextProps } from './DocsContext';
import { Component, CURRENT_SELECTION, DescriptionSlot } from './shared';
import { str } from '../lib/docgen/utils';
import { str } from '../lib/docgen';
export enum DescriptionType {
INFO = 'info',

View File

@ -1,44 +1,24 @@
import React, { FunctionComponent, useEffect } from 'react';
import { document } from 'global';
import { document, window } from 'global';
import { MDXProvider } from '@mdx-js/react';
import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming';
import { DocsWrapper, DocsContent, Source } from '@storybook/components';
import { components as htmlComponents, Code } from '@storybook/components/html';
import { DocsWrapper, DocsContent } from '@storybook/components';
import { components as htmlComponents } from '@storybook/components/html';
import { DocsContextProps, DocsContext } from './DocsContext';
import { anchorBlockIdFromId } from './Anchor';
import { storyBlockIdFromId } from './Story';
import { CodeOrSourceMdx, AnchorMdx, HeadersMdx } from './mdx';
import { scrollToElement } from './utils';
interface DocsContainerProps {
context: DocsContextProps;
}
interface CodeOrSourceProps {
className?: string;
}
export const CodeOrSource: FunctionComponent<CodeOrSourceProps> = props => {
const { className, children, ...rest } = props;
// markdown-to-jsx does not add className to inline code
if (
typeof className !== 'string' &&
(typeof children !== 'string' || !(children as string).match(/[\n\r]/g))
) {
return <Code>{children}</Code>;
}
// className: "lang-jsx"
const language = className && className.split('-');
return (
<Source
language={(language && language[1]) || 'plaintext'}
format={false}
code={children as string}
{...rest}
/>
);
};
const defaultComponents = {
...htmlComponents,
code: CodeOrSource,
code: CodeOrSourceMdx,
a: AnchorMdx,
...HeadersMdx,
};
export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context, children }) => {
@ -46,30 +26,45 @@ export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context,
const options = parameters.options || {};
const theme = ensureTheme(options.theme);
const { components: userComponents = null } = parameters.docs || {};
const components = { ...defaultComponents, ...userComponents };
const allComponents = { ...defaultComponents, ...userComponents };
useEffect(() => {
let element = document.getElementById(anchorBlockIdFromId(storyId));
if (!element) {
element = document.getElementById(storyBlockIdFromId(storyId));
let url;
try {
url = new URL(window.parent.location);
} catch (err) {
return;
}
if (element) {
const allStories = element.parentElement.querySelectorAll('[id|="anchor-"]');
let block = 'start';
if (allStories && allStories[0] === element) {
block = 'end'; // first story should be shown with the intro content above
if (url.hash) {
const element = document.getElementById(url.hash.substring(1));
if (element) {
// Introducing a delay to ensure scrolling works when it's a full refresh.
setTimeout(() => {
scrollToElement(element);
}, 200);
}
} else {
const element =
document.getElementById(anchorBlockIdFromId(storyId)) ||
document.getElementById(storyBlockIdFromId(storyId));
if (element) {
const allStories = element.parentElement.querySelectorAll('[id|="anchor-"]');
let block = 'start';
if (allStories && allStories[0] === element) {
block = 'end'; // first story should be shown with the intro content above
}
// Introducing a delay to ensure scrolling works when it's a full refresh.
setTimeout(() => {
scrollToElement(element, block);
}, 200);
}
element.scrollIntoView({
behavior: 'smooth',
block,
inline: 'nearest',
});
}
}, [storyId]);
return (
<DocsContext.Provider value={context}>
<ThemeProvider theme={theme}>
<MDXProvider components={components}>
<MDXProvider components={allComponents}>
<DocsWrapper className="sbdocs sbdocs-wrapper">
<DocsContent className="sbdocs sbdocs-content">{children}</DocsContent>
</DocsWrapper>

View File

@ -6,11 +6,12 @@ export interface DocsContextProps {
selectedStory?: string;
/**
* mdxStoryNameToId is an MDX-compiler-generated mapping of an MDX story's
* display name to its storyId. It's used internally by the `<Story>`
* doc block.
* mdxStoryNameToKey is an MDX-compiler-generated mapping of an MDX story's
* display name to its story key for ID generation. It's used internally by the `<Story>`
* and `Preview` doc blocks.
*/
mdxStoryNameToId?: Record<string, string>;
mdxStoryNameToKey?: Record<string, string>;
mdxComponentMeta?: any;
parameters?: any;
storyStore?: any;
forceRender?: () => void;

View File

@ -15,4 +15,10 @@ describe('defaultTitleSlot', () => {
expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('b');
expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('d');
});
it('empty options', () => {
const parameters = { options: {} };
expect(defaultTitleSlot({ selectedKind: 'a/b/c', parameters })).toBe('c');
expect(defaultTitleSlot({ selectedKind: 'a|b', parameters })).toBe('b');
expect(defaultTitleSlot({ selectedKind: 'a/b/c.d', parameters })).toBe('d');
});
});

View File

@ -1,7 +1,20 @@
import React, { FunctionComponent } from 'react';
import { H2 } from '@storybook/components/html';
import { HeaderMdx } from './mdx';
interface HeadingProps {
export interface HeadingProps {
children: JSX.Element | string;
disableAnchor?: boolean;
}
export const Heading: FunctionComponent<HeadingProps> = ({ children }) => <H2>{children}</H2>;
export const Heading: FunctionComponent<HeadingProps> = ({ children, disableAnchor }) => {
if (disableAnchor || typeof children !== 'string') {
return <H2>{children}</H2>;
}
const tagID = children.toLowerCase().replace(/[^a-z0-9]/gi, '-');
return (
<HeaderMdx as="h2" id={tagID}>
{children}
</HeaderMdx>
);
};

View File

@ -1,4 +1,8 @@
import { FunctionComponent } from 'react';
import React, { FC, useContext } from 'react';
import { document } from 'global';
import { Anchor } from './Anchor';
import { DocsContext, DocsContextProps } from './DocsContext';
import { getDocsStories } from './utils';
type Decorator = (...args: any) => any;
@ -9,9 +13,27 @@ interface MetaProps {
parameters?: any;
}
function getFirstStoryId(docsContext: DocsContextProps): string {
const stories = getDocsStories(docsContext);
return stories.length > 0 ? stories[0].id : null;
}
function renderAnchor() {
const context = useContext(DocsContext);
// eslint-disable-next-line react/destructuring-assignment
const anchorId = getFirstStoryId(context) || context.id;
return <Anchor storyId={anchorId} />;
}
/**
* This component is used to declare component metadata in docs
* and gets transformed into a default export underneath the hood.
* It doesn't actually render anything.
*/
export const Meta: FunctionComponent<MetaProps> = props => null;
export const Meta: FC<MetaProps> = () => {
const params = new URL(document.location).searchParams;
const isDocs = params.get('viewMode') === 'docs';
return isDocs ? renderAnchor() : null;
};

View File

@ -1,4 +1,5 @@
import React, { FunctionComponent, ReactElement, ReactNode, ReactNodeArray } from 'react';
import { toId, storyNameFromExport } from '@storybook/csf';
import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components';
import { getSourceProps } from './Source';
import { DocsContext, DocsContextProps } from './DocsContext';
@ -21,7 +22,7 @@ const getPreviewProps = (
children,
...props
}: PreviewProps & { children?: ReactNode },
{ mdxStoryNameToId, storyStore }: DocsContextProps
{ mdxStoryNameToKey, mdxComponentMeta, storyStore }: DocsContextProps
): PurePreviewProps => {
if (withSource === SourceState.NONE) {
return props;
@ -36,7 +37,14 @@ const getPreviewProps = (
const stories = childArray.filter(
(c: ReactElement) => c.props && (c.props.id || c.props.name)
) as ReactElement[];
const targetIds = stories.map(s => s.props.id || mdxStoryNameToId[s.props.name]);
const targetIds = stories.map(
s =>
s.props.id ||
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
storyNameFromExport(mdxStoryNameToKey[s.props.name])
)
);
const sourceProps = getSourceProps({ ids: targetIds }, { storyStore });
return {
...props, // pass through columns etc.

View File

@ -5,6 +5,8 @@ import {
PropsTable,
PropsTableError,
PropsTableProps,
PropsTableRowsProps,
PropsTableSectionsProps,
PropDef,
TabsState,
} from '@storybook/components';
@ -37,6 +39,9 @@ const inferPropsExtractor = (framework: string): PropsExtractor | null => {
}
};
const filterRows = (rows: PropDef[], exclude: string[]) =>
rows && rows.filter((row: PropDef) => !exclude.includes(row.name));
export const getComponentProps = (
component: Component,
{ exclude }: PropsProps,
@ -49,16 +54,25 @@ export const getComponentProps = (
const params = parameters || {};
const { framework = null } = params;
const { extractProps = inferPropsExtractor(framework) } = params.docs || {};
const { extractProps = inferPropsExtractor(framework) }: { extractProps: PropsExtractor } =
params.docs || {};
if (!extractProps) {
throw new Error(PropsTableError.PROPS_UNSUPPORTED);
}
let { rows } = extractProps(component);
let props = extractProps(component);
if (!isNil(exclude)) {
rows = rows.filter((row: PropDef) => !exclude.includes(row.name));
const { rows } = props as PropsTableRowsProps;
const { sections } = props as PropsTableSectionsProps;
if (rows) {
props = { rows: filterRows(rows, exclude) };
} else if (sections) {
Object.keys(sections).forEach(section => {
sections[section] = filterRows(sections[section], exclude);
});
}
}
return { rows };
return props;
} catch (err) {
return { error: err.message };
}

View File

@ -5,6 +5,7 @@ import { CURRENT_SELECTION } from './shared';
interface CommonProps {
language?: string;
dark?: boolean;
}
type SingleSourceProps = {
@ -76,7 +77,7 @@ export const getSourceProps = (
.join('\n\n');
}
return source
? { code: source, language: props.language || 'jsx' }
? { code: source, language: props.language || 'jsx', dark: props.dark || false }
: { error: SourceError.SOURCE_UNAVAILABLE };
};

View File

@ -17,7 +17,7 @@ export const Stories: FunctionComponent<StoriesProps> = ({ slot, title }) => {
const stories: DocsStoryProps[] = slot
? slot(componentStories, context)
: componentStories && componentStories.slice(1);
if (!stories) {
if (!stories || stories.length === 0) {
return null;
}
return (

View File

@ -2,6 +2,7 @@ import React, { createElement, ElementType, FunctionComponent, ReactNode } from
import { MDXProvider } from '@mdx-js/react';
import { components as docsComponents } from '@storybook/components/html';
import { Story, StoryProps as PureStoryProps } from '@storybook/components';
import { toId, storyNameFromExport } from '@storybook/csf';
import { CURRENT_SELECTION } from './shared';
import { DocsContext, DocsContextProps } from './DocsContext';
@ -40,12 +41,17 @@ const inferInlineStories = (framework: string): boolean => {
export const getStoryProps = (
props: StoryProps,
{ id: currentId, storyStore, parameters, mdxStoryNameToId }: DocsContextProps | null
{ id: currentId, storyStore, mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps | null
): PureStoryProps => {
const { id } = props as StoryRefProps;
const { name } = props as StoryDefProps;
const inputId = id === CURRENT_SELECTION ? currentId : id;
const previewId = inputId || mdxStoryNameToId[name];
const previewId =
inputId ||
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
storyNameFromExport(mdxStoryNameToKey[name])
);
const { height, inline } = props;
const data = storyStore.fromId(previewId);

View File

@ -1,7 +1,16 @@
import React, { FunctionComponent } from 'react';
import { H3 } from '@storybook/components/html';
import { HeaderMdx } from './mdx';
import { HeadingProps } from './Heading';
interface SubheadingProps {
children: JSX.Element | string;
}
export const Subheading: FunctionComponent<SubheadingProps> = ({ children }) => <H3>{children}</H3>;
export const Subheading: FunctionComponent<HeadingProps> = ({ children, disableAnchor }) => {
if (disableAnchor || typeof children !== 'string') {
return <H3>{children}</H3>;
}
const tagID = children.toLowerCase().replace(/[^a-z0-9]/gi, '-');
return (
<HeaderMdx as="h3" id={tagID}>
{children}
</HeaderMdx>
);
};

View File

@ -1,5 +1,5 @@
import React, { useContext, FunctionComponent } from 'react';
import { parseKind } from '@storybook/router';
import { parseKind } from '@storybook/csf';
import { Title as PureTitle } from '@storybook/components';
import { DocsContext } from './DocsContext';
import { StringSlot } from './shared';
@ -11,13 +11,9 @@ interface TitleProps {
export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
const {
showRoots,
hierarchyRootSeparator: rootSeparator,
hierarchySeparator: groupSeparator,
} = (parameters && parameters.options) || {
showRoots: undefined,
hierarchyRootSeparator: '|',
hierarchySeparator: /\/|\./,
};
hierarchyRootSeparator: rootSeparator = '|',
hierarchySeparator: groupSeparator = /\/|\./,
} = (parameters && parameters.options) || {};
let groups;
if (typeof showRoots !== 'undefined') {

View File

@ -20,6 +20,4 @@ export * from './Title';
export * from './Wrapper';
export * from './shared';
// helper function for MDX
export const makeStoryFn = (val: any) => (typeof val === 'function' ? val : () => val);
export * from './mdx';

View File

@ -0,0 +1,211 @@
import React, { FC, SyntheticEvent } from 'react';
import addons from '@storybook/addons';
import { Source } from '@storybook/components';
import { NAVIGATE_URL } from '@storybook/core-events';
import { Code, components } from '@storybook/components/html';
import { document } from 'global';
import { styled } from '@storybook/theming';
import { DocsContext, DocsContextProps } from './DocsContext';
// Hacky utility for asserting identifiers in MDX Story elements
export const assertIsFn = (val: any) => {
if (typeof val !== 'function') {
throw new Error(`Expected story function, got: ${val}`);
}
return val;
};
// Hacky utilty for adding mdxStoryToId to the default context
export const AddContext: FC<DocsContextProps> = props => {
const { children, ...rest } = props;
const parentContext = React.useContext(DocsContext);
return (
<DocsContext.Provider value={{ ...parentContext, ...rest }}>{children}</DocsContext.Provider>
);
};
interface CodeOrSourceMdxProps {
className?: string;
}
export const CodeOrSourceMdx: FC<CodeOrSourceMdxProps> = ({ className, children, ...rest }) => {
// markdown-to-jsx does not add className to inline code
if (
typeof className !== 'string' &&
(typeof children !== 'string' || !(children as string).match(/[\n\r]/g))
) {
return <Code>{children}</Code>;
}
// className: "lang-jsx"
const language = className && className.split('-');
return (
<Source
language={(language && language[1]) || 'plaintext'}
format={false}
code={children as string}
{...rest}
/>
);
};
function navigate(url: string) {
addons.getChannel().emit(NAVIGATE_URL, url);
}
// @ts-ignore
const A = components.a;
interface AnchorInPageProps {
hash: string;
}
const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => (
<A
href={hash}
target="_self"
onClick={(event: SyntheticEvent) => {
const id = hash.substring(1);
const element = document.getElementById(id);
if (element) {
navigate(hash);
}
}}
>
{children}
</A>
);
interface AnchorMdxProps {
href: string;
target: string;
}
export const AnchorMdx: FC<AnchorMdxProps> = props => {
const { href, target, children, ...rest } = props;
if (href) {
// Enable scrolling for in-page anchors.
if (href.startsWith('#')) {
return <AnchorInPage hash={href}>{children}</AnchorInPage>;
}
// Links to other pages of SB should use the base URL of the top level iframe instead of the base URL of the preview iframe.
if (target !== '_blank') {
return (
<A
href={href}
onClick={(event: SyntheticEvent) => {
event.preventDefault();
navigate(href);
}}
target={target}
{...rest}
>
{children}
</A>
);
}
}
// External URL dont need any modification.
return <A {...props} />;
};
const SUPPORTED_MDX_HEADERS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const OcticonHeaders = SUPPORTED_MDX_HEADERS.reduce(
(acc, headerType) => ({
...acc,
// @ts-ignore
[headerType]: styled(components[headerType])({
'& svg': {
visibility: 'hidden',
},
'&:hover svg': {
visibility: 'visible',
},
}),
}),
{}
);
const OcticonAnchor = styled.a(() => ({
float: 'left',
paddingRight: '4px',
marginLeft: '-20px',
}));
interface HeaderWithOcticonAnchorProps {
as: string;
id: string;
children: any;
}
const HeaderWithOcticonAnchor: FC<HeaderWithOcticonAnchorProps> = ({
as,
id,
children,
...rest
}) => {
// @ts-ignore
const OcticonHeader = OcticonHeaders[as];
const hash = `#${id}`;
return (
<OcticonHeader id={id} {...rest}>
<OcticonAnchor
aria-hidden="true"
href={hash}
tabIndex={-1}
target="_self"
onClick={(event: SyntheticEvent) => {
const element = document.getElementById(id);
if (element) {
navigate(hash);
}
}}
>
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path
fillRule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"
/>
</svg>
</OcticonAnchor>
{children}
</OcticonHeader>
);
};
interface HeaderMdxProps {
as: string;
id: string;
}
export const HeaderMdx: FC<HeaderMdxProps> = props => {
const { as, id, children, ...rest } = props;
// An id should have been added on every header by the "remark-slug" plugin.
if (id) {
return (
<HeaderWithOcticonAnchor as={as} id={id} {...rest}>
{children}
</HeaderWithOcticonAnchor>
);
}
// @ts-ignore
const Header = components[as];
// Make sure it still work if "remark-slug" plugin is not present.
return <Header {...props} />;
};
export const HeadersMdx = SUPPORTED_MDX_HEADERS.reduce(
(acc, headerType) => ({
...acc,
// @ts-ignore
[headerType]: (props: object) => <HeaderMdx as={headerType} {...props} />,
}),
{}
);

View File

@ -4,6 +4,11 @@ import { StoryData, Component } from './shared';
export const getDocsStories = (context: DocsContextProps): StoryData[] => {
const { storyStore, selectedKind } = context;
if (!storyStore) {
return [];
}
return storyStore
.getStoriesForKind(selectedKind)
.filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable));
@ -32,3 +37,11 @@ export const getComponentName = (component: Component): string => {
return component.name;
};
export function scrollToElement(element: any, block = 'start') {
element.scrollIntoView({
behavior: 'smooth',
block,
inline: 'nearest',
});
}

View File

@ -2,7 +2,7 @@
/* global window */
import { PropDef } from '@storybook/components';
import { Argument, CompodocJson, Component, Method, Property } from './types';
import { Argument, CompodocJson, Component, Method, Property, Directive } from './types';
type Sections = Record<string, PropDef[]>;
@ -18,7 +18,7 @@ export const setCompodocJson = (compodocJson: CompodocJson) => {
// @ts-ignore
export const getCompdocJson = (): CompodocJson => window.__STORYBOOK_COMPODOC_JSON__;
export const checkValidComponent = (component: Component) => {
export const checkValidComponentOrDirective = (component: Component | Directive) => {
if (!component.name) {
throw new Error(`Invalid component ${JSON.stringify(component)}`);
}
@ -71,15 +71,18 @@ const mapItemToSection = (key: string, item: Method | Property): string => {
}
};
const getComponentData = (component: Component) => {
const getComponentData = (component: Component | Directive) => {
if (!component) {
return null;
}
checkValidComponent(component);
checkValidComponentOrDirective(component);
const compodocJson = getCompdocJson();
checkValidCompodocJson(compodocJson);
const { name } = component;
return compodocJson.components.find((c: Component) => c.name === name);
return (
compodocJson.components.find((c: Component) => c.name === name) ||
compodocJson.directives.find((c: Directive) => c.name === name)
);
};
const displaySignature = (item: Method): string => {
@ -89,7 +92,7 @@ const displaySignature = (item: Method): string => {
return `(${args.join(', ')}) => ${item.returnType}`;
};
export const extractProps = (component: Component) => {
export const extractProps = (component: Component | Directive) => {
const componentData = getComponentData(component);
if (!componentData) {
return null;
@ -140,7 +143,7 @@ export const extractProps = (component: Component) => {
return isEmpty(sections) ? null : { sections };
};
export const extractComponentDescription = (component: Component) => {
export const extractComponentDescription = (component: Component | Directive) => {
const componentData = getComponentData(component);
if (!componentData) {
return null;

View File

@ -15,7 +15,7 @@ export interface Property {
description?: string;
}
export interface Component {
export interface Directive {
name: string;
propertiesClass: Property[];
inputsClass: Property[];
@ -24,6 +24,8 @@ export interface Component {
rawdescription: string;
}
export type Component = Directive;
export interface Argument {
name: string;
type: string;
@ -35,5 +37,6 @@ export interface Decorator {
}
export interface CompodocJson {
directives: Directive[];
components: Component[];
}

View File

@ -1 +0,0 @@
export * from '../../lib/docgen';

View File

@ -1,5 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */
import createCompiler from '@storybook/addon-docs/mdx-compiler-plugin';
import remarkSlug from 'remark-slug';
import remarkExternalLinks from 'remark-external-links';
function createBabelOptions(babelOptions?: any, configureJSX?: boolean) {
if (!configureJSX) {
@ -26,6 +28,10 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
sourceLoaderOptions = {},
} = options;
const mdxLoaderOptions = {
remarkPlugins: [remarkSlug, remarkExternalLinks],
};
// set `sourceLoaderOptions` to `null` to disable for manual configuration
const sourceLoader = sourceLoaderOptions
? [
@ -44,6 +50,18 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
...module,
rules: [
...(module.rules || []),
{
test: /\.js$/,
include: /node_modules\/acorn-jsx/,
use: [
{
loader: 'babel-loader',
options: {
presets: [[require.resolve('@babel/preset-env'), { modules: 'commonjs' }]],
},
},
],
},
{
test: /\.(stories|story).mdx$/,
use: [
@ -55,6 +73,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
loader: '@mdx-js/loader',
options: {
compilers: [createCompiler(options)],
...mdxLoaderOptions,
},
},
],
@ -69,6 +88,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
},
{
loader: '@mdx-js/loader',
options: mdxLoaderOptions,
},
],
},
@ -79,7 +99,7 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
return result;
}
export function addons(entry: any[] = [], options: any) {
export function managerEntries(entry: any[] = [], options: any) {
return [...entry, require.resolve('../../register')];
}

View File

@ -0,0 +1,11 @@
/* eslint-disable import/no-extraneous-dependencies */
import { addParameters } from '@storybook/client-api';
import { extractProps, extractComponentDescription } from './jsondoc';
addParameters({
docs: {
iframeHeight: 80,
extractProps,
extractComponentDescription,
},
});

View File

@ -0,0 +1 @@
export { setJSONDoc } from './jsondoc';

View File

@ -0,0 +1,30 @@
/* eslint-disable no-underscore-dangle */
/* global window */
export const setJSONDoc = jsondoc => {
window.__EMBER_GENERATED_DOC_JSON__ = jsondoc;
};
export const getJSONDoc = () => {
return window.__EMBER_GENERATED_DOC_JSON__;
};
export const extractProps = componentName => {
const json = getJSONDoc();
const componentDoc = json.included.find(doc => doc.attributes.name === componentName);
const rows = componentDoc.attributes.arguments.map(prop => {
return {
name: prop.name,
type: prop.type,
required: prop.tags.length ? prop.tags.some(tag => tag.name === 'required') : false,
defaultValue: prop.defaultValue,
description: prop.description,
};
});
return { rows };
};
export const extractComponentDescription = componentName => {
const json = getJSONDoc();
const componentDoc = json.included.find(doc => doc.attributes.name === componentName);
return componentDoc.attributes.description;
};

View File

@ -1,9 +1,10 @@
import PropTypes from 'prop-types';
import { isForwardRef, isMemo } from 'react-is';
import { PropDef } from '@storybook/components';
import { hasDocgen, extractPropsFromDocgen, PropsExtractor, TypeSystem } from '../../lib/docgen';
import { hasDocgen, extractComponentProps, PropsExtractor, TypeSystem } from '../../lib/docgen';
import { Component } from '../../blocks/shared';
import { enhancePropTypesProps } from './propTypes/handleProp';
import { enhanceTypeScriptProps } from './typeScript/handleProp';
export interface PropDefMap {
[p: string]: PropDef;
@ -32,16 +33,19 @@ function getPropDefs(component: Component, section: string): PropDef[] {
}
}
const extractedProps = extractPropsFromDocgen(processedComponent, section);
const extractedProps = extractComponentProps(processedComponent, section);
if (extractedProps.length === 0) {
return [];
}
if (extractedProps[0].typeSystem === TypeSystem.JAVASCRIPT) {
return enhancePropTypesProps(extractedProps, component);
switch (extractedProps[0].typeSystem) {
case TypeSystem.JAVASCRIPT:
return enhancePropTypesProps(extractedProps, component);
case TypeSystem.TYPESCRIPT:
return enhanceTypeScriptProps(extractedProps);
default:
return extractedProps.map(x => x.propDef);
}
return extractedProps.map(x => x.propDef);
}
export const extractProps: PropsExtractor = component => ({

View File

@ -1,79 +0,0 @@
export enum InspectionType {
IDENTIFIER = 'Identifier',
LITERAL = 'Literal',
OBJECT = 'Object',
ARRAY = 'Array',
FUNCTION = 'Function',
CLASS = 'Class',
ELEMENT = 'Element',
UNKNOWN = 'Unknown',
}
export interface BaseInspectionInferedType {
type: InspectionType;
}
// TODO: Fix this.
// export interface OptionalIdentifierInspectionType extends BaseInspectionInferedType {
// identifier?: string;
// }
// export interface RequiredIdentifierInspectionType extends BaseInspectionInferedType {
// identifier: string;
// }
// export type IdentifiableInspectionType =
// | OptionalIdentifierInspectionType
// | RequiredIdentifierInspectionType;
export interface InspectionIdentifier extends BaseInspectionInferedType {
type: InspectionType.IDENTIFIER;
identifier: string;
}
export interface InspectionLiteral extends BaseInspectionInferedType {
type: InspectionType.LITERAL;
}
export interface InspectionObject extends BaseInspectionInferedType {
type: InspectionType.OBJECT;
}
export interface InspectionArray extends BaseInspectionInferedType {
type: InspectionType.ARRAY;
}
export interface InspectionClass extends BaseInspectionInferedType {
type: InspectionType.CLASS;
identifier: string;
}
export interface InspectionFunction extends BaseInspectionInferedType {
type: InspectionType.FUNCTION;
identifier?: string;
hasArguments: boolean;
}
export interface InspectionElement extends BaseInspectionInferedType {
type: InspectionType.ELEMENT;
identifier?: string;
}
export interface InspectionUnknown extends BaseInspectionInferedType {
type: InspectionType.UNKNOWN;
}
export type InspectionInferedType =
| InspectionIdentifier
| InspectionLiteral
| InspectionObject
| InspectionArray
| InspectionClass
| InspectionFunction
| InspectionElement
| InspectionUnknown;
export interface InspectionResult {
inferedType: InspectionInferedType;
ast?: any;
}

View File

@ -0,0 +1,85 @@
import { isNil } from 'lodash';
import { PropDefaultValue } from '@storybook/components';
import { FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions';
import {
InspectionFunction,
InspectionResult,
InspectionType,
InspectionElement,
InspectionIdentifiableInferedType,
inspectValue,
} from '../inspection';
import { isHtmlTag } from '../isHtmlTag';
import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib';
import { generateCode } from '../generateCode';
import { generateObject } from './generateObject';
import { generateArray } from './generateArray';
import { getPrettyIdentifier } from './prettyIdentifier';
function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue {
const { identifier } = inferedType as InspectionFunction;
if (!isNil(identifier)) {
return createSummaryValue(
getPrettyIdentifier(inferedType as InspectionIdentifiableInferedType),
generateCode(ast)
);
}
const prettyCaption = generateCode(ast, true);
return !isTooLongForDefaultValueSummary(prettyCaption)
? createSummaryValue(prettyCaption)
: createSummaryValue(FUNCTION_CAPTION, generateCode(ast));
}
// All elements are JSX elements.
// JSX elements are not supported by escodegen.
function generateElement(
defaultValue: string,
inspectionResult: InspectionResult
): PropDefaultValue {
const { inferedType } = inspectionResult;
const { identifier } = inferedType as InspectionElement;
if (!isNil(identifier)) {
if (!isHtmlTag(identifier)) {
const prettyIdentifier = getPrettyIdentifier(
inferedType as InspectionIdentifiableInferedType
);
return createSummaryValue(
prettyIdentifier,
prettyIdentifier !== defaultValue ? defaultValue : undefined
);
}
}
return !isTooLongForDefaultValueSummary(defaultValue)
? createSummaryValue(defaultValue)
: createSummaryValue(ELEMENT_CAPTION, defaultValue);
}
export function createDefaultValue(defaultValue: string): PropDefaultValue {
try {
const inspectionResult = inspectValue(defaultValue);
switch (inspectionResult.inferedType.type) {
case InspectionType.OBJECT:
return generateObject(inspectionResult);
case InspectionType.FUNCTION:
return generateFunc(inspectionResult);
case InspectionType.ELEMENT:
return generateElement(defaultValue, inspectionResult);
case InspectionType.ARRAY:
return generateArray(inspectionResult);
default:
return null;
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
return null;
}

View File

@ -0,0 +1,189 @@
import { PropDefaultValue, PropDef } from '@storybook/components';
import { isNil, isPlainObject, isArray, isFunction, isString } from 'lodash';
// @ts-ignore
import reactElementToJSXString from 'react-element-to-jsx-string';
import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib';
import { inspectValue, InspectionFunction } from '../inspection';
import { generateObject } from './generateObject';
import { generateArray } from './generateArray';
import { getPrettyElementIdentifier, getPrettyFuncIdentifier } from './prettyIdentifier';
import { OBJECT_CAPTION, FUNCTION_CAPTION, ELEMENT_CAPTION } from '../captions';
import { isHtmlTag } from '../isHtmlTag';
export type TypeResolver = (rawDefaultProp: any, propDef: PropDef) => PropDefaultValue;
export interface TypeResolvers {
string: TypeResolver;
object: TypeResolver;
function: TypeResolver;
default: TypeResolver;
}
function isReactElement(element: any): boolean {
return !isNil(element.$$typeof);
}
export function extractFunctionName(func: Function, propName: string): string {
const { name } = func;
// Comparison with the prop name is to discard inferred function names.
if (name !== '' && name !== 'anoynymous' && name !== propName) {
return name;
}
return null;
}
const stringResolver: TypeResolver = rawDefaultProp => {
return createSummaryValue(rawDefaultProp);
};
function generateReactObject(rawDefaultProp: any) {
const { type } = rawDefaultProp;
const { displayName } = type;
const jsx = reactElementToJSXString(rawDefaultProp);
if (!isNil(displayName)) {
const prettyIdentifier = getPrettyElementIdentifier(displayName);
return createSummaryValue(prettyIdentifier, prettyIdentifier !== jsx ? jsx : undefined);
}
if (isString(type)) {
// This is an HTML element.
if (isHtmlTag(type)) {
const jsxCompact = reactElementToJSXString(rawDefaultProp, { tabStop: 0 });
const jsxSummary = jsxCompact.replace(/\r?\n|\r/g, '');
if (!isTooLongForDefaultValueSummary(jsxSummary)) {
return createSummaryValue(jsxSummary);
}
}
}
return createSummaryValue(ELEMENT_CAPTION, jsx);
}
const objectResolver: TypeResolver = rawDefaultProp => {
if (isReactElement(rawDefaultProp) && !isNil(rawDefaultProp.type)) {
return generateReactObject(rawDefaultProp);
}
if (isPlainObject(rawDefaultProp)) {
const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp));
return generateObject(inspectionResult);
}
if (isArray(rawDefaultProp)) {
const inspectionResult = inspectValue(JSON.stringify(rawDefaultProp));
return generateArray(inspectionResult);
}
return createSummaryValue(OBJECT_CAPTION);
};
const functionResolver: TypeResolver = (rawDefaultProp, propDef) => {
let isElement = false;
let inspectionResult;
// Try to display the name of the component. The body of the component is ommited since the code has been transpiled.
if (isFunction(rawDefaultProp.render)) {
isElement = true;
} else if (!isNil(rawDefaultProp.prototype) && isFunction(rawDefaultProp.prototype.render)) {
isElement = true;
} else {
let innerElement;
try {
inspectionResult = inspectValue(rawDefaultProp.toString());
const { hasParams, params } = inspectionResult.inferedType as InspectionFunction;
if (hasParams) {
// It might be a functional component accepting props.
if (params.length === 1 && params[0].type === 'ObjectPattern') {
innerElement = rawDefaultProp({});
}
} else {
innerElement = rawDefaultProp();
}
if (!isNil(innerElement)) {
if (isReactElement(innerElement)) {
isElement = true;
}
}
} catch (e) {
// do nothing.
}
}
const funcName = extractFunctionName(rawDefaultProp, propDef.name);
if (!isNil(funcName)) {
if (isElement) {
return createSummaryValue(getPrettyElementIdentifier(funcName));
}
if (!isNil(inspectionResult)) {
inspectionResult = inspectValue(rawDefaultProp.toString());
}
const { hasParams } = inspectionResult.inferedType as InspectionFunction;
return createSummaryValue(getPrettyFuncIdentifier(funcName, hasParams));
}
return createSummaryValue(isElement ? ELEMENT_CAPTION : FUNCTION_CAPTION);
};
const defaultResolver: TypeResolver = rawDefaultProp => {
return createSummaryValue(rawDefaultProp.toString());
};
const DEFAULT_TYPE_RESOLVERS: TypeResolvers = {
string: stringResolver,
object: objectResolver,
function: functionResolver,
default: defaultResolver,
};
export function createTypeResolvers(customResolvers: Partial<TypeResolvers> = {}): TypeResolvers {
return {
...DEFAULT_TYPE_RESOLVERS,
...customResolvers,
};
}
// When react-docgen cannot provide a defaultValue we take it from the raw defaultProp.
// It works fine for types that are not transpiled. For the types that are transpiled, we can only provide partial support.
// This means that:
// - The detail might not be available.
// - Identifiers might not be "prettified" for all the types.
export function createDefaultValueFromRawDefaultProp(
rawDefaultProp: any,
propDef: PropDef,
typeResolvers: TypeResolvers = DEFAULT_TYPE_RESOLVERS
): PropDefaultValue {
try {
// Keep the extra () otherwise it will fail for functions.
// eslint-disable-next-line prettier/prettier
switch (typeof (rawDefaultProp)) {
case 'string':
return typeResolvers.string(rawDefaultProp, propDef);
case 'object':
return typeResolvers.object(rawDefaultProp, propDef);
case 'function': {
return typeResolvers.function(rawDefaultProp, propDef);
}
default:
return typeResolvers.default(rawDefaultProp, propDef);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
return null;
}

View File

@ -0,0 +1,19 @@
import { PropDefaultValue } from '@storybook/components';
import { ARRAY_CAPTION } from '../captions';
import { InspectionResult, InspectionArray } from '../inspection';
import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib';
import { generateArrayCode } from '../generateCode';
export function generateArray({ inferedType, ast }: InspectionResult): PropDefaultValue {
const { depth } = inferedType as InspectionArray;
if (depth <= 2) {
const compactArray = generateArrayCode(ast, true);
if (!isTooLongForDefaultValueSummary(compactArray)) {
return createSummaryValue(compactArray);
}
}
return createSummaryValue(ARRAY_CAPTION, generateArrayCode(ast));
}

View File

@ -0,0 +1,19 @@
import { PropDefaultValue } from '@storybook/components';
import { OBJECT_CAPTION } from '../captions';
import { InspectionResult, InspectionArray } from '../inspection';
import { createSummaryValue, isTooLongForDefaultValueSummary } from '../../../../lib';
import { generateObjectCode } from '../generateCode';
export function generateObject({ inferedType, ast }: InspectionResult): PropDefaultValue {
const { depth } = inferedType as InspectionArray;
if (depth === 1) {
const compactObject = generateObjectCode(ast, true);
if (!isTooLongForDefaultValueSummary(compactObject)) {
return createSummaryValue(compactObject);
}
}
return createSummaryValue(OBJECT_CAPTION, generateObjectCode(ast));
}

View File

@ -0,0 +1,2 @@
export * from './createDefaultValue';
export * from './createFromRawDefaultProp';

View File

@ -0,0 +1,26 @@
import {
InspectionIdentifiableInferedType,
InspectionFunction,
InspectionType,
} from '../inspection';
export function getPrettyIdentifier(inferedType: InspectionIdentifiableInferedType): string {
const { type, identifier } = inferedType;
switch (type) {
case InspectionType.FUNCTION:
return getPrettyFuncIdentifier(identifier, (inferedType as InspectionFunction).hasParams);
case InspectionType.ELEMENT:
return getPrettyElementIdentifier(identifier);
default:
return identifier;
}
}
export function getPrettyFuncIdentifier(identifier: string, hasArguments: boolean): string {
return hasArguments ? `${identifier}( ... )` : `${identifier}()`;
}
export function getPrettyElementIdentifier(identifier: string) {
return `<${identifier} />`;
}

View File

@ -0,0 +1,70 @@
import { generate } from 'escodegen';
import dedent from 'ts-dedent';
const BASIC_OPTIONS = {
format: {
indent: {
style: ' ',
},
semicolons: false,
},
};
const COMPACT_OPTIONS = {
...BASIC_OPTIONS,
format: {
newline: '',
},
};
const PRETTY_OPTIONS = {
...BASIC_OPTIONS,
};
export function generateCode(ast: any, compact = false): string {
return generate(ast, compact ? COMPACT_OPTIONS : PRETTY_OPTIONS);
}
export function generateObjectCode(ast: any, compact = false): string {
return !compact ? generateCode(ast) : generateCompactObjectCode(ast);
}
function generateCompactObjectCode(ast: any): string {
let result = generateCode(ast, true);
// Cannot get escodegen to add a space before the last } with the compact mode settings.
// Fix it until a better solution is found.
if (!result.endsWith(' }')) {
result = `${result.slice(0, -1)} }`;
}
return result;
}
export function generateArrayCode(ast: any, compact = false): string {
return !compact ? generateMultilineArrayCode(ast) : generateCompactArrayCode(ast);
}
function generateMultilineArrayCode(ast: any): string {
let result = generateCode(ast);
// escodegen add extra spacing before the closing bracket of a multile line array with a nested object.
// Fix it until a better solution is found.
if (result.endsWith(' }]')) {
result = dedent(result);
}
return result;
}
function generateCompactArrayCode(ast: any): string {
let result = generateCode(ast, true);
// escodegen add extra an extra before the opening bracket of a compact array that contains primitive values.
// Fix it until a better solution is found.
if (result.startsWith('[ ')) {
result = result.replace('[ ', '[');
}
return result;
}

View File

@ -0,0 +1,3 @@
export * from './captions';
export * from './isHtmlTag';
export * from './generateCode';

View File

@ -67,14 +67,34 @@ describe('parse', () => {
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
it('support deep PropTypes.shape', () => {
const result = parse('PropTypes.shape({ foo: PropTypes.shape({ bar: PropTypes.string }) })');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(2);
expect(result.ast).toBeDefined();
});
it('support shape', () => {
const result = parse('shape({ foo: PropTypes.string })');
const result = parse('shape({ foo: string })');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
it('support deep shape', () => {
const result = parse('shape({ foo: shape({ bar: string }) })');
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(2);
expect(result.ast).toBeDefined();
});
@ -83,6 +103,7 @@ describe('parse', () => {
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
@ -95,6 +116,25 @@ describe('parse', () => {
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
it('support deep object literal', () => {
const result = parse(`
{
foo: {
hey: PropTypes.string
},
bar: PropTypes.string,
hey: {
ho: PropTypes.string
}
}`);
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(2);
expect(result.ast).toBeDefined();
});
@ -103,6 +143,7 @@ describe('parse', () => {
const inferedType = result.inferedType as InspectionObject;
expect(inferedType.type).toBe(InspectionType.OBJECT);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
@ -111,6 +152,16 @@ describe('parse', () => {
const inferedType = result.inferedType as InspectionArray;
expect(inferedType.type).toBe(InspectionType.ARRAY);
expect(inferedType.depth).toBe(1);
expect(result.ast).toBeDefined();
});
it('support deep array', () => {
const result = parse("['bottom-left', { foo: string }, [['hey', 'ho']]]");
const inferedType = result.inferedType as InspectionArray;
expect(inferedType.type).toBe(InspectionType.ARRAY);
expect(inferedType.depth).toBe(3);
expect(result.ast).toBeDefined();
});
@ -129,7 +180,8 @@ describe('parse', () => {
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBeUndefined();
expect(inferedType.hasArguments).toBeFalsy();
expect(inferedType.hasParams).toBeFalsy();
expect(inferedType.params.length).toBe(0);
expect(result.ast).toBeDefined();
});
@ -139,7 +191,8 @@ describe('parse', () => {
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBeUndefined();
expect(inferedType.hasArguments).toBeTruthy();
expect(inferedType.hasParams).toBeTruthy();
expect(inferedType.params.length).toBe(2);
expect(result.ast).toBeDefined();
});
@ -149,7 +202,8 @@ describe('parse', () => {
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBe('concat');
expect(inferedType.hasArguments).toBeFalsy();
expect(inferedType.hasParams).toBeFalsy();
expect(inferedType.params.length).toBe(0);
expect(result.ast).toBeDefined();
});
@ -159,7 +213,8 @@ describe('parse', () => {
expect(inferedType.type).toBe(InspectionType.FUNCTION);
expect(inferedType.identifier).toBe('concat');
expect(inferedType.hasArguments).toBeTruthy();
expect(inferedType.hasParams).toBeTruthy();
expect(inferedType.params.length).toBe(2);
expect(result.ast).toBeDefined();
});

View File

@ -7,7 +7,6 @@ import estree from 'estree';
import * as acornWalk from 'acorn-walk';
import {
InspectionType,
InspectionInferedType,
InspectionLiteral,
InspectionElement,
InspectionFunction,
@ -16,6 +15,7 @@ import {
InspectionUnknown,
InspectionIdentifier,
InspectionArray,
InspectionInferedType,
} from './types';
interface ParsingResult<T> {
@ -35,6 +35,29 @@ function extractIdentifierName(identifierNode: any) {
return !isNil(identifierNode) ? identifierNode.name : null;
}
function filterAncestors(ancestors: estree.Node[]): estree.Node[] {
return ancestors.filter(x => x.type === 'ObjectExpression' || x.type === 'ArrayExpression');
}
function calculateNodeDepth(node: estree.Expression): number {
const depths: number[] = [];
acornWalk.ancestor(
node,
{
ObjectExpression(_: any, ancestors: estree.Node[]) {
depths.push(filterAncestors(ancestors).length);
},
ArrayExpression(_: any, ancestors: estree.Node[]) {
depths.push(filterAncestors(ancestors).length);
},
},
ACORN_WALK_VISITORS
);
return Math.max(...depths);
}
function parseIdentifier(identifierNode: estree.Identifier): ParsingResult<InspectionIdentifier> {
return {
inferedType: {
@ -72,7 +95,8 @@ function parseFunction(
const inferedType: InspectionFunction | InspectionElement = {
type: isJsx ? InspectionType.ELEMENT : InspectionType.FUNCTION,
hasArguments: funcNode.params.length !== 0,
params: funcNode.params,
hasParams: funcNode.params.length !== 0,
};
const identifierName = extractIdentifierName((funcNode as estree.FunctionExpression).id);
@ -135,10 +159,7 @@ function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObj
const identifierName = extractIdentifierName(identifierNode);
if (identifierName === 'shape') {
return {
inferedType: { type: InspectionType.OBJECT },
ast: callNode.arguments[0],
};
return parseObject(callNode.arguments[0] as estree.ObjectExpression);
}
return null;
@ -146,14 +167,14 @@ function parseCall(callNode: estree.CallExpression): ParsingResult<InspectionObj
function parseObject(objectNode: estree.ObjectExpression): ParsingResult<InspectionObject> {
return {
inferedType: { type: InspectionType.OBJECT },
inferedType: { type: InspectionType.OBJECT, depth: calculateNodeDepth(objectNode) },
ast: objectNode,
};
}
function parseArray(arrayNode: estree.ArrayExpression): ParsingResult<InspectionArray> {
return {
inferedType: { type: InspectionType.ARRAY },
inferedType: { type: InspectionType.ARRAY, depth: calculateNodeDepth(arrayNode) },
ast: arrayNode,
};
}

Some files were not shown because too many files have changed in this diff Show More