Merge branch 'next' into pr/baraalex/7207

This commit is contained in:
Kai Roeder 2019-07-27 00:08:36 +02:00
commit b2682d8bf5
931 changed files with 23342 additions and 43488 deletions

View File

@ -54,7 +54,6 @@ module.exports = {
},
{
test: './lib',
exclude: './addons/storysource/src/loader',
presets: [
['@babel/preset-env', { shippedProposals: true, useBuiltIns: 'usage', corejs: '3' }],
'@babel/preset-react',
@ -75,14 +74,11 @@ module.exports = {
},
{
test: [
'./lib/core/src/server',
'./lib/node-logger',
'./lib/codemod',
'./lib/source-loader/src',
'./addons/storyshots',
'./addons/storysource/src/loader',
'./app/**/src/server/**',
'./app/**/src/bin/**',
'**/src/server/**',
'**/src/bin/**',
],
presets: [
[

View File

@ -321,29 +321,6 @@ jobs:
- run:
name: Upload coverage
command: yarn coverage
cli-test:
<<: *defaults
environment:
BASH_ENV: ~/.bashrc
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Test
command: yarn test --cli
no_output_timeout: 1800
cli-test-latest-cra:
<<: *defaults
environment:
BASH_ENV: ~/.bashrc
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Test CLI with latest CR(N)A
command: yarn test-latest-cra
workflows:
version: 2
build_test_deploy:
@ -373,12 +350,6 @@ workflows:
- coverage:
requires:
- test
- cli-test:
requires:
- build
- cli-test-latest-cra:
requires:
- build
- chromatic:
requires:
- examples

View File

@ -7,9 +7,11 @@ docs/public
storybook-static
built-storybooks
lib/cli/test
lib/codemod/src/transforms/__testfixtures__
scripts/storage
*.bundle.js
*.js.map
*.d.ts
!.remarkrc.js
!.babelrc.js
@ -18,7 +20,3 @@ scripts/storage
!.jest.config.js
!.storybook
REACT_NATIVE
examples-native
react-native
ondevice-*

View File

@ -35,7 +35,12 @@ module.exports = {
settings: {
'import/core-modules': ['enzyme'],
'import/ignore': ['node_modules\\/(?!@storybook)'],
'import/resolver': { node: { extensions: ['.js', '.ts', '.tsx', '.mjs'] } },
'import/resolver': {
node: {
extensions: ['.js', '.ts', '.tsx', '.mjs', '.d.ts'],
paths: ['node_modules/', 'node_modules/@types/'],
},
},
'html/html-extensions': ['.html'],
},
rules: {
@ -135,7 +140,13 @@ module.exports = {
],
'no-underscore-dangle': [
error,
{ allow: ['__STORYBOOK_CLIENT_API__', '__STORYBOOK_ADDONS_CHANNEL__'] },
{
allow: [
'__STORYBOOK_CLIENT_API__',
'__STORYBOOK_ADDONS_CHANNEL__',
'__STORYBOOK_STORY_STORE__',
],
},
],
'@typescript-eslint/no-var-requires': ignore,
'@typescript-eslint/camelcase': ignore,

View File

@ -24,12 +24,7 @@ If applicable, add screenshots to help explain your problem.
If applicable, add code samples to help explain your problem.
**System:**
- OS: [e.g. iOS, Windows10, MacOS]
- Device: [e.g. iPhoneX, Macbook Pro 2018]
- Browser: [e.g. chrome, safari]
- Framework: [e.g. react, vue, angular]
- Addons: [if relevant]
- Version: [e.g. 4.0.0]
Please paste the results of `npx -p @storybook/cli@next sb info` here.
**Additional context**
Add any other context about the problem here.

View File

@ -5,7 +5,7 @@
'app: polymer': ['stijnkoopal', 'ndelangen']
'app: preact': ['BartWaardenburg']
'app: react-native': ['benoitdion', 'gongreg']
'app: react-native-server': ['benoitdion', 'igor-dv']
'app: react-native-server': ['benoitdion', 'gongreg']
'app: svelte': ['cam-stitt', 'plumpNation']
'app: vue': ['backbone87', 'elevatebart', 'pksunkara']
'api: addons': ['ndelangen']
@ -15,10 +15,11 @@
'addon: info': ['shilman', 'elevatebart']
'addon: knobs': ['leoyli', 'Armanio']
'addon: storysource': ['igor-dv', 'libetl']
typescript: ['kroeder', 'gaetanmaisse', 'ndelangen']
typescript: ['kroeder', 'gaetanmaisse', 'ndelangen', 'emilio-martinez']
theming: ['ndelangen', 'domyen']
cra: ['mrmckeb']
cli: ['Keraito']
dependencies: ['ndelangen']
'babel / webpack': ['ndelangen', 'igor-dv', 'shilman']
'yarn / npm': ['ndelangen', 'tmeasday']
'source-loader': ['libetl']

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
node_modules
*.log
.idea
*.iml
.vscode
*.sw*
npm-shrinkwrap.json

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
language: node_js
node_js:
- "8"
cache:
yarn: true
install:
- yarn install
- yarn bootstrap --core
script:
jobs:
include:
- script: travis_wait 30 yarn test --cli
- script: travis_wait 30 yarn test-latest-cra

View File

@ -1,3 +1,367 @@
## 5.2.0-beta.10 (July 26, 2019)
This is a breaking release that undoes an unintentional breaking change introduced in 5.1.0 (and will also be released as a 5.1.x patch)
### Breaking Changes
* Core: Remove project root `babel.config.js` loading ([#7573](https://github.com/storybookjs/storybook/pull/7573))
### Features
* React: Add hooks support to stories ([#7571](https://github.com/storybookjs/storybook/pull/7571))
### Bug Fixes
* UI: Fix attribute warning on shortcut button click ([#7548](https://github.com/storybookjs/storybook/pull/7548))
## 5.2.0-beta.9 (July 26, 2019)
### Bug Fixes
* Angular: Fix automatic module metadata extraction for forRoot imports ([#7224](https://github.com/storybookjs/storybook/pull/7224))
### Maintenance
* Rename "Module" to Component Story Format ([#7564](https://github.com/storybookjs/storybook/pull/7564))
## 5.2.0-beta.8 (July 25, 2019)
### Breaking Changes
`source-loader` is now part of `addon-docs` preset. If you're using both the `addon-docs` preset and `source-loader` in your project, you need to update. You can remove `source-loader` and let the preset take care of it. Alternatively, you can disable `source-loader` in the preset by setting `sourceLoaderOptions` to `null`.
* Addon-docs: Add source-loader to preset ([#7547](https://github.com/storybookjs/storybook/pull/7547))
* Core: Don't allow duplicate titles ([#7542](https://github.com/storybookjs/storybook/pull/7542))
### Bug Fixes
* Addon-storysource: Add source-loader dep to avoid breaking change ([#7554](https://github.com/storybookjs/storybook/pull/7554))
* Addon-contexts: Ensure nodes is Array ([#7393](https://github.com/storybookjs/storybook/pull/7393))
### Maintenance
* Angular: Log angular cli config errors ([#7484](https://github.com/storybookjs/storybook/pull/7484))
## 5.2.0-beta.7 (July 23, 2019)
### Features
* Addon-docs: MDX function stories ([#7529](https://github.com/storybookjs/storybook/pull/7529))
* CLI: update `sb init` to module format for Ember/Marko/Mithril/Rax/Riot/Svelte ([#7504](https://github.com/storybookjs/storybook/pull/7504))
* CLI: update `sb init` to module format for Angular ([#7502](https://github.com/storybookjs/storybook/pull/7502))
* CLI: update `sb init` to module format for React ([#7500](https://github.com/storybookjs/storybook/pull/7500))
### Bug Fixes
* Ondevice-knobs: Graceful fail on missing default ([#7533](https://github.com/storybookjs/storybook/pull/7533))
### Maintenance
* Build: Attempt to fix travis timeouts ([#7531](https://github.com/storybookjs/storybook/pull/7531))
## 5.2.0-beta.6 (July 23, 2019)
### Breaking Changes
It is now recommended to only use ONE `load` call in your app and it will warn you if you call it more than once. The examples and docs have been updated to reflect this. Technically this is not a breaking change since the old API is supported, but it's a change in usage from previous versions.
### Features
* Core: Top-level components in MDX/Module formats ([#7524](https://github.com/storybookjs/storybook/pull/7524))
* Core: Module format `load` accept loader function ([#7518](https://github.com/storybookjs/storybook/pull/7518))
### Bug Fixes
* Addon-centered: Fix component disappearing on zoom ([#7400](https://github.com/storybookjs/storybook/pull/7400))
### Maintenance
* Addon-knobs: enable Typescript `strict` flag ([#7515](https://github.com/storybookjs/storybook/pull/7515))
## 5.2.0-beta.5 (July 21, 2019)
### Features
* CLI: update `sb init` to module format for Vue ([#7501](https://github.com/storybookjs/storybook/pull/7501))
* CLI: update `sb init` to module format for HTML/Polymer ([#7503](https://github.com/storybookjs/storybook/pull/7503))
### Bug Fixes
* Source-loader: Separate server and client code for IE support ([#7510](https://github.com/storybookjs/storybook/pull/7510))
## 5.2.0-beta.4 (July 20, 2019)
### Breaking Changes
* Core: Module format story decorators ([#7490](https://github.com/storybookjs/storybook/pull/7490))
### Features
* Addon-jest: UI Redesign ([#7424](https://github.com/storybookjs/storybook/pull/7424))
* Marko: support rerendering ([#7460](https://github.com/storybookjs/storybook/pull/7460))
### Bug Fixes
* Addon-docs: Fix MDX source string escaping ([#7497](https://github.com/storybookjs/storybook/pull/7497))
### Dependency Upgrades
* Marko: Upgrade loader & config ([#7459](https://github.com/storybookjs/storybook/pull/7459))
* Update core-js in addon-ondevice-actions package.json ([#7491](https://github.com/storybookjs/storybook/pull/7491))
## 5.2.0-beta.3 (July 19, 2019)
### Features
* React-native: Add storyId as testID ([#7482](https://github.com/storybookjs/storybook/pull/7482))
### Bug Fixes
* React-native: On-device knobs input fixes ([#7475](https://github.com/storybookjs/storybook/pull/7475))
* React-native: Fix crna-kitchen-sink ([#7200](https://github.com/storybookjs/storybook/pull/7200))
## 5.2.0-beta.2 (July 18, 2019)
### Features
* CLI: convert mdx to module format ([#7419](https://github.com/storybookjs/storybook/pull/7419))
* CLI: sb migrate npm & typescript support ([#7463](https://github.com/storybookjs/storybook/pull/7463))
* Addon-Docs: HTML support & example ([#7454](https://github.com/storybookjs/storybook/pull/7454))
### Bug Fixes
* Convert-storiesof-to-module: user exports, collisions, reserved keywords ([#7471](https://github.com/storybookjs/storybook/pull/7471))
* React-native: On-device knobs fixes ([#7470](https://github.com/storybookjs/storybook/pull/7470))
### Dependency Upgrades
* Addon-docs: Upgrade MDX to 1.1 ([#7476](https://github.com/storybookjs/storybook/pull/7476))
## 5.2.0-beta.1 (July 18, 2019)
### Features
* React native: Emit event when story is rendered ([#7449](https://github.com/storybookjs/storybook/pull/7449))
### Bug Fixes
* Addon-knobs: improve types via generics and readonlyarray ([#7411](https://github.com/storybookjs/storybook/pull/7411))
* Ondevice-backgrounds: use same param key as addon-backgrounds ([#7437](https://github.com/storybookjs/storybook/pull/7437))
## 5.2.0-beta.0 (July 15, 2019)
### Features
* Codemod: Convert module format to MDX ([#7418](https://github.com/storybookjs/storybook/pull/7418))
## 5.2.0-alpha.44 (July 15, 2019)
### Features
* CLI: Add migrate command ([#7414](https://github.com/storybookjs/storybook/pull/7414))
### Bug Fixes
* UI: Fix Panel rendered wrong at Docs-page ([#7327](https://github.com/storybookjs/storybook/pull/7327))
### Maintenance
* Typescript: Fix types of client-api & storystore ([#7337](https://github.com/storybookjs/storybook/pull/7337))
## 5.2.0-alpha.43 (July 13, 2019)
### Bug Fixes
* Addon-analytics: Fix API signature ([#7410](https://github.com/storybookjs/storybook/pull/7410))
* Addon-knobs: fix knobs function return types ([#7391](https://github.com/storybookjs/storybook/pull/7391))
* UI: Fix proptype for isToolshown ([#7405](https://github.com/storybookjs/storybook/pull/7405))
* UI: Fix propType warnings ([#7408](https://github.com/storybookjs/storybook/pull/7408))
### Maintenance
* Addon-actions: Use v4 UUID instead of v1 for action IDs ([#7397](https://github.com/storybookjs/storybook/pull/7397))
* UI: Remove recompose ([#7385](https://github.com/storybookjs/storybook/pull/7385))
* UI: FIX & IMPROVE styling interop of addon-background & addon-viewport ([#7385](https://github.com/storybookjs/storybook/pull/7385))
### Breaking Changes
* Move grid toolbar feature to background-addon ([#7385](https://github.com/storybookjs/storybook/pull/7385))
## 5.2.0-alpha.42 (July 12, 2019)
### Breaking Changes
* Addon-docs: Remove primary parameter ([#7383](https://github.com/storybookjs/storybook/pull/7383))
### Bug Fixes
* Addon-docs: Fix default separator inconsistency ([#7382](https://github.com/storybookjs/storybook/pull/7382))
* UI: Fix placement of notificationistList on docs page ([#7290](https://github.com/storybookjs/storybook/pull/7290))
### Maintenance
* Typescript: Migrate @storybook/html ([#7338](https://github.com/storybookjs/storybook/pull/7338))
### Dependency Upgrades
* Bump lodash from 4.17.13 to 4.17.14 ([#7384](https://github.com/storybookjs/storybook/pull/7384))
* [Security] Bump lodash.defaultsdeep from 4.6.0 to 4.6.1 ([#7370](https://github.com/storybookjs/storybook/pull/7370))
* [Security] Bump lodash from 4.17.11 to 4.17.13 ([#7374](https://github.com/storybookjs/storybook/pull/7374))
* [Security] Bump lodash.mergewith from 4.6.1 to 4.6.2 ([#7372](https://github.com/storybookjs/storybook/pull/7372))
* [Security] Bump lodash.merge from 4.6.1 to 4.6.2 ([#7373](https://github.com/storybookjs/storybook/pull/7373))
* [Security] Bump lodash-es from 4.17.11 to 4.17.14 ([#7371](https://github.com/storybookjs/storybook/pull/7371))
* Upgrade react-select dependency to version 3 for addon-knobs ([#7336](https://github.com/storybookjs/storybook/pull/7336))
## 5.2.0-alpha.41 (July 11, 2019)
### Features
* API: Preview hooks ([#6916](https://github.com/storybookjs/storybook/pull/6916))
* Core: Custom webpack option for standalone storybook ([#6886](https://github.com/storybookjs/storybook/pull/6886))
### Bug Fixes
* Addon-knobs: Fix TypeError on KnobManager channel ([#7341](https://github.com/storybookjs/storybook/pull/7341))
* React-native: Explicitly depend on emotion core and theming ([#7362](https://github.com/storybookjs/storybook/pull/7362))
### Dependency Upgrades
* Bump @babel/preset-env from 7.5.0 to 7.5.4 ([#7364](https://github.com/storybookjs/storybook/pull/7364))
* Update react-test-renderer requirement from 16.5.1 to 16.8.6 in /examples-native/crna-kitchen-sink ([#6372](https://github.com/storybookjs/storybook/pull/6372))
* Bump rax-text from 0.6.5 to 1.0.0 ([#7346](https://github.com/storybookjs/storybook/pull/7346))
## 5.2.0-alpha.40 (July 10, 2019)
### Bug Fixes
* Addon-knobs: Revert entrypoint deletion ([#7369](https://github.com/storybookjs/storybook/pull/7369))
* Typescript: Fix types in api package ([#7072](https://github.com/storybookjs/storybook/pull/7072))
* UI: Fix settings page route (about, shortcuts) ([#7241](https://github.com/storybookjs/storybook/pull/7241))
### Maintenance
* Linting: ADD an ignore for an eslint error about a missing dependency (puppeteer) ([#7239](https://github.com/storybookjs/storybook/pull/7239))
* CI: ADD travis ([#7252](https://github.com/storybookjs/storybook/pull/7252))
* Typescript: Migrate @storybook/angular ([#6570](https://github.com/storybookjs/storybook/pull/6570))
### Dependency Upgrades
* Bump express-graphql from 0.7.1 to 0.8.0 ([#7345](https://github.com/storybookjs/storybook/pull/7345))
* Bump react-native-modal-datetime-picker from 6.1.0 to 7.4.2 ([#6844](https://github.com/storybookjs/storybook/pull/6844))
## 5.2.0-alpha.39 (July 10, 2019)
### Bug Fixes
* UI: Fix Sidebar input refresh on 'Enter' ([#7342](https://github.com/storybookjs/storybook/pull/7342))
* Addon-knobs: Fix select options types to allow string[] and null ([#7356](https://github.com/storybookjs/storybook/pull/7356))
### Maintenance
* Typescript: Migrate @storybook/react ([#7054](https://github.com/storybookjs/storybook/pull/7054))
* Build: delete tests & snapshots from dist ([#7358](https://github.com/storybookjs/storybook/pull/7358))
## 5.2.0-alpha.38 (July 9, 2019)
### Bug Fixes
* Addon-storysource: Replace loader with source-loader ([#7272](https://github.com/storybookjs/storybook/pull/7272))
### Maintenance
* Typescript: Migrate @storybook/addon-knobs ([#7180](https://github.com/storybookjs/storybook/pull/7180))
### Dependency Upgrades
* Upgrade all dependencies ([#7329](https://github.com/storybookjs/storybook/pull/7329))
## 5.2.0-alpha.37 (July 8, 2019)
### Bug Fixes
* Addon-docs: Use storyFn instead of getDecorated ([#7334](https://github.com/storybookjs/storybook/pull/7334))
* Addon-knobs: Prevent rerender when a button callback returns false. ([#7197](https://github.com/storybookjs/storybook/pull/7197))
* Addons: Fix null parameters in disable addons tab logic ([#7333](https://github.com/storybookjs/storybook/pull/7333))
* Addon-docs: Fix renaming stories on module / MDX format ([#7319](https://github.com/storybookjs/storybook/pull/7319))
* Addon-centered/contexts: Move optionalDependencies to peerDependencies ([#7315](https://github.com/storybookjs/storybook/pull/7315))
### Maintenance
* Typescript: migrate client api ([#7147](https://github.com/storybookjs/storybook/pull/7147))
* Angular-cli: Add addon-docs example ([#7257](https://github.com/storybookjs/storybook/pull/7257))
## 5.2.0-alpha.36 (July 5, 2019)
### Features
* Addon-docs: Added inline option to Story block ([#7308](https://github.com/storybookjs/storybook/pull/7308))
* Addon-knobs: Ensure unique knob names across groups ([#6793](https://github.com/storybookjs/storybook/pull/6793))
* Core: Enable webpack to rebuild changes in node_modules ([#6265](https://github.com/storybooorybook/pull/6265))
* Addons: Disable option for addon tab ([#6923](https://github.com/storybookjs/storybook/pull/6923))
### Bug Fixes
* Fix lint error from #6923 ([#7311](https://github.com/storybookjs/storybook/pull/7311))
* Addon-actions: fix serialization performance ([#7256](https://github.com/storybookjs/storybook/pull/7256))
### Maintenance
* Typescript: Migrate @storybook/addon-event ([#7190](https://github.com/storybookjs/storybook/pull/7190))
* Typescript: Improve actions type ([#7012](https://github.com/storybookjs/storybook/pull/7012))
## 5.2.0-alpha.35 (July 3, 2019)
### Bug Fixes
* React-Native: Fix null story check ([#7243](https://github.com/storybookjs/storybook/pull/7243))
## 5.2.0-alpha.34 (July 2, 2019)
### Bug Fixes
* CLI: Fix `--preview-url` for static builds ([#7245](https://github.com/storybookjs/storybook/pull/7245))
* Addon-docs: Fix non-React support & add Vue example ([#7222](https://github.com/storybookjs/storybook/pull/7222))
* CLI: Move the free port logic so that loadOptions don't override it ([#7237](https://github.com/storybookjs/storybook/pull/7237))
## 5.2.0-alpha.33 (July 1, 2019)
### Features
* CLI: Add `--preview-url` for custom preview ([#7235](https://github.com/storybookjs/storybook/pull/7235))
### Bug Fixes
* React-Native: Upgrade to new `story_store` API ([#7234](https://github.com/storybookjs/storybook/pull/7234))
## 5.2.0-alpha.32 (June 29, 2019)
### Features
* Addon-docs: Add .story.mdx support to preset ([#7229](https://github.com/storybookjs/storybook/pull/7229))
### Bug Fixes
* React-native: Fix react native server ([#7187](https://github.com/storybookjs/storybook/pull/7187))
* Addon-docs: Fix source-loader in monorepo examples ([#7214](https://github.com/storybookjs/storybook/pull/7214))
### Maintenance
* Addon-docs: Convert repo stories to new module format ([#7175](https://github.com/storybookjs/storybook/pull/7175))
## 5.2.0-alpha.31 (June 27, 2019)
### Breaking Changes
* Module format: story field for name/parameters annotation ([#7202](https://github.com/storybookjs/storybook/pull/7202))
### Features
* Core: Story sorting ([#6472](https://github.com/storybookjs/storybook/pull/6472))
### Maintenance
* Addon-docs: Fix source-loader CI errors ([#7203](https://github.com/storybookjs/storybook/pull/7203))
## 5.2.0-alpha.30 (June 25, 2019)
This release merges `release/docs-technical-preview` branch back into `next` through a series of PRs. It also contains other changes that came in on `next` since the last alpha.
@ -371,7 +735,7 @@ Publish failed
- Addon-docs: Docs page bugfix
- Addon-docs: Fix source block for legacy stories
NOTE: use `@storybook/addon-storysource/loader` with option `injectParameters: true` for legacy source
NOTE: use `@storybook/source-loader` with option `injectParameters: true` for legacy source
## 5.2.0-alpha.6 (May 14, 2019)

View File

@ -162,7 +162,7 @@ If you follow that process, you can then link to the GitHub repository in the is
Sometimes your storybook is deeply ingrained in your own setup and it's hard to create a minimal viable reproduction somewhere else.
Inside the storybook repo we have a script that allows you to test the packages inside this repo in your own seperate project.
Inside the storybook repo we have a script that allows you to test the packages inside this repo in your own separate project.
You can use `npm link` on all packages, but npm linking is cumbersome and has subtle differences from what happens in a registry-based installation.
So the way our script works is that it:

View File

@ -1,14 +1,20 @@
# Migration
- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x)
- [Docs mode docgen](#docs-mode-docgen)
- [From version 5.0.x to 5.1.x](#from-version-50x-to-51x)
- [React native server](#react-native-server)
- [Angular 7](#angular-7)
- [CoreJS 3](#corejs-3)
- [From version 5.0.1 to 5.0.2](#from-version-501-to-502)
- [Deprecate webpack extend mode](#deprecate-webpack-extend-mode)
- [From version 4.1.x to 5.0.x](#from-version-41x-to-50x)
- [Migration](#migration)
- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x)
- [Grid toolbar-feature](#grid-toolbar-feature)
- [Docs mode docgen](#docs-mode-docgen)
- [storySort option](#storysort-option)
- [From version 5.1.x to 5.1.10](#from-version-51x-to-5110)
- [babel.config.js support](#babelconfigjs-support)
- [From version 5.0.x to 5.1.x](#from-version-50x-to-51x)
- [React native server](#react-native-server)
- [Angular 7](#angular-7)
- [CoreJS 3](#corejs-3)
- [From version 5.0.1 to 5.0.2](#from-version-501-to-502)
- [Deprecate webpack extend mode](#deprecate-webpack-extend-mode)
- [From version 4.1.x to 5.0.x](#from-version-41x-to-50x)
- [sortStoriesByKind](#sortstoriesbykind)
- [Webpack config simplification](#webpack-config-simplification)
- [Theming overhaul](#theming-overhaul)
- [Story hierarchy defaults](#story-hierarchy-defaults)
@ -17,49 +23,54 @@
- [Addon backgrounds uses parameters](#addon-backgrounds-uses-parameters)
- [Addon cssresources name attribute renamed](#addon-cssresources-name-attribute-renamed)
- [Addon viewport uses parameters](#addon-viewport-uses-parameters)
- [Addon a11y uses parameters](#addon-a11y-uses-parameters-decorator-renamed)
- [Addon a11y uses parameters, decorator renamed](#addon-a11y-uses-parameters-decorator-renamed)
- [New keyboard shortcuts defaults](#new-keyboard-shortcuts-defaults)
- [New URL structure](#new-url-structure)
- [Rename of the `--secure` cli parameter to `--https`](#rename-of-the---secure-cli-parameter-to---https)
- [Vue integration](#vue-integration)
- [From version 4.0.x to 4.1.x](#from-version-40x-to-41x)
- [From version 4.0.x to 4.1.x](#from-version-40x-to-41x)
- [Private addon config](#private-addon-config)
- [React 15.x](#react-15x)
- [From version 3.4.x to 4.0.x](#from-version-34x-to-40x)
- [React 16.3+](#react-163)
- [Generic addons](#generic-addons)
- [Knobs select ordering](#knobs-select-ordering)
- [Knobs URL parameters](#knobs-url-parameters)
- [Keyboard shortcuts moved](#keyboard-shortcuts-moved)
- [Removed addWithInfo](#removed-addwithinfo)
- [Removed RN packager](#removed-rn-packager)
- [Removed RN addons](#removed-rn-addons)
- [Storyshots Changes](#storyshots-changes)
- [Webpack 4](#webpack-4)
- [Babel 7](#babel-7)
- [Create-react-app](#create-react-app)
- [Upgrade CRA1 to babel 7](#upgrade-cra1-to-babel-7)
- [Migrate CRA1 while keeping babel 6](#migrate-cra1-while-keeping-babel-6)
- [start-storybook opens browser](#start-storybook-opens-browser)
- [CLI Rename](#cli-rename)
- [Addon story parameters](#addon-story-parameters)
- [From version 3.3.x to 3.4.x](#from-version-33x-to-34x)
- [From version 3.2.x to 3.3.x](#from-version-32x-to-33x)
- [`babel-core` is now a peer dependency (#2494)](#babel-core-is-now-a-peer-dependency-2494)
- [Base webpack config now contains vital plugins (#1775)](#base-webpack-config-now-contains-vital-plugins-1775)
- [Refactored Knobs](#refactored-knobs)
- [From version 3.1.x to 3.2.x](#from-version-31x-to-32x)
- [Moved TypeScript addons definitions](#moved-typescript-addons-definitions)
- [Updated Addons API](#updated-addons-api)
- [From version 3.0.x to 3.1.x](#from-version-30x-to-31x)
- [Moved TypeScript definitions](#moved-typescript-definitions)
- [Deprecated head.html](#deprecated-headhtml)
- [From version 2.x.x to 3.x.x](#from-version-2xx-to-3xx)
- [Webpack upgrade](#webpack-upgrade)
- [Packages renaming](#packages-renaming)
- [Deprecated embedded addons](#deprecated-embedded-addons)
- [From version 3.4.x to 4.0.x](#from-version-34x-to-40x)
- [React 16.3+](#react-163)
- [Generic addons](#generic-addons)
- [Knobs select ordering](#knobs-select-ordering)
- [Knobs URL parameters](#knobs-url-parameters)
- [Keyboard shortcuts moved](#keyboard-shortcuts-moved)
- [Removed addWithInfo](#removed-addwithinfo)
- [Removed RN packager](#removed-rn-packager)
- [Removed RN addons](#removed-rn-addons)
- [Storyshots Changes](#storyshots-changes)
- [Webpack 4](#webpack-4)
- [Babel 7](#babel-7)
- [Create-react-app](#create-react-app)
- [Upgrade CRA1 to babel 7](#upgrade-cra1-to-babel-7)
- [Migrate CRA1 while keeping babel 6](#migrate-cra1-while-keeping-babel-6)
- [start-storybook opens browser](#start-storybook-opens-browser)
- [CLI Rename](#cli-rename)
- [Addon story parameters](#addon-story-parameters)
- [From version 3.3.x to 3.4.x](#from-version-33x-to-34x)
- [From version 3.2.x to 3.3.x](#from-version-32x-to-33x)
- [`babel-core` is now a peer dependency (#2494)](#babel-core-is-now-a-peer-dependency-2494)
- [Base webpack config now contains vital plugins (#1775)](#base-webpack-config-now-contains-vital-plugins-1775)
- [Refactored Knobs](#refactored-knobs)
- [From version 3.1.x to 3.2.x](#from-version-31x-to-32x)
- [Moved TypeScript addons definitions](#moved-typescript-addons-definitions)
- [Updated Addons API](#updated-addons-api)
- [From version 3.0.x to 3.1.x](#from-version-30x-to-31x)
- [Moved TypeScript definitions](#moved-typescript-definitions)
- [Deprecated head.html](#deprecated-headhtml)
- [From version 2.x.x to 3.x.x](#from-version-2xx-to-3xx)
- [Webpack upgrade](#webpack-upgrade)
- [Packages renaming](#packages-renaming)
- [Deprecated embedded addons](#deprecated-embedded-addons)
## From version 5.1.x to 5.2.x
### Grid toolbar-feature
The grid feature in the toolbar has been relocated to [addon-background](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds), follow the setup intructions on that addon to get the feature again.
### Docs mode docgen
This isn't a breaking change per se, because `addon-docs` is a new feature. However it's intended to replace `addon-info`, so if you're migrating from `addon-info` there are a few things you should know:
@ -67,6 +78,26 @@ This isn't a breaking change per se, because `addon-docs` is a new feature. Howe
1. Support for only one prop table
2. Prop table docgen info should be stored on the component and not in the global variable `STORYBOOK_REACT_CLASSES` as before.
### storySort option
In 5.0.x the global option `sortStoriesByKind` option was [inadverttly removed](#sortstoriesbykind). In 5.2 we've introduced a new option, `storySort`, to replace it. `storySort` takes a comparator function, so it is strictly more powerful than `sortStoriesByKind`.
For example, here's how to sort by story ID using `storySort`:
```js
addParameters({
options: {
storySort: (a, b) => a[1].id.localeCompare(b[1].id),
},
});
```
## From version 5.1.x to 5.1.10
### babel.config.js support
SB 5.1.0 added [support for project root `babel.config.js` files](https://github.com/storybookjs/storybook/pull/6634), which was an [unintentional breaking change](https://github.com/storybookjs/storybook/issues/7058#issuecomment-515398228). 5.1.10 fixes this, but if you relied on project root `babel.config.js` support, this bugfix is a breaking change. The workaround is to copy the file into your `.storybook` config directory. We may add back project-level support in 6.0.
## From version 5.0.x to 5.1.x
### React native server

View File

@ -163,7 +163,7 @@ Looking for a first issue to tackle?
### Development scripts
Storybook is organized as a monorepo using [Lerna](https://lernajs.io). Useful scripts include:
Storybook is organized as a monorepo using [Lerna](https://lerna.js.org/). Useful scripts include:
#### `yarn bootstrap`

View File

@ -78,7 +78,7 @@ storiesOf('button', module)
## Roadmap
* Make UI accessibile
* Make UI accessible
* Show in story where violations are.
* Add more example tests
* Add tests

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "a11y addon for storybook",
"keywords": [
"a11y",
@ -26,12 +26,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/client-logger": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-logger": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"axe-core": "^3.2.2",
"common-tags": "^1.8.0",
"core-js": "^3.0.1",

View File

@ -34,15 +34,15 @@ const Icon = styled(Icons)(
: {}
);
const Passes = styled.span(({ theme }) => ({
const Passes = styled.span<{}>(({ theme }) => ({
color: theme.color.positive,
}));
const Violations = styled.span(({ theme }) => ({
const Violations = styled.span<{}>(({ theme }) => ({
color: theme.color.negative,
}));
const Incomplete = styled.span(({ theme }) => ({
const Incomplete = styled.span<{}>(({ theme }) => ({
color: theme.color.warning,
}));

View File

@ -1,5 +1,5 @@
import { document } from 'global';
import React, { Component } from 'react';
import React, { Component, ReactNode } from 'react';
import memoize from 'memoizerific';
import { styled } from '@storybook/theming';
@ -38,86 +38,90 @@ const ColorIcon = styled.span(
interface ColorBlindnessProps {}
interface ColorBlindnessState {
expanded: boolean;
filter: string | null;
active: string | null;
}
const baseList = [
'protanopia',
'protanomaly',
'deuteranopia',
'deuteranomaly',
'tritanopia',
'tritanomaly',
'achromatopsia',
'achromatomaly',
'mono',
];
export interface Link {
id: string;
title: ReactNode;
right?: ReactNode;
active: boolean;
onClick: () => void;
}
const getColorList = (active: string | null, set: (i: string | null) => void): Link[] => [
...(active !== null
? [
{
id: 'reset',
title: 'Reset color filter',
onClick: () => {
set(null);
},
right: undefined,
active: false,
},
]
: []),
...baseList.map(i => ({
id: i,
title: i.charAt(0).toUpperCase() + i.slice(1),
onClick: () => {
set(i);
},
right: <ColorIcon filter={i} />,
active: active === i,
})),
];
export class ColorBlindness extends Component<ColorBlindnessProps, ColorBlindnessState> {
state: ColorBlindnessState = {
expanded: false,
filter: null,
active: null,
};
setFilter = (filter: string | null) => {
setActive = (active: string | null) => {
const iframe = getIframe();
if (iframe) {
iframe.style.filter = getFilter(filter);
iframe.style.filter = getFilter(active);
this.setState({
expanded: false,
filter,
active,
});
} else {
logger.error('Cannot find Storybook iframe');
}
};
onVisibilityChange = (s: boolean) => {
const { expanded } = this.state;
if (expanded !== s) {
this.setState({ expanded: s });
}
};
render() {
const { filter, expanded } = this.state;
let colorList = [
'protanopia',
'protanomaly',
'deuteranopia',
'deuteranomaly',
'tritanopia',
'tritanomaly',
'achromatopsia',
'achromatomaly',
'mono',
].map(i => ({
id: i,
title: i.charAt(0).toUpperCase() + i.slice(1),
onClick: () => {
this.setFilter(i);
},
right: <ColorIcon filter={i} />,
active: filter === i,
}));
if (filter !== null) {
colorList = [
{
id: 'reset',
title: 'Reset color filter',
onClick: () => {
this.setFilter(null);
},
right: undefined,
active: false,
},
...colorList,
];
}
const { active } = this.state;
return (
<WithTooltip
placement="top"
trigger="click"
tooltipShown={expanded}
onVisibilityChange={this.onVisibilityChange}
tooltip={<TooltipLinkList links={colorList} />}
tooltip={({ onHide }) => {
const colorList = getColorList(active, i => {
this.setActive(i);
onHide();
});
return <TooltipLinkList links={colorList} />;
}}
closeOnClick
onDoubleClick={() => this.setFilter(null)}
onDoubleClick={() => this.setActive(null)}
>
<IconButton key="filter" active={!!filter} title="Color Blindness Emulation">
<IconButton key="filter" active={!!active} title="Color Blindness Emulation">
<Icons icon="mirror" />
</IconButton>
</WithTooltip>

View File

@ -11,7 +11,7 @@ const Item = styled.li({
fontWeight: 600,
});
const ItemTitle = styled.span(({ theme }) => ({
const ItemTitle = styled.span<{}>(({ theme }) => ({
borderBottom: `1px solid ${theme.appBorderColor}`,
width: '100%',
display: 'flex',

View File

@ -31,7 +31,7 @@ enum CheckBoxStates {
INDETERMINATE,
}
const Checkbox = styled.input(({ disabled }) => ({
const Checkbox = styled.input<{ disabled: boolean }>(({ disabled }) => ({
cursor: disabled ? 'not-allowed' : 'pointer',
}));

View File

@ -10,7 +10,7 @@ import { Tags } from './Tags';
import { RuleType } from '../A11YPanel';
import HighlightToggle from './HighlightToggle';
const Wrapper = styled.div(({ theme }) => ({
const Wrapper = styled.div<{}>(({ theme }) => ({
display: 'flex',
width: '100%',
borderBottom: `1px solid ${theme.appBorderColor}`,
@ -30,7 +30,7 @@ const Icon = styled<any, any>(Icons)(({ theme }) => ({
display: 'inline-flex',
}));
const HeaderBar = styled.div(({ theme }) => ({
const HeaderBar = styled.div<{}>(({ theme }) => ({
padding: theme.layoutMargin,
paddingLeft: theme.layoutMargin - 3,
background: 'none',

View File

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

View File

@ -321,7 +321,7 @@ exports[`HighlightToggle component should match snapshot 1`] = `
}
}
>
<ConnectFunction>
<Connect(HighlightToggle)>
<HighlightToggle
addElement={[Function]}
elementsToHighlight={Array []}
@ -346,7 +346,7 @@ exports[`HighlightToggle component should match snapshot 1`] = `
/>
</Styled(input)>
</HighlightToggle>
</ConnectFunction>
</Connect(HighlightToggle)>
</ThemeProvider>
</ThemedHighlightToggle>
</Provider>

View File

@ -15,7 +15,7 @@ const Container = styled.div({
minHeight: '100%',
});
const HighlightToggleLabel = styled.label(({ theme }) => ({
const HighlightToggleLabel = styled.label<{}>(({ theme }) => ({
cursor: 'pointer',
userSelect: 'none',
marginBottom: '3px',
@ -77,7 +77,7 @@ const Item = styled.button(
const TabsWrapper = styled.div({});
const List = styled.div(({ theme }) => ({
const List = styled.div<{}>(({ theme }) => ({
boxShadow: `${theme.appBorderColor} 0 -1px 0 0 inset`,
background: 'rgba(0, 0, 0, .05)',
display: 'flex',

View File

@ -2,7 +2,7 @@ import React, { Fragment, FunctionComponent } from 'react';
import { styled } from '@storybook/theming';
import { addons, types } from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from './constants';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { ColorBlindness } from './components/ColorBlindness';
import { A11YPanel } from './components/A11YPanel';
@ -94,6 +94,7 @@ addons.register(ADDON_ID, api => {
title: 'Accessibility',
type: types.PANEL,
render: ({ active, key }) => <A11YPanel key={key} api={api} active={active} />,
paramKey: PARAM_KEY,
});
addons.add(PANEL_ID, {

View File

@ -78,7 +78,7 @@ storiesOf('Button', module).add('default view', () => (
## Configuration
Arguments which are passed to the action call will have to be serialized while be "transfered"
Arguments which are passed to the action call will have to be serialized while be "transferred"
over the channel.
This is not very optimal and can cause lag when large objects are being logged, for this reason it is possible
@ -111,7 +111,7 @@ action('my-action', {
|Name|Type|Description|Default|
|---|---|---|---|
|`depth`|Number|Configures the transfered depth of any logged objects.|`10`|
|`depth`|Number|Configures the transferred depth of any logged objects.|`10`|
|`clearOnStoryChange`|Boolean|Flag whether to clear the action logger when switching away from the current story.|`true`|
|`limit`|Number|Limits the number of items logged in the action logger|`50`|

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
@ -21,15 +21,15 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"fast-deep-equal": "^2.0.1",
"global": "^4.3.2",
"lodash": "^4.17.11",
"polished": "^3.3.1",
"prop-types": "^15.7.2",
"react": "^16.8.3",

View File

@ -10,7 +10,7 @@ export const Action = styled.div({
alignItems: 'flex-start',
});
export const Counter = styled.div(({ theme }) => ({
export const Counter = styled.div<{}>(({ theme }) => ({
backgroundColor: opacify(0.5, theme.appBorderColor),
color: theme.color.inverseText,
fontSize: theme.typography.size.s1,

View File

@ -1,3 +1,4 @@
export const PARAM_KEY = 'actions';
export const ADDON_ID = 'storybook/actions';
export const PANEL_ID = `${ADDON_ID}/panel`;
export const EVENT_ID = `${ADDON_ID}/action-event`;

View File

@ -1,13 +1,14 @@
import React from 'react';
import addons from '@storybook/addons';
import ActionLogger from './containers/ActionLogger';
import { ADDON_ID, PANEL_ID } from '.';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
export function register() {
addons.register(ADDON_ID, api => {
addons.addPanel(PANEL_ID, {
title: 'Actions',
render: ({ active, key }) => <ActionLogger key={key} api={api} active={active} />,
paramKey: PARAM_KEY,
});
});
}

View File

@ -2,4 +2,5 @@ export interface ActionOptions {
depth?: number;
clearOnStoryChange?: boolean;
limit?: number;
allowFunction?: boolean;
}

View File

@ -0,0 +1,81 @@
import { ActionOptions } from './ActionOptions';
import { ActionsMap } from './ActionsMap';
export interface ActionsFunction {
<T extends string>(handlerMap: Record<T, string>, options?: ActionOptions): ActionsMap<T>;
<T extends string>(...handlers: T[]): ActionsMap<T>;
<T extends string>(handler1: T, options?: ActionOptions): ActionsMap<T>;
<T extends string>(handler1: T, handler2: T, options?: ActionOptions): ActionsMap<T>;
<T extends string>(handler1: T, handler2: T, handler3: T, options?: ActionOptions): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
handler6: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
handler6: T,
handler7: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
handler6: T,
handler7: T,
handler8: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
handler6: T,
handler7: T,
handler8: T,
handler9: T,
options?: ActionOptions
): ActionsMap<T>;
<T extends string>(
handler1: T,
handler2: T,
handler3: T,
handler4: T,
handler5: T,
handler6: T,
handler7: T,
handler8: T,
handler9: T,
handler10: T,
options?: ActionOptions
): ActionsMap<T>;
}

View File

@ -1,5 +1,3 @@
import { HandlerFunction } from './HandlerFunction';
export interface ActionsMap {
[key: string]: HandlerFunction;
}
export type ActionsMap<T extends string = string> = Record<T, HandlerFunction>;

View File

@ -1,4 +1,5 @@
export * from './ActionDisplay';
export * from './ActionsFunction';
export * from './ActionOptions';
export * from './ActionsMap';
export * from './DecoratorFunction';

View File

@ -91,3 +91,62 @@ describe('Depth config', () => {
});
});
});
describe('allowFunction config', () => {
it('with global allowFunction false', () => {
const channel = createChannel();
const allowFunction = false;
configureActions({
allowFunction,
});
action('test-action')({
root: {
one: {
a: 1,
b: () => 'foo',
},
},
});
expect(getChannelData(channel)[0]).toEqual({
root: {
one: {
a: 1,
b: expect.any(Function),
},
},
});
});
// TODO: this test is pretty pointless, as the real channel isn't used, nothing is changed
it('with global allowFunction true', () => {
const channel = createChannel();
const allowFunction = true;
configureActions({
allowFunction,
});
action('test-action')({
root: {
one: {
a: 1,
b: () => 'foo',
},
},
});
expect(getChannelData(channel)[0]).toEqual({
root: {
one: {
a: 1,
b: expect.any(Function),
},
},
});
});
});

View File

@ -1,4 +1,4 @@
import uuid from 'uuid/v1';
import uuid from 'uuid/v4';
import { addons } from '@storybook/addons';
import { EVENT_ID } from '../constants';
import { ActionDisplay, ActionOptions, HandlerFunction } from '../models';
@ -22,6 +22,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti
options: {
...actionOptions,
depth: minDepth + (actionOptions.depth || 3),
allowFunction: actionOptions.allowFunction || false,
},
};
channel.emit(EVENT_ID, actionDisplayToEmit);

View File

@ -1,8 +1,8 @@
import { action } from './action';
import { ActionOptions, ActionsMap } from '../models';
import { ActionsFunction, ActionOptions, ActionsMap } from '../models';
import { config } from './configureActions';
export function actions(...args: any[]): ActionsMap {
export const actions: ActionsFunction = (...args: any[]) => {
let options: ActionOptions = config;
const names = args;
// last argument can be options
@ -26,4 +26,4 @@ export function actions(...args: any[]): ActionsMap {
actionsObject[name] = action(namesObject[name], options);
});
return actionsObject;
}
};

View File

@ -1,14 +1,9 @@
// Based on http://backbonejs.org/docs/backbone.html#section-164
import { document, Element } from 'global';
import { isEqual } from 'lodash';
import { addons } from '@storybook/addons';
import Events from '@storybook/core-events';
import { useEffect } from '@storybook/client-api';
import { actions } from './actions';
let lastSubscription: () => () => void;
let lastArgs: any[];
const delegateEventSplitter = /^(\S+)\s*(.*)$/;
const isIE = Element != null && !Element.prototype.matches;
@ -42,24 +37,17 @@ const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) =>
});
};
const actionsSubscription = (...args: any[]) => {
if (!isEqual(args, lastArgs)) {
lastArgs = args;
// @ts-ignore
const handlers = createHandlers(...args);
lastSubscription = () => {
export const createDecorator = (actionsFn: any) => (...args: any[]) => (storyFn: () => any) => {
useEffect(() => {
if (root != null) {
const handlers = createHandlers(actionsFn, ...args);
handlers.forEach(({ eventName, handler }) => root.addEventListener(eventName, handler));
return () =>
handlers.forEach(({ eventName, handler }) => root.removeEventListener(eventName, handler));
};
}
return lastSubscription;
};
}
return undefined;
}, [root, actionsFn, args]);
export const createDecorator = (actionsFn: any) => (...args: any[]) => (storyFn: () => any) => {
if (root != null) {
addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, actionsSubscription(actionsFn, ...args));
}
return storyFn();
};

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "A storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@ -25,12 +25,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/client-logger": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-logger": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"memoizerific": "^1.11.3",
"react": "^16.8.3",

View File

@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react';
import React, { Component, Fragment, ReactElement } from 'react';
import memoize from 'memoizerific';
import { Combo, Consumer, API } from '@storybook/api';
@ -14,7 +14,7 @@ interface Item {
title: string;
onClick: () => void;
value: string;
right?: any;
right?: ReactElement;
}
interface Input {
@ -23,7 +23,7 @@ interface Input {
default?: boolean;
}
const iframeId = 'storybook-preview-background';
const iframeId = 'storybook-preview-iframe';
const createBackgroundSelectorItem = memoize(1000)(
(
@ -71,72 +71,56 @@ const mapper = ({ api, state }: Combo): { items: Input[]; selected: string | nul
return { items: list || [], selected };
};
const getDisplayedItems = memoize(10)((list: Input[], selected: string | null, change) => {
let availableBackgroundSelectorItems: Item[] = [];
const getDisplayedItems = memoize(10)(
(
list: Input[],
selected: string | null,
change: (arg: { selected: string; name: string }) => void
) => {
let availableBackgroundSelectorItems: Item[] = [];
if (selected !== 'transparent') {
availableBackgroundSelectorItems.push(
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change)
);
if (selected !== 'transparent') {
availableBackgroundSelectorItems.push(
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change)
);
}
if (list.length) {
availableBackgroundSelectorItems = [
...availableBackgroundSelectorItems,
...list.map(({ name, value }) =>
createBackgroundSelectorItem(null, name, value, true, change)
),
];
}
return availableBackgroundSelectorItems;
}
if (list.length) {
availableBackgroundSelectorItems = [
...availableBackgroundSelectorItems,
...list.map(({ name, value }) =>
createBackgroundSelectorItem(null, name, value, true, change)
),
];
}
return availableBackgroundSelectorItems;
});
);
interface GlobalState {
name: string | undefined;
selected: string | undefined;
}
interface State {
expanded: boolean;
}
interface Props {
api: API;
}
export class BackgroundSelector extends Component<Props, State> {
state: State = {
expanded: false,
};
export class BackgroundSelector extends Component<Props> {
change = ({ selected, name }: GlobalState) => {
const { api } = this.props;
const { expanded } = this.state;
if (expanded) {
this.setState({ expanded: false });
}
if (typeof selected === 'string') {
api.setAddonState<string>(PARAM_KEY, selected);
}
api.emit(EVENTS.UPDATE, { selected, name });
};
onVisibilityChange = (s: boolean) => {
const { expanded } = this.state;
if (expanded !== s) {
this.setState({ expanded: s });
}
};
render() {
const { expanded } = this.state;
return (
<Consumer filter={mapper}>
{({ items, selected }: ReturnType<typeof mapper>) => {
const selectedBackgroundColor = getSelectedBackgroundColor(items, selected);
const links = getDisplayedItems(items, selectedBackgroundColor, this.change);
return items.length ? (
<Fragment>
@ -155,9 +139,14 @@ export class BackgroundSelector extends Component<Props, State> {
<WithTooltip
placement="top"
trigger="click"
tooltipShown={expanded}
onVisibilityChange={this.onVisibilityChange}
tooltip={<TooltipLinkList links={links} />}
tooltip={({ onHide }) => (
<TooltipLinkList
links={getDisplayedItems(items, selectedBackgroundColor, i => {
this.change(i);
onHide();
})}
/>
)}
closeOnClick
>
<IconButton

View File

@ -0,0 +1,41 @@
import React, { FunctionComponent } from 'react';
import { useAddonState } from '@storybook/api';
import { Global } from '@storybook/theming';
import { Icons, IconButton } from '@storybook/components';
import { ADDON_ID } from '../constants';
const iframeId = 'storybook-preview-iframe';
export const GridSelector: FunctionComponent = () => {
const [state, setState] = useAddonState<boolean>(`${ADDON_ID}/grid`);
return (
<IconButton
key="background"
active={state}
title="Change the background of the preview"
onClick={() => setState(!state)}
>
<Icons icon="grid" />
{state ? (
<Global
styles={{
[`#${iframeId}`]: {
backgroundSize: '100px 100px, 100px 100px, 20px 20px, 20px 20px',
backgroundPosition: '-1px -1px, -1px -1px, -1px -1px, -1px -1px',
backgroundBlendMode: 'difference',
backgroundImage: [
'linear-gradient(rgba(130, 130, 130,0.5) 1px,transparent 1px)',
'linear-gradient(90deg,rgb(130, 130, 130,0.5) 1px,transparent 1px)',
'linear-gradient(rgba(130, 130, 130, 0.25) 1px,transparent 1px)',
'linear-gradient(90deg,rgba(130, 130, 130, 0.25) 1px,transparent 1px)',
].join(','),
},
}}
/>
) : null}
</IconButton>
);
};

View File

@ -1,14 +1,20 @@
import React from 'react';
import React, { Fragment } from 'react';
import { addons, types } from '@storybook/addons';
import { ADDON_ID } from './constants';
import { BackgroundSelector } from './containers/BackgroundSelector';
import { GridSelector } from './containers/GridSelector';
addons.register(ADDON_ID, api => {
addons.add(ADDON_ID, {
title: 'Backgrounds',
type: types.TOOL,
match: ({ viewMode }) => viewMode === 'story',
render: () => <BackgroundSelector api={api} />,
render: () => (
<Fragment>
<BackgroundSelector api={api} />
<GridSelector />
</Fragment>
),
});
});

View File

@ -1,3 +1,5 @@
import { StoryFn } from "@storybook/addons";
export interface ICollection {
[p: string]: any;
}
@ -11,11 +13,13 @@ export interface NgModuleMetadata {
}
export interface IStory {
props?: ICollection;
moduleMetadata?: Partial<NgModuleMetadata>;
component?: any;
props?: ICollection;
propsMeta?: ICollection;
moduleMetadata?: NgModuleMetadata;
template?: string;
styles?: string[];
}
declare module '@storybook/addon-centered/angular' {
export function centered(story: IStory): IStory;
export function centered(story: StoryFn<IStory>): IStory;
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Storybook decorator to center components",
"keywords": [
"addon",
@ -23,14 +23,18 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/mithril": "^1.1.16"
"@types/mithril": "^1.1.16",
"mithril": "*",
"preact": "*",
"react": "*"
},
"optionalDependencies": {
"peerDependencies": {
"mithril": "*",
"preact": "*",
"react": "*"

View File

@ -1,3 +1,5 @@
import { StoryFn } from '@storybook/addons';
import { IStory } from '../angular.d';
import styles from './styles';
function getComponentSelector(component: any) {
@ -43,7 +45,7 @@ function getModuleMetadata(metadata: any) {
return moduleMetadata;
}
export default function(metadataFn: any) {
export default function(metadataFn: StoryFn<IStory>) {
const metadata = metadataFn();
return {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-contexts",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Storybook Addon Contexts",
"keywords": [
"storybook",
@ -28,17 +28,20 @@
"dev:check-types": "tsc --noEmit"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"core-js": "^3.0.1"
},
"peerDependencies": {
"global": "*",
"qs": "*"
"preact": "*",
"qs": "*",
"react": "*",
"vue": "*"
},
"optionalDependencies": {
"devDependencies": {
"preact": "*",
"react": "*",
"vue": "*"

View File

@ -21,7 +21,7 @@ export const ContextsManager: ContextsManager = ({ api }) => {
);
// from preview
useChannel(UPDATE_MANAGER, newNodes => setNodes(newNodes), []);
useChannel(UPDATE_MANAGER, newNodes => setNodes(newNodes || []), []);
// to preview
useEffect(() => api.emit(REBOOT_MANAGER), []);

View File

@ -23,10 +23,13 @@ describe('Tests on addon-contexts component: ToolBarMenu', () => {
// then
expect(result).toMatchInlineSnapshot(`
<lifecycle(WithTooltipPure)
<WithTooltipPure
closeOnClick={true}
hasChrome={true}
modifiers={Object {}}
onVisibilityChange={[Function]}
placement="top"
svg={false}
tooltip={
<ToolBarMenuOptions
activeName="A"
@ -50,7 +53,7 @@ describe('Tests on addon-contexts component: ToolBarMenu', () => {
icon="globe"
/>
</IconButton>
</lifecycle(WithTooltipPure)>
</WithTooltipPure>
`);
});
@ -73,10 +76,13 @@ describe('Tests on addon-contexts component: ToolBarMenu', () => {
// then
expect(result).toMatchInlineSnapshot(`
<lifecycle(WithTooltipPure)
<WithTooltipPure
closeOnClick={true}
hasChrome={true}
modifiers={Object {}}
onVisibilityChange={[Function]}
placement="top"
svg={false}
tooltip={
<ToolBarMenuOptions
activeName="A"
@ -97,7 +103,7 @@ describe('Tests on addon-contexts component: ToolBarMenu', () => {
>
Some Context
</TabButton>
</lifecycle(WithTooltipPure)>
</WithTooltipPure>
`);
});
});

View File

@ -1,5 +1,5 @@
import React, { ComponentProps } from 'react';
import { Icons, IconButton, WithTooltip, TabButton } from '@storybook/components';
import { Icons, IconButton, WithTooltipPure, TabButton } from '@storybook/components';
import { ToolBarMenuOptions } from './ToolBarMenuOptions';
import { ContextNode, FCNoChildren } from '../../shared/types.d';
@ -20,7 +20,7 @@ export const ToolBarMenu: ToolBarMenu = ({
setExpanded,
optionsProps,
}) => (
<WithTooltip
<WithTooltipPure
closeOnClick
trigger="click"
placement="top"
@ -35,5 +35,5 @@ export const ToolBarMenu: ToolBarMenu = ({
) : (
<TabButton active={active}>{title}</TabButton>
)}
</WithTooltip>
</WithTooltipPure>
);

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-cssresources",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "A storybook addon to switch between css resources at runtime for your story",
"keywords": [
"addon",
@ -25,10 +25,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3"

View File

@ -1,7 +1,7 @@
import React from 'react';
import { addons, types } from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from './constants';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { CssResourcePanel } from './css-resource-panel';
addons.register(ADDON_ID, api => {
@ -10,5 +10,6 @@ addons.register(ADDON_ID, api => {
type: types.PANEL,
title: 'CSS resources',
render: ({ active }) => <CssResourcePanel key={PANEL_ID} api={api} active={active} />,
paramKey: PARAM_KEY,
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-design-assets",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Design asset preview for storybook",
"keywords": [
"addon",
@ -27,12 +27,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/client-logger": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-logger": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"common-tags": "^1.8.0",
"core-js": "^3.0.1",
"global": "^4.3.2",

View File

@ -50,16 +50,15 @@ export const Panel = () => {
const [selected, setSelected] = useAddonState<Selected>(ADDON_ID, 0);
const { storyId } = useStorybookState();
if (results.length === 0) {
return null;
}
if (results.length && !results[selected]) {
setSelected(0);
return null;
}
return useMemo(() => {
if (results.length === 0) {
return null;
}
if (results.length && !results[selected]) {
setSelected(0);
return null;
}
const url = getUrl(results[selected]).replace('{id}', storyId);
return (
<Fragment>

View File

@ -2,7 +2,7 @@ import React from 'react';
import { addons, types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
import { ADDON_ID, PANEL_ID } from './constants';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { Panel } from './panel';
addons.register(ADDON_ID, () => {
@ -14,5 +14,6 @@ addons.register(ADDON_ID, () => {
<Panel />
</AddonPanel>
),
paramKey: PARAM_KEY,
});
});

View File

@ -15,16 +15,15 @@ features as well. This chart captures the current state of support
| MDX stories | + | + | + | + | + | + | + | + | + | + | + |
| Module stories | + | + | + | + | + | + | + | + | + | + | + |
| Legacy stories | + | + | + | + | + | + | + | + | + | + | + |
| Source \* | + | + | + | + | + | + | + | + | + | + | + |
| Source | + | + | + | + | + | + | + | + | + | + | + |
| Notes / Info | + | + | + | + | + | + | + | + | + | + | + |
| Props table | + | # | # | | | | | | | | |
| Docgen | + | # | # | | | | | | | | |
| Props table | + | + | # | | | | | | | | |
| Docgen | + | + | # | | | | | | | | |
| Inline stories | + | # | | | | | | | | | |
**Notes:**
- `#` denotes planned/WIP support
- \* Source supports legacy `JS storiesOf` and `MDX` stories. `Typescript` and new `module format` support is WIP
## Installation
@ -34,41 +33,61 @@ First add the package. Make sure that the versions for your `@storybook/*` packa
yarn add -D @storybook/addon-docs
```
The add the following line to your `.storybook/presets.js` file:
The add the following to your `.storybook/presets.js` exports:
```js
module.exports = ['@storybook/addon-docs/react/preset'];
module.exports = ['@storybook/addon-docs/common/preset'];
```
Finally, import your stories and MDX files in `.storybook/config.js`:
Finally, update your Storybook configuration `.storybook/config.js`. Add `DocsPage` to auto-generate docs for your existing stories, and load MDX files.
```js
import { load } from '@storybook/react';
import { load, addDecorator } from '@storybook/react';
import { DocsPage } from '@storybook/addon-docs/blocks';
// standard configuration here
// ...
addDecorator({ docs: DocsPage });
// wherever your story files are located
load(require.context('../src', true, /\.stories\.js$/), module);
load(require.context('../src', true, /\.stories\.mdx$/), module);
load(require.context('../src', true, /\.stories\.(js|ts|tsx|mdx)$/), module);
```
## Preset options
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/common/preset',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
},
},
];
```
The `configureJsx` option is useful when you're writing your docs in MDX and your project's babel config isn't already set up to handle JSX files. `babelOptions` is a way to further configure the babel processor when you're using `configureJSX`.
`sourceLoaderOptions` is an object for configuring `@storybook/source-loader`. When set to `null` it tells docs not to run the `source-loader` at all, which can be used as an optimization, or if you're already using `source-loader` in your `webpack.config.js`.
## Manual configuration
Docs uses Storybook presets as a configuration shortcut. 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/addons.js`:
```js
import '@storybook/addon-docs/register';
```
Then configure Storybook's webpack loader to understand MDX files in `.storybook/webpack.config.js`:
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`:
```js
const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin');
module.exports = async ({ config }) => {
config.module.rules.push({
test: /\.mdx$/,
test: /\.(stories|story)\.mdx$/,
use: [
{
loader: 'babel-loader',
@ -83,6 +102,12 @@ module.exports = async ({ config }) => {
},
],
});
config.module.rules.push({
test: /\.(stories|story)\.[tj]sx?$/,
loader: require.resolve('@storybook/source-loader'),
exclude: [/node_modules/],
enforce: 'pre',
});
return config;
};
```

View File

@ -80,7 +80,11 @@ function MDXContent({ components, ...props }) {
mdxType=\\"Meta\\"
/>
<h1>{\`Decorated story\`}</h1>
<Story name=\\"one\\" mdxType=\\"Story\\">
<Story
name=\\"one\\"
decorators={[storyFn => <div className=\\"local\\">{storyFn()}</div>]}
mdxType=\\"Story\\"
>
<Button mdxType=\\"Button\\">One</Button>
</Story>
</MDXLayout>
@ -90,7 +94,9 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.parameters = { mdxSource: \`<Button>One</Button>\` };
one.story = {};
one.story.parameters = { mdxSource: '<Button>One</Button>' };
one.story.decorators = [storyFn => <div className=\\"local\\">{storyFn()}</div>];
const componentMeta = {
title: 'Button',
@ -119,6 +125,65 @@ export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin supports function stories 1`] = `
"/* @jsx mdx */
import { DocsContainer } from '@storybook/addon-docs/blocks';
const makeShortcode = name =>
function MDXDefaultShortcode(props) {
console.warn(
'Component ' +
name +
' was not imported, exported, or provided by MDXProvider as global scope'
);
return <div {...props} />;
};
const Story = makeShortcode('Story');
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Story name=\\"function\\" height=\\"100px\\" mdxType=\\"Story\\">
{() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
}}
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const story0 = () => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
};
story0.story = {};
story0.story.name = 'function';
story0.story.parameters = {
mdxSource:
\\"() => {\\\\n const btn = document.createElement('button');\\\\n btn.innerHTML = 'Hello Button';\\\\n btn.addEventListener('click', action('Click'));\\\\n return btn;\\\\n}\\",
};
const componentMeta = { includeStories: ['story0'] };
const mdxKind = componentMeta.title || componentMeta.displayName;
const WrappedMDXContent = ({ context }) => (
<DocsContainer context={{ ...context, mdxKind }} content={MDXContent} />
);
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = WrappedMDXContent;
export default componentMeta;
"
`;
exports[`docs-mdx-compiler-plugin supports non-story exports 1`] = `
"/* @jsx mdx */
import { DocsContainer } from '@storybook/addon-docs/blocks';
@ -159,11 +224,13 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.parameters = { mdxSource: \`<Button>One</Button>\` };
one.story = {};
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.title = 'hello story';
helloStory.parameters = { mdxSource: \`<Button>Hello button</Button>\` };
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
@ -229,17 +296,11 @@ export const toStorybook = () => ({
declarations: [Welcome],
},
});
toStorybook.title = 'to storybook';
toStorybook.parameters = {
mdxSource: \`{
template: \\\\\`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\\\\\`,
props: {
showApp: linkTo('Button')
},
moduleMetadata: {
declarations: [Welcome]
}
}\`,
toStorybook.story = {};
toStorybook.story.name = 'to storybook';
toStorybook.story.parameters = {
mdxSource:
'{\\\\n template: \`<storybook-welcome-component (showApp)=\\"showApp()\\"></storybook-welcome-component>\`,\\\\n props: {\\\\n showApp: linkTo(\\\\'Button\\\\')\\\\n },\\\\n moduleMetadata: {\\\\n declarations: [Welcome]\\\\n }\\\\n}',
};
const componentMeta = { title: 'MDX|Welcome', includeStories: ['toStorybook'] };
@ -279,8 +340,8 @@ function MDXContent({ components, ...props }) {
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
component: Button,
notes: 'component notes',
}}
mdxType=\\"Meta\\"
@ -304,13 +365,15 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const componentNotes = () => <Button>Component notes</Button>;
componentNotes.title = 'component notes';
componentNotes.parameters = { mdxSource: \`<Button>Component notes</Button>\` };
componentNotes.story = {};
componentNotes.story.name = 'component notes';
componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>' };
export const storyNotes = () => <Button>Story notes</Button>;
storyNotes.title = 'story notes';
storyNotes.parameters = {
mdxSource: \`<Button>Story notes</Button>\`,
storyNotes.story = {};
storyNotes.story.name = 'story notes';
storyNotes.story.parameters = {
mdxSource: '<Button>Story notes</Button>',
...{
notes: 'story notes',
},
@ -319,7 +382,6 @@ storyNotes.parameters = {
const componentMeta = {
title: 'Button',
parameters: {
component: Button,
notes: 'component notes',
},
includeStories: ['componentNotes', 'storyNotes'],
@ -360,8 +422,8 @@ function MDXContent({ components, ...props }) {
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
parameters={{
component: Button,
notes: 'component notes',
}}
mdxType=\\"Meta\\"
@ -385,16 +447,17 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const helloButton = () => <Button>Hello button</Button>;
helloButton.title = 'hello button';
helloButton.parameters = { mdxSource: \`<Button>Hello button</Button>\` };
helloButton.story = {};
helloButton.story.name = 'hello button';
helloButton.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
export const two = () => <Button>Two</Button>;
two.parameters = { mdxSource: \`<Button>Two</Button>\` };
two.story = {};
two.story.parameters = { mdxSource: '<Button>Two</Button>' };
const componentMeta = {
title: 'Button',
parameters: {
component: Button,
notes: 'component notes',
},
includeStories: ['helloButton', 'two'],
@ -448,11 +511,13 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const one = () => <Button>One</Button>;
one.parameters = { mdxSource: \`<Button>One</Button>\` };
one.story = {};
one.story.parameters = { mdxSource: '<Button>One</Button>' };
export const helloStory = () => <Button>Hello button</Button>;
helloStory.title = 'hello story';
helloStory.parameters = { mdxSource: \`<Button>Hello button</Button>\` };
helloStory.story = {};
helloStory.story.name = 'hello story';
helloStory.story.parameters = { mdxSource: '<Button>Hello button</Button>' };
const componentMeta = { title: 'Button', includeStories: ['one', 'helloStory'] };
@ -542,7 +607,8 @@ function MDXContent({ components, ...props }) {
MDXContent.isMDXComponent = true;
export const text = () => 'Plain text';
text.parameters = { mdxSource: \`'Plain text'\` };
text.story = {};
text.story.parameters = { mdxSource: \\"'Plain text'\\" };
const componentMeta = { title: 'Text', includeStories: ['text'] };

1
addons/docs/angular/index.js vendored Normal file
View File

@ -0,0 +1 @@
module.exports = require('../common/index');

1
addons/docs/angular/preset.js vendored Normal file
View File

@ -0,0 +1 @@
module.exports = require('../common/preset');

View File

@ -5,12 +5,13 @@ function createBabelOptions({ babelOptions, configureJSX }) {
return babelOptions;
}
const babelPlugins = (babelOptions && babelOptions.plugins) || [];
return {
...babelOptions,
// for frameworks that are not working with react, we need to configure
// the jsx to transpile mdx, for now there will be a flag for that
// for more complex solutions we can find alone that we need to add '@babel/plugin-transform-react-jsx'
plugins: [...babelOptions.plugins, '@babel/plugin-transform-react-jsx'],
plugins: [...babelPlugins, '@babel/plugin-transform-react-jsx'],
};
}
@ -18,7 +19,19 @@ function webpack(webpackConfig = {}, options = {}) {
const { module = {} } = webpackConfig;
// it will reuse babel options that are already in use in storybook
// also, these babel options are chained with other presets.
const { babelOptions, configureJSX } = options;
const { babelOptions, configureJSX, sourceLoaderOptions = {} } = options;
// set `sourceLoaderOptions` to `null` to disable for manual configuration
const sourceLoader = sourceLoaderOptions
? [
{
test: /\.(stories|story)\.[tj]sx?$/,
loader: require.resolve('@storybook/source-loader'),
options: sourceLoaderOptions,
enforce: 'pre',
},
]
: [];
return {
...webpackConfig,
@ -26,20 +39,8 @@ function webpack(webpackConfig = {}, options = {}) {
...module,
rules: [
...(module.rules || []),
// {
// test: [/\.stories\.(jsx?$|ts?$)/],
// enforce: 'pre',
// use: [
// {
// loader: require.resolve('@storybook/addon-storysource/loader'),
// options: {
// injectParameters: true,
// },
// },
// ],
// },
{
test: /\.stories.mdx$/,
test: /\.(stories|story).mdx$/,
use: [
{
loader: 'babel-loader',
@ -55,7 +56,7 @@ function webpack(webpackConfig = {}, options = {}) {
},
{
test: /\.mdx$/,
exclude: /\.stories.mdx$/,
exclude: /\.(stories|story).mdx$/,
use: [
{
loader: 'babel-loader',
@ -66,17 +67,14 @@ function webpack(webpackConfig = {}, options = {}) {
},
],
},
...sourceLoader,
],
},
};
}
function addons(entry = []) {
return [
...entry,
// require.resolve('@storybook/addon-storysource/register'),
require.resolve('../register'),
];
return [...entry, require.resolve('../register')];
}
module.exports = { webpack, addons };

View File

@ -8,6 +8,6 @@ import { Story, Meta } from '@storybook/addon-docs/blocks';
# Decorated story
<Story name="one">
<Story name="one" decorators={[storyFn => <div className="local">{storyFn()}</div>]}>
<Button>One</Button>
</Story>

View File

@ -1,7 +1,7 @@
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs/blocks';
<Meta title="Button" parameters={{ component: Button, notes: 'component notes' }} />
<Meta title="Button" component={Button} parameters={{ notes: 'component notes' }} />
<Story name="component notes">
<Button>Component notes</Button>

View File

@ -1,7 +1,7 @@
import { Button } from '@storybook/react/demo';
import { Preview, Story, Meta } from '@storybook/addon-docs/blocks';
<Meta title="Button" parameters={{ component: Button, notes: 'component notes' }} />
<Meta title="Button" component={Button} parameters={{ notes: 'component notes' }} />
# Preview

View File

@ -0,0 +1,8 @@
<Story name="function" height="100px">
{() => {
const btn = document.createElement('button');
btn.innerHTML = 'Hello Button';
btn.addEventListener('click', action('Click'));
return btn;
}}
</Story>

View File

@ -0,0 +1 @@
module.exports = require('../common/index');

View File

@ -0,0 +1 @@
module.exports = require('../common/preset');

View File

@ -2,6 +2,7 @@ const mdxToJsx = require('@mdx-js/mdx/mdx-hast-to-jsx');
const parser = require('@babel/parser');
const generate = require('@babel/generator').default;
const camelCase = require('lodash/camelCase');
const jsStringEscape = require('js-string-escape');
// Generate the MDX as is, but append named exports for every
// story in the contents
@ -60,28 +61,38 @@ function genStoryExport(ast, counter) {
const { code } = generate(body, {});
storyCode = code;
}
statements.push(
`export const ${storyKey} = () => (
${storyCode}
);`
);
if (storyCode.trim().startsWith('() =>')) {
statements.push(`export const ${storyKey} = ${storyCode}`);
} else {
statements.push(
`export const ${storyKey} = () => (
${storyCode}
);`
);
}
statements.push(`${storyKey}.story = {};`);
if (storyName !== storyKey) {
statements.push(`${storyKey}.title = '${storyName}';`);
statements.push(`${storyKey}.story.name = '${storyName}';`);
}
let parameters = getAttr(ast.openingElement, 'parameters');
parameters = parameters && parameters.expression;
const source = `\`${storyCode.replace(/`/g, '\\`')}\``;
const source = jsStringEscape(storyCode);
if (parameters) {
const { code: params } = generate(parameters, {});
// FIXME: hack in the story's source as a parameter
statements.push(`${storyKey}.parameters = { mdxSource: ${source}, ...${params} };`);
statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}', ...${params} };`);
} else {
statements.push(`${storyKey}.parameters = { mdxSource: ${source} };`);
statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}' };`);
}
// console.log(statements);
let decorators = getAttr(ast.openingElement, 'decorators');
decorators = decorators && decorators.expression;
if (decorators) {
const { code: decos } = generate(decorators, {});
statements.push(`${storyKey}.story.decorators = ${decos};`);
}
return {
[storyKey]: statements.join('\n'),

View File

@ -4,7 +4,14 @@ const mdx = require('@mdx-js/mdx');
const prettier = require('prettier');
const plugin = require('./mdx-compiler-plugin');
function format(code) {
async function generate(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const code = mdx.sync(content, {
filepath: filePath,
compilers: [plugin({})],
});
return prettier.format(code, {
parser: 'babel',
printWidth: 100,
@ -15,17 +22,6 @@ function format(code) {
});
}
async function generate(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const result = mdx.sync(content, {
filepath: filePath,
compilers: [plugin({})],
});
return format(result);
}
describe('docs-mdx-compiler-plugin', () => {
it('supports vanilla mdx', async () => {
const code = await generate(path.resolve(__dirname, './fixtures/vanilla.mdx'));
@ -67,6 +63,10 @@ describe('docs-mdx-compiler-plugin', () => {
const code = await generate(path.resolve(__dirname, './fixtures/non-story-exports.mdx'));
expect(code).toMatchSnapshot();
});
it('supports function stories', async () => {
const code = await generate(path.resolve(__dirname, './fixtures/story-function.mdx'));
expect(code).toMatchSnapshot();
});
it('errors on missing story props', async () => {
await expect(
generate(path.resolve(__dirname, './fixtures/story-missing-props.mdx'))

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Superior documentation for your components",
"keywords": [
"addon",
@ -25,16 +25,18 @@
"dependencies": {
"@babel/generator": "^7.4.0",
"@babel/parser": "^7.4.2",
"@mdx-js/loader": "^1.0.0",
"@mdx-js/mdx": "^1.0.0",
"@mdx-js/react": "^1.0.16",
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/router": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@mdx-js/loader": "^1.1.0",
"@mdx-js/mdx": "^1.1.0",
"@mdx-js/react": "^1.0.27",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/router": "5.2.0-beta.10",
"@storybook/source-loader": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"js-string-escape": "^1.0.1",
"lodash": "^4.17.11",
"prop-types": "^15.7.2"
},

View File

@ -37,13 +37,8 @@ interface StoryData {
const getDocsStories = (type: DocsStoriesType, componentStories: StoryData[]): DocsStoryProps[] => {
let stories = componentStories;
if (type !== DocsStoriesType.ALL) {
const primary = stories.find(s => s.parameters && s.parameters.primary);
const [first, ...rest] = stories;
if (type === DocsStoriesType.PRIMARY) {
stories = [primary || first];
} else {
stories = primary ? stories.filter(s => !s.parameters || !s.parameters.primary) : rest;
}
stories = type === DocsStoriesType.PRIMARY ? [first] : rest;
}
return stories.map(({ id, name, parameters: { notes, info } }) => ({
id,
@ -100,7 +95,7 @@ const getDocsPageProps = (context: DocsContextProps): DocsPageProps => {
hierarchySeparator: groupSeparator,
} = (parameters && parameters.options) || {
hierarchyRootSeparator: '|',
hierarchySeparator: '/',
hierarchySeparator: /\/|\./,
};
const { groups } = parseKind(selectedKind, { rootSeparator, groupSeparator });

View File

@ -4,6 +4,7 @@ type Decorator = (...args: any) => any;
interface MetaProps {
title: string;
component?: any;
decorators?: [Decorator];
parameters?: any;
}

View File

@ -7,6 +7,7 @@ import { DocsContext, DocsContextProps } from './DocsContext';
interface CommonProps {
height?: string;
inline?: boolean;
}
type StoryDefProps = {
@ -38,15 +39,17 @@ export const getStoryProps = (
const inputId = id === CURRENT_SELECTION ? currentId : id;
const previewId = inputId || toId(mdxKind, name);
const { height } = props;
const { height, inline } = props;
const data = storyStore.fromId(previewId);
const { framework = null } = parameters || {};
// prefer props, then global options, then framework-inferred values
const { inlineStories = inferInlineStories(framework), iframeHeight = undefined } =
(parameters && parameters.options && parameters.options.docs) || {};
return {
inline: inlineStories,
inline: typeof inline === 'boolean' ? inline : inlineStories,
id: previewId,
storyFn: data && data.getDecorated(),
storyFn: data && data.storyFn,
height: height || iframeHeight,
title: data && data.name,
};

View File

@ -1,4 +1,4 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-underscore-dangle,react/forbid-foreign-prop-types */
import PropTypes from 'prop-types';
import { PropDef } from '@storybook/components';

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-events",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Add events to your Storybook stories.",
"keywords": [
"addon",
@ -19,14 +19,16 @@
},
"license": "MIT",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-api": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"format-json": "^1.0.3",
"lodash": "^4.17.11",

View File

@ -5,10 +5,19 @@ import isEqual from 'lodash/isEqual';
import { styled } from '@storybook/theming';
import json from 'format-json';
import Textarea from 'react-textarea-autosize';
import { OnEmitEvent } from '../index';
const StyledTextarea = styled(Textarea)(
interface StyledTextareaProps {
shown: boolean;
failed: boolean;
value?: string;
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
}
const StyledTextarea = styled(({ shown, failed, ...rest }: StyledTextareaProps) => (
<Textarea {...rest} />
))<{ shown: boolean; failed: boolean }>(
{
flex: '1 0 0',
boxSizing: 'border-box',
@ -67,7 +76,7 @@ const Label = styled.label({
textAlign: 'right',
width: 100,
fontWeight: '600',
});
} as any);
const Wrapper = styled.div({
display: 'flex',
@ -77,15 +86,29 @@ const Wrapper = styled.div({
width: '100%',
});
function getJSONFromString(str) {
function getJSONFromString(str: string) {
try {
return JSON.parse(str);
} catch (e) {
return str;
}
}
interface ItemProps {
name: string;
title: string;
onEmit: (event: OnEmitEvent) => void;
payload: unknown;
}
class Item extends Component {
interface ItemState {
isTextAreaShowed: boolean;
failed: boolean;
payload: unknown;
payloadString: string;
prevPayload: unknown;
}
class Item extends Component<ItemProps, ItemState> {
static propTypes = {
name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
@ -98,12 +121,16 @@ class Item extends Component {
payload: {},
};
state = {
state: ItemState = {
isTextAreaShowed: false,
failed: false,
payload: null,
payloadString: '',
prevPayload: null,
};
onChange = ({ target: { value } }) => {
const newState = {
onChange = ({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>) => {
const newState: Partial<ItemState> = {
payloadString: value,
};
@ -114,7 +141,7 @@ class Item extends Component {
newState.failed = true;
}
this.setState(newState);
this.setState(state => ({ ...state, ...newState }));
};
onEmitClick = () => {
@ -133,7 +160,7 @@ class Item extends Component {
}));
};
static getDerivedStateFromProps = ({ payload }, { prevPayload }) => {
static getDerivedStateFromProps = ({ payload }: ItemProps, { prevPayload }: ItemState) => {
if (!isEqual(payload, prevPayload)) {
const payloadString = json.plain(payload);
const refinedPayload = getJSONFromString(payloadString);
@ -150,7 +177,6 @@ class Item extends Component {
render() {
const { title, name } = this.props;
const { failed, isTextAreaShowed, payloadString } = this.state;
return (
<Wrapper>
<Label htmlFor={`addon-event-${name}`}>{title}</Label>

View File

@ -2,9 +2,11 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@storybook/theming';
import { API } from '@storybook/api';
import { EVENTS } from '../constants';
import Event from './Event';
import { Event as EventType, OnEmitEvent } from '../index';
const Wrapper = styled.div({
width: '100%',
@ -13,7 +15,15 @@ const Wrapper = styled.div({
minHeight: '100%',
});
export default class EventsPanel extends Component {
interface EventsPanelProps {
active: boolean;
api: API;
}
interface EventsPanelState {
events: EventType[];
}
export default class EventsPanel extends Component<EventsPanelProps, EventsPanelState> {
static propTypes = {
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
@ -23,7 +33,7 @@ export default class EventsPanel extends Component {
}).isRequired,
};
state = {
state: EventsPanelState = {
events: [],
};
@ -39,13 +49,12 @@ export default class EventsPanel extends Component {
api.off(EVENTS.ADD, this.onAdd);
}
onAdd = events => {
onAdd = (events: EventType[]) => {
this.setState({ events });
};
onEmit = event => {
onEmit = (event: OnEmitEvent) => {
const { api } = this.props;
api.emit(EVENTS.EMIT, event);
};

View File

@ -1,3 +1,5 @@
export const PARAM_KEY = 'events';
export const ADDON_ID = 'storybook/events';
export const PANEL_ID = `${ADDON_ID}/panel`;

View File

@ -1,3 +1,4 @@
// TODO remove in 6.0
import addons from '@storybook/addons';
import CoreEvents from '@storybook/core-events';
import deprecate from 'util-deprecate';
@ -30,21 +31,7 @@ const addEvents = ({ emit, events }) => {
addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription);
};
const WithEvents = deprecate(({ children, ...options }) => {
export const WithEvents = deprecate(({ children, ...options }) => {
addEvents(options);
return children;
}, `<WithEvents> usage is deprecated, use .addDecorator(withEvents({emit, events})) instead`);
export default options => {
if (options.children) {
return WithEvents(options);
}
return storyFn => {
addEvents(options);
return storyFn();
};
};
if (module && module.hot && module.hot.decline) {
module.hot.decline();
}

View File

@ -0,0 +1,69 @@
import { ReactNode } from 'react';
import addons from '@storybook/addons';
import CoreEvents from '@storybook/core-events';
import deprecate from 'util-deprecate';
import { EVENTS } from './constants';
let prevEvents: Event[];
let currentEmit: (name: string, payload: unknown) => void;
export interface OnEmitEvent {
name: string;
payload: unknown;
}
const onEmit = (event: OnEmitEvent) => {
currentEmit(event.name, event.payload);
};
const subscription = () => {
const channel = addons.getChannel();
channel.on(EVENTS.EMIT, onEmit);
return () => {
prevEvents = null;
addons.getChannel().emit(EVENTS.ADD, []);
channel.removeListener(EVENTS.EMIT, onEmit);
};
};
const addEvents = ({ emit, events }: Options) => {
if (prevEvents !== events) {
addons.getChannel().emit(EVENTS.ADD, events);
prevEvents = events;
}
currentEmit = emit;
addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription);
};
export interface Event {
name: string;
title: string;
payload: unknown;
}
interface Options {
children?: ReactNode;
emit: (eventName: string, ...args: any) => void;
events: Event[];
}
const WithEvents = deprecate(({ children, ...options }: Options) => {
addEvents(options);
return children;
}, `<WithEvents> usage is deprecated, use .addDecorator(withEvents({emit, events})) instead`);
export default (options: Options) => {
if (options.children) {
return WithEvents(options);
}
return (storyFn: () => ReactNode) => {
addEvents(options);
return storyFn();
};
};
if (module && module.hot && module.hot.decline) {
module.hot.decline();
}

View File

@ -2,14 +2,14 @@ import React from 'react';
import addons from '@storybook/addons';
import Panel from './components/Panel';
import { ADDON_ID, PANEL_ID } from './constants';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
export function register() {
addons.register(ADDON_ID, api => {
addons.addPanel(PANEL_ID, {
title: 'Events',
// eslint-disable-next-line react/prop-types
render: ({ active, key }) => <Panel key={key} api={api} active={active} />,
paramKey: PARAM_KEY,
});
});
}

2
addons/events/src/typings.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare module 'react-lifecycles-compat';
declare module 'format-json';

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"types": ["webpack-env"]
},
"include": [
"src/**/*"
],
"exclude": [
"src/__tests__/**/*"
]
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-google-analytics",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Storybook addon for google analytics",
"keywords": [
"addon",
@ -20,8 +20,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react-ga": "^2.5.7"

View File

@ -17,7 +17,7 @@ addons.register('storybook/google-analytics', api => {
fatal: true,
});
});
api.on(STORY_MISSING, ({ id }: { id: string }) => {
api.on(STORY_MISSING, (id: string) => {
ReactGA.exception({
description: `attempted to render ${id}, but it is missing`,
fatal: false,

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-graphql",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Storybook addon to display the GraphiQL IDE",
"keywords": [
"addon",
@ -22,8 +22,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"graphiql": "^0.13.0",

View File

@ -1,7 +1,7 @@
import { addons, types } from '@storybook/addons';
import GQL from './manager';
import { ADDON_ID } from '.';
import { ADDON_ID, PARAM_KEY } from '.';
export const register = () => {
addons.register(ADDON_ID, () => {
@ -11,6 +11,7 @@ export const register = () => {
route: ({ storyId }) => `/graphql/${storyId}`,
match: ({ viewMode }) => viewMode === 'graphql',
render: GQL,
paramKey: PARAM_KEY,
});
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-info",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "A Storybook addon to show additional information for your stories.",
"keywords": [
"addon",
@ -22,10 +22,10 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/client-logger": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/client-logger": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"jsx-to-string": "^1.4.0",

View File

@ -310,7 +310,7 @@ exports[`addon Info should render <Info /> for memoized component 1`] = `
<div>
It's a
story:
<TestComponent
<Memo(TestComponent)
array={
Array [
1,
@ -368,7 +368,7 @@ exports[`addon Info should render <Info /> for memoized component 1`] = `
</li>
</ul>
</div>
</TestComponent>
</Memo(TestComponent)>
</div>
</div>
<button

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",
@ -28,14 +28,15 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^16.8.3",
"react-sizeme": "^2.5.2",
"upath": "^1.1.0",
"util-deprecate": "^1.0.2"
},

View File

@ -1,6 +0,0 @@
export default {
success: 'LIGHTSEAGREEN',
error: 'CRIMSON',
warning: 'DARKORANGE',
grey: 'LIGHTSLATEGRAY',
};

View File

@ -1,37 +0,0 @@
import React from 'react';
import { styled } from '@storybook/theming';
interface IndicatorProps {
color: string;
size: number;
children?: React.ReactNode;
right?: boolean;
overrides?: any;
styles?: React.CSSProperties;
}
const Indicator = styled.div<IndicatorProps>(
({ color, size }) => ({
boxSizing: 'border-box',
padding: `0 ${size / 2}px`,
width: `fit-content`,
minHeight: size,
fontSize: size / 1.4,
lineHeight: `${size}px`,
color: 'white',
textTransform: 'uppercase',
borderRadius: size / 2,
backgroundColor: color,
}),
({ overrides }) => ({
...overrides,
})
);
Indicator.defaultProps = {
right: false,
children: '',
};
export default Indicator;

View File

@ -1,205 +1,164 @@
/* eslint-disable react/no-array-index-key */
/* eslint-disable no-param-reassign */
/* eslint-disable no-control-regex */
/* tslint:disable:object-literal-sort-keys */
import React from 'react';
import React, { Fragment } from 'react';
import { styled } from '@storybook/theming';
import colors from '../colors';
const patterns = [/^\x08+/, /^\x1b\[[012]?K/, /^\x1b\[?[\d;]{0,3}/];
const positiveConsoleRegex = /\[32m(.*?)\[39m/;
const negativeConsoleRegex = /\[31m(.*?)\[39m/;
const positiveType = 'positive';
const negativeType = 'negative';
const endToken = '[39m';
const failStartToken = '[31m';
const passStartToken = '[32m';
const stackTraceStartToken = 'at';
const titleEndToken = ':';
const Pre = styled.pre({
margin: 0,
});
type MsgElement = string | JSX.Element;
const Positive = styled.strong({
color: colors.success,
fontWeight: 500,
});
const Negative = styled.strong({
color: colors.error,
fontWeight: 500,
});
class TestDetail {
description: MsgElement[];
interface StackTraceProps {
trace: MsgElement[];
className?: string;
result: MsgElement[];
stackTrace: string;
}
const StackTrace = styled(({ trace, className }: StackTraceProps) => (
<details className={className}>
<summary>Callstack</summary>
{trace
.join('')
.trim()
.split(/\n/)
.map((traceLine, traceLineIndex) => (
<div key={traceLineIndex}>{traceLine.trim()}</div>
))}
</details>
))({
background: '#e2e2e2',
padding: 10,
const StackTrace = styled.pre<{}>(({ theme }) => ({
background: theme.color.lighter,
paddingTop: '4px',
paddingBottom: '4px',
paddingLeft: '6px',
borderRadius: '2px',
overflow: 'auto',
margin: '10px 30px 10px 30px',
whiteSpace: 'pre',
}));
const Results = styled.div({
paddingTop: '10px',
marginLeft: '31px',
marginRight: '30px',
});
const Description = styled.div<{}>(({ theme }) => ({
paddingBottom: '10px',
paddingTop: '10px',
borderBottom: theme.appBorderColor,
marginLeft: '31px',
marginRight: '30px',
overflowWrap: 'break-word',
}));
const StatusColor = styled.strong<{ status: string }>(({ status, theme }) => ({
color: status === positiveType ? theme.color.positive : theme.color.negative,
fontWeight: 500,
}));
const Main = styled(({ msg, className }) => <section className={className}>{msg}</section>)({
padding: 10,
borderBottom: '1px solid #e2e2e2',
padding: 5,
});
interface SubProps {
msg: MsgElement[];
className?: string;
}
const Sub = styled(({ msg, className }: SubProps) => (
<section className={className}>
{msg
.filter(item => typeof item !== 'string' || item.trim() !== '')
.map((item, index, list) => {
if (typeof item === 'string') {
if (index === 0 && index === list.length - 1) {
return item.trim();
}
if (index === 0) {
return item.replace(/^[\s\n]*/, '');
}
if (index === list.length - 1) {
return item.replace(/[\s\n]*$/, '');
}
}
return item;
})}
</section>
))({
padding: 10,
});
interface SubgroupOptions {
startTrigger: (e: MsgElement) => boolean;
endTrigger: (e: MsgElement) => boolean;
grouper: (list: MsgElement[], key: number) => JSX.Element;
accList?: MsgElement[];
grouped?: MsgElement[];
grouperIndex?: number;
mode?: 'inject' | 'stop';
injectionPoint?: number;
}
const createSubgroup = ({
startTrigger,
endTrigger,
grouper,
accList = [],
grouped = [],
grouperIndex = 0,
mode,
injectionPoint,
}: SubgroupOptions) => (acc: MsgElement[], item: MsgElement, i: number, list: MsgElement[]) => {
grouperIndex += 1;
// start or stop extraction
if (startTrigger(item)) {
mode = 'inject';
injectionPoint = i;
}
if (endTrigger(item)) {
mode = 'stop';
const colorizeText: (msg: string, type: string) => MsgElement[] = (msg: string, type: string) => {
if (type) {
return msg
.split(type === positiveType ? positiveConsoleRegex : negativeConsoleRegex)
.map((i, index) =>
index % 2 ? (
<StatusColor key={`${type}_${i}`} status={type}>
{i}
</StatusColor>
) : (
i
)
);
}
return [msg];
};
// push item in correct aggregator
if (mode === 'inject') {
grouped.push(item);
} else {
accList.push(item);
}
const getConvertedText: (msg: string) => MsgElement[] = (msg: string) => {
let elementArray: MsgElement[] = [];
// on last iteration inject at detected injection point, and group
if (i === list.length - 1) {
// Provide a "safety net" when Jest returns a partially recognized "group"
// (recognized by acc.startTrigger but acc.endTrigger was never found) and
// it's the only group in output for a test result. In that case, accList
// will be empty, so return whatever was found, even if it will be unstyled
// and prevent next createSubgroup calls from throwing due to empty lists.
accList.push(null);
if (!msg) return elementArray;
return accList.reduce<MsgElement[]>((eacc, el, ei) => {
if (injectionPoint === 0 && ei === 0) {
// at index 0, inject before
return eacc.concat(grouper(grouped, grouperIndex)).concat(el);
const splitText = msg
.split(/\[2m/)
.join('')
.split(/\[22m/);
splitText.forEach(element => {
if (element && element.trim()) {
if (
element.indexOf(failStartToken) > -1 &&
element.indexOf(failStartToken) < element.indexOf(endToken)
) {
elementArray = elementArray.concat(colorizeText(element, negativeType));
} else if (
element.indexOf(passStartToken) > -1 &&
element.indexOf(passStartToken) < element.indexOf(endToken)
) {
elementArray = elementArray.concat(colorizeText(element, positiveType));
} else {
elementArray = elementArray.concat(element);
}
if (injectionPoint > 0 && injectionPoint === ei + 1) {
// at index > 0, and next index WOULD BE injectionPoint, inject after
return eacc.concat(el).concat(grouper(grouped, grouperIndex));
}
});
return elementArray;
};
const getTestDetail: (msg: string) => TestDetail = (msg: string) => {
const lines = msg.split('\n').filter(Boolean);
const testDetail: TestDetail = new TestDetail();
testDetail.description = getConvertedText(lines[0]);
testDetail.stackTrace = '';
testDetail.result = [];
for (let index = 1; index < lines.length; index += 1) {
const current = lines[index];
const next = lines[index + 1];
if (
current
.trim()
.toLowerCase()
.indexOf(stackTraceStartToken) === 0
) {
testDetail.stackTrace += `${current.trim()}\n`;
} else if (current.trim().indexOf(titleEndToken) > -1) {
let title;
let value = null;
if (current.trim().indexOf(titleEndToken) === current.length - 1) {
// there are breaks in the middle of result
title = current.trim();
value = getConvertedText(next);
index += 1;
} else {
// results come in a single line
title = current.substring(0, current.indexOf(titleEndToken)).trim();
value = getConvertedText(current.substring(current.indexOf(titleEndToken), current.length));
}
// do not inject
return eacc.concat(el);
}, []);
testDetail.result = [...testDetail.result, title, ' ', ...value, <br key={index} />];
} else {
// results come in an unexpected format
testDetail.result = [...testDetail.result, ' ', ...getConvertedText(current)];
}
}
return acc;
return testDetail;
};
interface MessageProps {
msg: string;
}
type MsgElement = string | JSX.Element;
export const Message = (props: any) => {
const { msg } = props;
const Message = ({ msg }: MessageProps) => {
const data = patterns
.reduce((acc, regex) => acc.replace(regex, ''), msg)
.split(/\[2m/)
.join('')
.split(/\[22m/)
.reduce((acc, item) => acc.concat(item), [] as string[])
.map((item, li) =>
item
.split(/\[32m(.*?)\[39m/)
.map((i, index) => (index % 2 ? <Positive key={`p_${li}_${i}`}>{i}</Positive> : i))
)
.reduce((acc, item) => acc.concat(item))
.map((item, li) =>
typeof item === 'string'
? item
.split(/\[31m(.*?)\[39m/)
.map((i, index) => (index % 2 ? <Negative key={`n_${li}_${i}`}>{i}</Negative> : i))
: item
)
.reduce<MsgElement[]>((acc, item) => acc.concat(item), [])
.reduce(
createSubgroup({
startTrigger: e => typeof e === 'string' && e.indexOf('Error: ') === 0,
endTrigger: e => typeof e === 'string' && Boolean(e.match('Expected ')),
grouper: (list, key) => <Main key={key} msg={list} />,
}),
[]
)
.reduce(
(acc, it) =>
typeof it === 'string' ? acc.concat(it.split(/(at(.|\n)+\d+:\d+\))/)) : acc.concat(it),
[] as MsgElement[]
)
.reduce((acc, item) => acc.concat(item), [] as MsgElement[])
.reduce(
createSubgroup({
startTrigger: e => typeof e === 'string' && e.indexOf('Expected ') !== -1,
endTrigger: e => typeof e === 'string' && Boolean(e.match(/^at/)),
grouper: (list, key) => <Sub key={key} msg={list} />,
}),
[]
)
.reduce(
createSubgroup({
startTrigger: e => typeof e === 'string' && Boolean(e.match(/at(.|\n)+\d+:\d+\)/)),
endTrigger: () => false,
grouper: (list, key) => <StackTrace key={key} trace={list} />,
}),
[]
);
return <Pre>{data}</Pre>;
const detail: TestDetail = getTestDetail(msg);
return (
<Fragment>
{detail.description ? <Description>{detail.description}</Description> : null}
{detail.result ? <Results>{detail.result}</Results> : null}
{detail.stackTrace ? <StackTrace>{detail.stackTrace}</StackTrace> : null}
</Fragment>
);
};
export default Message;

View File

@ -1,22 +1,19 @@
import React from 'react';
import { styled } from '@storybook/theming';
import { ScrollArea } from '@storybook/components';
import Indicator from './Indicator';
import Result, { FailedResult } from './Result';
import React, { Fragment } from 'react';
import { styled, withTheme } from '@storybook/theming';
import { ScrollArea, TabsState } from '@storybook/components';
import { SizeMe } from 'react-sizeme';
import Result from './Result';
import provideJestResult, { Test } from '../hoc/provideJestResult';
import colors from '../colors';
const List = styled.ul({
listStyle: 'none',
fontSize: 14,
padding: 0,
margin: '10px 0',
margin: 0,
});
const Item = styled.li({
display: 'block',
margin: '10px 0',
padding: 0,
});
@ -25,80 +22,70 @@ const NoTests = styled.div({
flex: 1,
});
const FileTitle = styled.h2({
marginRight: '6px',
marginBottom: '3px',
fontWeight: 500,
fontSize: 18,
const ProgressWrapper = styled.div({
position: 'relative',
height: '10px',
});
const SuiteHead = styled.div({
display: 'flex',
alignItems: 'baseline',
justifyContent: 'space-between',
position: 'relative',
paddingTop: 10,
position: 'absolute',
zIndex: 2,
right: '50px',
marginTop: '15px',
});
const SuiteTotals = styled(({ successNumber, failedNumber, result, className }) => (
const SuiteTotals = styled(({ successNumber, failedNumber, result, className, width }) => (
<div className={className}>
{successNumber > 0 && <div style={{ color: colors.success }}>{successNumber} passed</div>}
{failedNumber > 0 && <div style={{ color: colors.error }}>{failedNumber} failed</div>}
<div>{result.assertionResults.length} total</div>
<div>
<strong>
{result.endTime - result.startTime}
ms
</strong>
</div>
<Fragment>
{width > 325 ? (
<div>
{result.assertionResults.length} {result.assertionResults.length > 1 ? `tests` : `test`}
</div>
) : null}
{width > 280 ? (
<div>
{result.endTime - result.startTime}
ms
</div>
) : null}
</Fragment>
</div>
))({
))(({ theme }) => ({
display: 'flex',
alignItems: 'center',
color: colors.grey,
fontSize: '10px',
color: theme.color.dark,
fontSize: '14px',
marginTop: '-5px',
'& > *': {
marginLeft: 10,
marginRight: 10,
},
});
}));
const SuiteProgress = styled(({ successNumber, result, className }) => (
<div className={className} role="progressbar">
<span style={{ width: `${(successNumber / result.assertionResults.length) * 100}%` }} />
</div>
))(() => ({
width: '100%',
backgroundColor: colors.error,
height: 4,
top: 0,
))(({ theme }) => ({
width: '30px',
backgroundColor: theme.color.negative,
height: '6px',
top: '3px',
position: 'absolute',
left: 0,
borderRadius: 3,
overflow: 'hidden',
appearance: 'none',
'& > span': {
backgroundColor: colors.success,
backgroundColor: theme.color.positive,
bottom: 0,
position: 'absolute',
left: 0,
top: 0,
boxShadow: '4px 0 0 white',
},
}));
const SuiteTitle = styled.div({
display: 'flex',
alignItems: 'center',
marginBottom: '3px',
});
const PassingRate = styled.div({
fontWeight: 500,
fontSize: '10px',
});
interface ContentProps {
tests: Test[];
className?: string;
@ -107,64 +94,77 @@ interface ContentProps {
const Content = styled(({ tests, className }: ContentProps) => (
<div className={className}>
{tests.map(({ name, result }) => {
const title = name || 'Result status';
if (!result) {
return (
<NoTests key={title}>This story has tests configured, but no file was found</NoTests>
);
return <NoTests key={name}>This story has tests configured, but no file was found</NoTests>;
}
const successNumber = result.assertionResults.filter(({ status }) => status === 'passed')
.length;
const failedNumber = result.assertionResults.length - successNumber;
const passingRate = ((successNumber / result.assertionResults.length) * 100).toFixed(2);
return (
<section key={title}>
<SuiteTitle>
<FileTitle>{`${title}:`}</FileTitle>
<Indicator
color={result.status === 'passed' ? colors.success : colors.error}
size={16}
styles={{ marginRight: 5 }}
>
{result.status}
</Indicator>
</SuiteTitle>
<SuiteHead>
<PassingRate>{`Passing rate: ${(
(successNumber / result.assertionResults.length) *
100
).toFixed(2)}%`}</PassingRate>
<SuiteProgress {...{ successNumber, failedNumber, result }} />
<SuiteTotals {...{ successNumber, failedNumber, result }} />
</SuiteHead>
<List>
{result.assertionResults.map(res => (
<Item key={res.fullName || res.title}>
{res.failureMessages && res.failureMessages.length ? (
<FailedResult {...res} />
) : (
<Result {...res} />
)}
</Item>
))}
</List>
</section>
<SizeMe refreshMode="debounce">
{({ size }: { size: any }) => {
const { width } = size;
return (
<section key={name}>
<SuiteHead>
<SuiteTotals {...{ successNumber, failedNumber, result, passingRate, width }} />
{width > 240 ? (
<ProgressWrapper>
<SuiteProgress {...{ successNumber, failedNumber, result }} />
</ProgressWrapper>
) : null}
</SuiteHead>
<TabsState initial="failing-tests" backgroundColor="rgba(0,0,0,.05)">
<div id="failing-tests" title={`${failedNumber} Failed`} color="#FF4400">
<List>
{result.assertionResults.map(res => {
return res.status === 'failed' ? (
<Item key={res.fullName || res.title}>
<Result {...res} />
</Item>
) : null;
})}
</List>
</div>
<div id="passing-tests" title={`${successNumber} Passed`} color="#66BF3C">
<List>
{result.assertionResults.map(res => {
return res.status === 'passed' ? (
<Item key={res.fullName || res.title}>
<Result {...res} />
</Item>
) : null;
})}
</List>
</div>
</TabsState>
</section>
);
}}
</SizeMe>
);
})}
</div>
))({
padding: '10px 20px',
flex: '1 1 0%',
});
const ContentWithTheme = withTheme(Content);
interface PanelProps {
tests: null | Test[];
}
const Panel = ({ tests }: PanelProps) => (
<ScrollArea vertical>
{tests ? <Content tests={tests} /> : <NoTests>This story has no tests configured</NoTests>}
{tests ? (
<ContentWithTheme tests={tests} />
) : (
<NoTests>This story has no tests configured</NoTests>
)}
</ScrollArea>
);

View File

@ -1,89 +1,94 @@
import React from 'react';
import React, { Component, Fragment, useState } from 'react';
import { styled } from '@storybook/theming';
import { Icons } from '@storybook/components';
import Message from './Message';
import Indicator from './Indicator';
import colors from '../colors';
const FlexContainer = styled.div({
const Wrapper = styled.div<{ status: string }>(({ theme, status }) => ({
display: 'flex',
alignItems: 'center',
});
width: '100%',
borderTop: `1px solid ${theme.appBorderColor}`,
'&:hover': {
background: status === `failed` ? theme.background.hoverable : null,
},
}));
const Head = styled.header({
const HeaderBar = styled.div<{ status: string }>(({ theme, status }) => ({
padding: theme.layoutMargin,
paddingLeft: theme.layoutMargin - 3,
background: 'none',
color: 'inherit',
textAlign: 'left',
cursor: status === `failed` ? 'pointer' : null,
borderLeft: '3px solid transparent',
width: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
});
const Title = styled.h3({
padding: '10px 10px 0 10px',
margin: 0,
});
'&:focus': {
outline: '0 none',
borderLeft: `3px solid ${theme.color.secondary}`,
},
}));
export const FailedResult = styled(({ fullName, title, status, failureMessages, className }) => (
<div className={className}>
<Head>
<FlexContainer>
<Indicator
color={colors.error}
size={10}
overrides={{ borderRadius: '5px 0', position: 'absolute', top: -1, left: -1 }}
/>
<Title>{fullName || title}</Title>
</FlexContainer>
<Indicator
color={colors.error}
size={16}
overrides={{ borderRadius: '0 5px', position: 'absolute', top: -1, right: -1 }}
>
{status}
</Indicator>
</Head>
{failureMessages.map((msg: string, i: number) => (
// eslint-disable-next-line react/no-array-index-key
<Message msg={msg} key={i} />
))}
</div>
))({
display: 'block',
borderRadius: 5,
margin: 0,
padding: 0,
position: 'relative',
border: '1px solid silver',
boxSizing: 'border-box',
});
const Icon = styled<any, any>(Icons)(({ theme }) => ({
height: 10,
width: 10,
minWidth: 10,
color: theme.color.mediumdark,
marginRight: '10px',
transition: 'transform 0.1s ease-in-out',
alignSelf: 'center',
display: 'inline-flex',
}));
const capitalizeFirstLetter = (text: string) => {
return text
.charAt(0)
.toUpperCase()
.concat(text.slice(1));
};
interface ResultProps {
fullName?: string;
title?: string;
failureMessages: any;
status: string;
}
const Result = ({ fullName, title, status }: ResultProps) => (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<FlexContainer>
<Indicator color={colors.success} size={10} overrides={{ marginRight: 10 }} />
<div>{fullName || title}</div>
</FlexContainer>
<FlexContainer>
<Indicator color={colors.success} size={14} right>
{status}
</Indicator>
</FlexContainer>
</div>
);
Result.defaultProps = {
fullName: '',
title: '',
};
export function Result(props: ResultProps) {
const [isOpen, setIsOpen] = useState(false);
const onToggle = () => {
setIsOpen(!isOpen);
};
const { fullName, title, failureMessages, status } = props;
return (
<Fragment>
<Wrapper status={status}>
<HeaderBar onClick={onToggle} role="button" status={status}>
{status === `failed` ? (
<Icon
icon="chevrondown"
size={10}
color="#9DA5AB"
style={{
transform: `rotate(${isOpen ? 0 : -90}deg)`,
}}
/>
) : null}
<div>{capitalizeFirstLetter(fullName) || capitalizeFirstLetter(title)}</div>
</HeaderBar>
</Wrapper>
{isOpen ? (
<Fragment>
{failureMessages.map((msg: string, i: number) => (
// eslint-disable-next-line react/no-array-index-key
<Message msg={msg} key={i} />
))}
</Fragment>
) : null}
</Fragment>
);
}
export default Result;

View File

@ -1,9 +1,9 @@
import addons from '@storybook/addons';
import addons, { Parameters } from '@storybook/addons';
import deprecate from 'util-deprecate';
import { normalize, sep } from 'upath';
import { ADD_TESTS } from './shared';
interface AddonParameters {
interface AddonParameters extends Parameters {
jest?: string | string[] | { disable: true };
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import addons from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from './shared';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared';
import Panel from './components/Panel';
@ -8,5 +8,6 @@ addons.register(ADDON_ID, api => {
addons.addPanel(PANEL_ID, {
title: 'tests',
render: ({ active, key }) => <Panel key={key} api={api} active={active} />,
paramKey: PARAM_KEY,
});
});

View File

@ -1,4 +1,5 @@
// addons, panels and events get unique names using a prefix
export const PARAM_KEY = 'test';
export const ADDON_ID = 'storybookjs/test';
export const PANEL_ID = `${ADDON_ID}/panel`;

View File

@ -410,6 +410,9 @@ const groupId = 'GROUP-ID1';
button(label, handler, groupId);
```
Button knobs cause the story to re-render after the handler fires, you can prevent
this by having the handler return false.
### withKnobs options
withKnobs also accepts two optional options as story parameters.

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-knobs",
"version": "5.2.0-alpha.30",
"version": "5.2.0-beta.10",
"description": "Storybook Addon Prop Editor Component",
"keywords": [
"addon",
@ -17,16 +17,17 @@
},
"license": "MIT",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.2.0-alpha.30",
"@storybook/client-api": "5.2.0-alpha.30",
"@storybook/components": "5.2.0-alpha.30",
"@storybook/core-events": "5.2.0-alpha.30",
"@storybook/theming": "5.2.0-alpha.30",
"@storybook/addons": "5.2.0-beta.10",
"@storybook/api": "5.2.0-beta.10",
"@storybook/client-api": "5.2.0-beta.10",
"@storybook/components": "5.2.0-beta.10",
"@storybook/core-events": "5.2.0-beta.10",
"@storybook/theming": "5.2.0-beta.10",
"copy-to-clipboard": "^3.0.8",
"core-js": "^3.0.1",
"escape-html": "^1.0.3",
@ -37,12 +38,18 @@
"qs": "^6.6.0",
"react-color": "^2.17.0",
"react-lifecycles-compat": "^3.0.4",
"react-select": "^2.2.0"
"react-select": "^3.0.0"
},
"peerDependencies": {
"react": "*"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/escape-html": "0.0.20",
"@types/react-color": "^3.0.1",
"@types/react-lifecycles-compat": "^3.0.1",
"@types/react-select": "^2.0.19"
}
}

View File

@ -56,6 +56,7 @@ describe('KnobManager', () => {
const newKnob = {
...defaultKnob,
label: 'foo',
defaultValue: defaultKnob.value,
};
@ -86,6 +87,7 @@ describe('KnobManager', () => {
const newKnob = {
...defaultKnob,
label: 'foo',
defaultValue: defaultKnob.value,
};

View File

@ -1,25 +1,34 @@
/* eslint no-underscore-dangle: 0 */
import { navigator } from 'global';
import escape from 'escape-html';
import { getQueryParams } from '@storybook/client-api';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Channel } from '@storybook/channels';
import KnobStore from './KnobStore';
import KnobStore, { KnobStoreKnob } from './KnobStore';
import { Knob, KnobType, Mutable } from './type-defs';
import { SET } from './shared';
import { deserializers } from './converters';
const knobValuesFromUrl = Object.entries(getQueryParams()).reduce((acc, [k, v]) => {
if (k.includes('knob-')) {
return { ...acc, [k.replace('knob-', '')]: v };
}
return acc;
}, {});
const knobValuesFromUrl: Record<string, string> = Object.entries(getQueryParams()).reduce(
(acc, [k, v]) => {
if (k.includes('knob-')) {
return { ...acc, [k.replace('knob-', '')]: v };
}
return acc;
},
{}
);
// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
const PANEL_UPDATE_INTERVAL = 400;
const escapeStrings = obj => {
function escapeStrings(obj: { [key: string]: string }): { [key: string]: string };
function escapeStrings(obj: (string | string[])[]): (string | string[])[];
function escapeStrings(obj: string): string;
function escapeStrings(obj: any): any {
if (typeof obj === 'string') {
return escape(obj);
}
@ -31,35 +40,45 @@ const escapeStrings = obj => {
const didChange = newArray.some((newValue, key) => newValue !== obj[key]);
return didChange ? newArray : obj;
}
return Object.entries(obj).reduce((acc, [key, oldValue]) => {
return Object.entries<{ [key: string]: string }>(obj).reduce((acc, [key, oldValue]) => {
const newValue = escapeStrings(oldValue);
return newValue === oldValue ? acc : { ...acc, [key]: newValue };
}, obj);
};
}
interface KnobManagerOptions {
escapeHTML?: boolean;
disableDebounce?: boolean;
}
export default class KnobManager {
constructor() {
this.knobStore = new KnobStore();
this.options = {};
}
knobStore = new KnobStore();
setChannel(channel) {
channel: Channel | undefined;
options: KnobManagerOptions = {};
calling: boolean = false;
setChannel(channel: Channel) {
this.channel = channel;
}
setOptions(options) {
setOptions(options: KnobManagerOptions) {
this.options = options;
}
getKnobValue({ value }) {
getKnobValue({ value }: Knob) {
return this.options.escapeHTML ? escapeStrings(value) : value;
}
knob(name, options) {
knob<T extends KnobType = any>(name: string, options: Knob<T>): Mutable<Knob<T>['value']> {
this._mayCallChannel();
const knobName = options.groupId ? `${name}_${options.groupId}` : name;
const { knobStore } = this;
const existingKnob = knobStore.get(name);
const existingKnob = knobStore.get(knobName);
// We need to return the value set by the knob editor via this.
// Normally the knobs are reset and so re-use is safe as long as the types match
@ -75,24 +94,25 @@ export default class KnobManager {
return this.getKnobValue(existingKnob);
}
const knobInfo = {
const knobInfo: Knob<T> & { name: string; label: string; defaultValue?: any } = {
...options,
name,
name: knobName,
label: name,
};
if (knobValuesFromUrl[name]) {
const value = deserializers[options.type](knobValuesFromUrl[name]);
if (knobValuesFromUrl[knobName]) {
const value = deserializers[options.type](knobValuesFromUrl[knobName]);
knobInfo.defaultValue = value;
knobInfo.value = value;
delete knobValuesFromUrl[name];
delete knobValuesFromUrl[knobName];
} else {
knobInfo.defaultValue = options.value;
}
knobStore.set(name, knobInfo);
return this.getKnobValue(knobStore.get(name));
knobStore.set(knobName, knobInfo as KnobStoreKnob);
return this.getKnobValue(knobStore.get(knobName));
}
_mayCallChannel() {
@ -101,6 +121,11 @@ export default class KnobManager {
// unused knobs from the panel. This function sends the `setKnobs` message to the channel
// triggering a panel re-render.
if (!this.channel) {
// to prevent call to undefined channel and therefore throwing TypeError
return;
}
if (this.calling) {
// If a call to channel has already registered ignore this call.
// Once the previous call is completed all the changes to knobStore including the one that
@ -114,7 +139,7 @@ export default class KnobManager {
setTimeout(() => {
this.calling = false;
// emit to the channel and trigger a panel re-render
this.channel.emit(SET, { knobs: this.knobStore.getAll(), timestamp });
if (this.channel) this.channel.emit(SET, { knobs: this.knobStore.getAll(), timestamp });
}, PANEL_UPDATE_INTERVAL);
}
}

View File

@ -1,56 +0,0 @@
const callArg = fn => fn();
const callAll = fns => fns.forEach(callArg);
export default class KnobStore {
constructor() {
this.store = {};
this.callbacks = [];
}
has(key) {
return this.store[key] !== undefined;
}
set(key, value) {
this.store[key] = value;
this.store[key].used = true;
this.store[key].groupId = value.groupId;
// debounce the execution of the callbacks for 50 milliseconds
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(callAll, 50, this.callbacks);
}
get(key) {
const knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}
getAll() {
return this.store;
}
reset() {
this.store = {};
}
markAllUnused() {
Object.keys(this.store).forEach(knobName => {
this.store[knobName].used = false;
});
}
subscribe(cb) {
this.callbacks.push(cb);
}
unsubscribe(cb) {
const index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
}
}

View File

@ -0,0 +1,72 @@
import { Knob } from './type-defs';
type Callback = () => any;
export type KnobStoreKnob = Knob & {
name: string;
label: string;
used?: boolean;
defaultValue?: any;
hideLabel?: boolean;
callback?: () => any;
};
const callArg = (fn: Callback) => fn();
const callAll = (fns: Callback[]) => fns.forEach(callArg);
export default class KnobStore {
store: Record<string, KnobStoreKnob> = {};
callbacks: Callback[] = [];
timer: number | undefined;
has(key: string) {
return this.store[key] !== undefined;
}
set(key: string, value: KnobStoreKnob) {
this.store[key] = {
...value,
used: true,
groupId: value.groupId,
};
// debounce the execution of the callbacks for 50 milliseconds
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(callAll, 50, this.callbacks) as number;
}
get(key: string) {
const knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}
getAll() {
return this.store;
}
reset() {
this.store = {};
}
markAllUnused() {
Object.keys(this.store).forEach(knobName => {
this.store[knobName].used = false;
});
}
subscribe(cb: Callback) {
this.callbacks.push(cb);
}
unsubscribe(cb: Callback) {
const index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
}
}

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