mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:01:48 +08:00
Merge branch 'next' into pr/fraincs/8897
This commit is contained in:
commit
ae27b715f0
@ -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: [
|
||||
|
@ -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
|
||||
|
@ -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
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
.yarn/releases/yarn-*.js linguist-generated=true
|
1
.github/autolabeler.yml
vendored
1
.github/autolabeler.yml
vendored
@ -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/**"]
|
||||
|
3
.github/automention.yml
vendored
3
.github/automention.yml
vendored
@ -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
32
.github/workflows/tests-puppeteer.yml
vendored
Normal 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
3
.gitignore
vendored
@ -2,7 +2,8 @@ node_modules
|
||||
*.log
|
||||
.idea
|
||||
*.iml
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
*.sw*
|
||||
npm-shrinkwrap.json
|
||||
dist
|
||||
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
.yarn
|
38
.vscode/launch.json
vendored
Normal file
38
.vscode/launch.json
vendored
Normal 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>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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>)
|
||||
|
560
CHANGELOG.md
560
CHANGELOG.md
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
25
HISTORY.md
25
HISTORY.md
@ -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'
|
||||
```
|
111
MIGRATION.md
111
MIGRATION.md
@ -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 }),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
@ -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/) | [](app/vue) |
|
||||
| [Angular](app/angular) | [v5.1.0](https://storybooks-angular.netlify.com/) | [](app/angular) |
|
||||
| [Polymer](app/polymer) | [v5.1.0](https://storybooks-polymer.netlify.com/) | [](app/polymer) |
|
||||
| [Marionette.js](app/marionette) | - | [](app/marionette) |
|
||||
| [Mithril](app/mithril) | [v5.1.0](https://storybooks-mithril.netlify.com/) | [](app/mithril) |
|
||||
| [Marko](app/marko) | [v5.1.0](https://storybooks-marko.netlify.com/) | [](app/marko) |
|
||||
| [HTML](app/html) | [v5.1.0](https://storybooks-html.netlify.com/) | [](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/) | [](app/riot) |
|
||||
| [Ember](app/ember) | [v5.1.0](https://storybooks-ember.netlify.com/) | [](app/ember) |
|
||||
| [Preact](app/preact) | [v5.1.0](https://storybooks-preact.netlify.com/) | [](app/preact) |
|
||||
| [Rax](app/rax) | [v5.1.0](https://storybooks-rax.netlify.com/) | [](app/rax) |
|
||||
|
||||
### Sub Projects
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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 },
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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 },
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
}));
|
||||
|
@ -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
@ -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 };
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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/**/*"
|
||||
|
@ -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>
|
||||
);
|
||||
```
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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',
|
||||
}));
|
||||
|
||||
|
@ -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 },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
4
addons/contexts/rax.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { withContexts } from './dist/preview/frameworks/rax';
|
||||
|
||||
export { withContexts };
|
||||
export default withContexts;
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
14
addons/contexts/src/preview/frameworks/rax.ts
Normal file
14
addons/contexts/src/preview/frameworks/rax.ts
Normal 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);
|
@ -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 />
|
||||
);
|
||||
```
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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 doesn’t show up the way I’d expect in the Canvas”
|
||||
@ -37,7 +37,7 @@ For example, the following MDX story:
|
||||
Shows up in the dev tools as follows:
|
||||
|
||||
<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.
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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
150
addons/docs/ember/README.md
Normal 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)
|
1
addons/docs/ember/index.js
Normal file
1
addons/docs/ember/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('../dist/frameworks/ember');
|
@ -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
120
addons/docs/react/README.md
Normal 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)
|
@ -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>
|
||||
);
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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') {
|
||||
|
@ -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';
|
||||
|
211
addons/docs/src/blocks/mdx.tsx
Normal file
211
addons/docs/src/blocks/mdx.tsx
Normal 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} />,
|
||||
}),
|
||||
{}
|
||||
);
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
export * from '../../lib/docgen';
|
@ -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')];
|
||||
}
|
||||
|
||||
|
11
addons/docs/src/frameworks/ember/config.js
Normal file
11
addons/docs/src/frameworks/ember/config.js
Normal 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,
|
||||
},
|
||||
});
|
1
addons/docs/src/frameworks/ember/index.js
Normal file
1
addons/docs/src/frameworks/ember/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { setJSONDoc } from './jsondoc';
|
30
addons/docs/src/frameworks/ember/jsondoc.js
Normal file
30
addons/docs/src/frameworks/ember/jsondoc.js
Normal 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;
|
||||
};
|
@ -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 => ({
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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));
|
||||
}
|
@ -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));
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export * from './createDefaultValue';
|
||||
export * from './createFromRawDefaultProp';
|
@ -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} />`;
|
||||
}
|
70
addons/docs/src/frameworks/react/lib/generateCode.ts
Normal file
70
addons/docs/src/frameworks/react/lib/generateCode.ts
Normal 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;
|
||||
}
|
3
addons/docs/src/frameworks/react/lib/index.ts
Normal file
3
addons/docs/src/frameworks/react/lib/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './captions';
|
||||
export * from './isHtmlTag';
|
||||
export * from './generateCode';
|
@ -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();
|
||||
});
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user