mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-17 05:02:23 +08:00
Merge branch 'master' into babel-7
# Conflicts: # MIGRATION.md # addons/backgrounds/package.json # addons/events/package.json # addons/info/package.json # addons/info/src/__snapshots__/index.test.js.snap # addons/jest/package.json # addons/knobs/package.json # addons/links/package.json # addons/notes/package.json # addons/options/package.json # addons/storyshots/storyshots-core/package.json # addons/storyshots/storyshots-puppeteer/package.json # addons/storysource/package.json # addons/viewport/package.json # app/angular/package.json # app/html/package.json # app/marko/package.json # app/mithril/package.json # app/polymer/package.json # app/react/package.json # app/vue/package.json # examples/angular-cli/package.json # examples/cra-kitchen-sink/package.json # examples/html-kitchen-sink/package.json # examples/marko-cli/package.json # examples/mithril-kitchen-sink/package.json # examples/official-storybook/package.json # examples/official-storybook/stories/__snapshots__/addon-info.stories.storyshot # examples/official-storybook/stories/__snapshots__/other-dirname.stories.storyshot # examples/polymer-cli/package.json # examples/vue-kitchen-sink/package.json # jest.config.js # lib/cli/package.json # lib/cli/test/snapshots/angular-cli/package.json # lib/cli/test/snapshots/marko/package.json # lib/cli/test/snapshots/meteor/package.json # lib/cli/test/snapshots/mithril/package.json # lib/cli/test/snapshots/polymer/package.json # lib/cli/test/snapshots/react/package.json # lib/cli/test/snapshots/react_project/package.json # lib/cli/test/snapshots/react_scripts/package.json # lib/cli/test/snapshots/sfc_vue/package.json # lib/cli/test/snapshots/update_package_organisations/package.json # lib/cli/test/snapshots/vue/package.json # lib/cli/test/snapshots/webpack_react/package.json # lib/codemod/package.json # lib/components/src/layout/__snapshots__/index.stories.storyshot # lib/core/package.json # lib/core/src/server/config/webpack.config.prod.js # package.json # yarn.lock
This commit is contained in:
commit
a039c96426
@ -8,7 +8,9 @@ languages:
|
||||
- .*\.test\.js
|
||||
- .*\/__test__\/.*\.js
|
||||
- .*\/__mock__\/.*\.js
|
||||
- .*\.stories\.js
|
||||
test:
|
||||
include:
|
||||
- .*\.test\.js
|
||||
- .*\/__test__\/.*\.js
|
||||
- .*\.storyshot
|
||||
|
@ -39,6 +39,7 @@ jobs:
|
||||
- examples/official-storybook/node_modules
|
||||
- examples/polymer-cli/node_modules
|
||||
- examples/vue-kitchen-sink/node_modules
|
||||
- examples/svelte-kitchen-sink/node_modules
|
||||
- examples/marko-cli/node_modules
|
||||
- save_cache:
|
||||
name: "Cache core dist"
|
||||
@ -72,6 +73,11 @@ jobs:
|
||||
command: |
|
||||
cd examples/vue-kitchen-sink
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: "Build svelte kitchen-sink"
|
||||
command: |
|
||||
cd examples/svelte-kitchen-sink
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: "Build angular-cli"
|
||||
command: |
|
||||
@ -126,6 +132,11 @@ jobs:
|
||||
command: |
|
||||
cd examples/vue-kitchen-sink
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: "Run svelte kitchen-sink (smoke test)"
|
||||
command: |
|
||||
cd examples/svelte-kitchen-sink
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: "Run angular-cli (smoke test)"
|
||||
command: |
|
||||
|
@ -3,6 +3,6 @@ root = true
|
||||
[*]
|
||||
end_of_line = lf
|
||||
|
||||
[*.{js,json,ts,vue,html}]
|
||||
[*.{js,json,ts,vue,svelte,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
@ -14,6 +14,7 @@ module.exports = {
|
||||
plugins: ['prettier', 'jest', 'import', 'react', 'jsx-a11y', 'json'],
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
},
|
||||
env: {
|
||||
|
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@ -13,7 +13,7 @@
|
||||
/addons/links/ @hypnosphi @ndelangen
|
||||
/addons/notes/ @alexandrebodin
|
||||
/addons/options/ @danielduan @UsulPro
|
||||
/addons/storyshots/ @igor-dv @thomasbertet @hypnosphi
|
||||
/addons/storyshots/ @igor-dv @thomasbertet @hypnosphi
|
||||
/addons/storysource/ @igor-dv
|
||||
/addons/viewport/ @saponifi3d
|
||||
|
||||
@ -22,16 +22,18 @@
|
||||
/app/react/ @xavcz @shilman @thomasbertet
|
||||
/app/react-native/ @rmevans9 @danielduan @Gongreg @tmeasday
|
||||
/app/vue/ @thomasbertet @kazupon
|
||||
/app/svelte/ @plumpNation
|
||||
|
||||
/docs/ @ndelangen @shilman @hypnosphi
|
||||
|
||||
/examples/angular-cli/ @igor-dv @alterx
|
||||
/examples/cra-kitchen-sink/ @ndelangen @UsulPro @hypnosphi
|
||||
/examples/cra-kitchen-sink/ @ndelangen @UsulPro @hypnosphi
|
||||
/examples/crna-kitchen-sink/ @Gongreg @danielduan
|
||||
/examples/official-storybook/ @hypnosphi @danielduan @UsulPro
|
||||
/examples/polymer-cli/ @naipath @igor-dv
|
||||
/examples/react-native-vanilla/ @tmeasday @danielduan
|
||||
/examples/vue-kitchen-sink/ @igor-dv @alexandrebodin
|
||||
/examples/svelte-kitchen-sink/ @plumpNation
|
||||
|
||||
/lib/addons/ @ndelangen @theinterned
|
||||
/lib/channel-postmessage/ @mnmtanish @ndelangen
|
||||
|
1
.github/autolabeler.yml
vendored
1
.github/autolabeler.yml
vendored
@ -17,6 +17,7 @@
|
||||
'app: react-native': ["app/react-native/**"]
|
||||
'app: react': ["app/react/**"]
|
||||
'app: vue': ["app/vue/**"]
|
||||
'app: svelte': ["app/svelte/**"]
|
||||
'app: mithril': ["app/mithril/**"]
|
||||
'babel / webpack': ["webpack", "babel"]
|
||||
'cli': ["lib/cli/**"]
|
||||
|
@ -1,6 +1,7 @@
|
||||
/example/
|
||||
/demo/
|
||||
/docs/
|
||||
/media/
|
||||
/node_modules/
|
||||
/.storybook/
|
||||
|
||||
|
@ -15,7 +15,7 @@ enum class StorybookApp(val appName: String, val exampleDir: String, val merged:
|
||||
HTML("HTML", "html-kitchen-sink"),
|
||||
MARKO("Marko", "marko-cli"),
|
||||
HYPERAPP("Hyperapp", "hyperapp-kitchen-sink", false),
|
||||
SVELTE("Svelte", "svelte-kitchen-sink", false);
|
||||
SVELTE("Svelte", "svelte-kitchen-sink");
|
||||
|
||||
val lowerName = appName.toLowerCase()
|
||||
|
||||
|
107
.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts
vendored
Normal file
107
.teamcity/OpenSourceProjects_Storybook/patches/projects/69382d9b-7791-418a-9ff6-1c83b86ed6b5.kts
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package OpenSourceProjects_Storybook.patches.projects
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.Project
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ProjectFeature
|
||||
import jetbrains.buildServer.configs.kotlin.v2017_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the project with uuid = '69382d9b-7791-418a-9ff6-1c83b86ed6b5' (id = 'OpenSourceProjects_Storybook')
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeProject("69382d9b-7791-418a-9ff6-1c83b86ed6b5") {
|
||||
features {
|
||||
val feature1 = find<ProjectFeature> {
|
||||
feature {
|
||||
type = "project-graphs"
|
||||
id = "PROJECT_EXT_306"
|
||||
param("format", "duration")
|
||||
param("series", """
|
||||
[
|
||||
{
|
||||
"type": "valueType",
|
||||
"title": "Build Duration (all stages)",
|
||||
"sourceBuildTypeId": "OpenSourceProjects_Storybook_Build_2",
|
||||
"key": "BuildDuration"
|
||||
}
|
||||
]
|
||||
""".trimIndent())
|
||||
param("seriesTitle", "Serie")
|
||||
param("title", "New chart title")
|
||||
}
|
||||
}
|
||||
feature1.apply {
|
||||
param("hideFilters", "")
|
||||
param("title", "Build Duration (all stages)")
|
||||
param("defaultFilters", "")
|
||||
}
|
||||
val feature2 = find<ProjectFeature> {
|
||||
feature {
|
||||
type = "project-graphs"
|
||||
id = "PROJECT_EXT_307"
|
||||
param("defaultFilters", "")
|
||||
param("format", "percent")
|
||||
param("hideFilters", "")
|
||||
param("series", """
|
||||
[
|
||||
{
|
||||
"type": "valueType",
|
||||
"title": "Covered Percentage of JS Lines",
|
||||
"sourceBuildTypeId": "OpenSourceProjects_Storybook_Test",
|
||||
"key": "Covered Percentage of JS Lines"
|
||||
}
|
||||
]
|
||||
""".trimIndent())
|
||||
param("seriesTitle", "Serie")
|
||||
param("title", "New chart title")
|
||||
}
|
||||
}
|
||||
feature2.apply {
|
||||
param("title", "Covered Percentage of JS Lines")
|
||||
}
|
||||
val feature3 = find<ProjectFeature> {
|
||||
feature {
|
||||
type = "project-graphs"
|
||||
id = "PROJECT_EXT_308"
|
||||
param("format", "integer")
|
||||
param("series", """
|
||||
[
|
||||
{
|
||||
"type": "valueType",
|
||||
"title": "Total Number of JS Statements",
|
||||
"sourceBuildTypeId": "OpenSourceProjects_Storybook_Test",
|
||||
"key": "Total Number of JS Statements"
|
||||
}
|
||||
]
|
||||
""".trimIndent())
|
||||
param("seriesTitle", "Serie")
|
||||
param("title", "New chart title")
|
||||
}
|
||||
}
|
||||
feature3.apply {
|
||||
param("hideFilters", "")
|
||||
param("title", "Total Number of JS Statements")
|
||||
param("defaultFilters", "")
|
||||
}
|
||||
add {
|
||||
feature {
|
||||
type = "project-graphs"
|
||||
id = "PROJECT_EXT_117"
|
||||
param("series", """
|
||||
[
|
||||
{
|
||||
"type": "valueType",
|
||||
"title": "Total Artifacts Size",
|
||||
"sourceBuildTypeId": "OpenSourceProjects_Storybook_CliTestLatestCra",
|
||||
"key": "ArtifactsSize"
|
||||
}
|
||||
]
|
||||
""".trimIndent())
|
||||
param("format", "text")
|
||||
param("title", "Total Artifacts Size")
|
||||
param("seriesTitle", "Serie")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
## Addon / Framework Support Table
|
||||
|
||||
| |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[a11y](addons/a11y) |+| |+|+|+|+|+|+|
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|+|
|
||||
|[backgrounds](addons/backgrounds) |+| |+|+|+|+|+|+|
|
||||
|[centered](addons/centered) |+| |+|+| |+|+| |
|
||||
|[events](addons/events) |+| |+|+|+|+|+|+|
|
||||
|[graphql](addons/graphql) |+| | | | | | | |
|
||||
|[info](addons/info) |+| | | | | | | |
|
||||
|[jest](addons/jest) |+| | |+| | |+| |
|
||||
|[knobs](addons/knobs) |+|+|+|+|+|+|+|+|
|
||||
|[links](addons/links) |+|+|+|+|+|+|+| |
|
||||
|[notes](addons/notes) |+| |+|+|+|+|+| |
|
||||
|[options](addons/options) |+|+|+|+|+|+|+| |
|
||||
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |
|
||||
|[storysource](addons/storysource)|+| |+|+|+|+|+|+|
|
||||
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|
|
||||
| |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| [Svelte](app/svelte)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[a11y](addons/a11y) |+| |+|+|+|+|+|+| |
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|+|+|
|
||||
|[backgrounds](addons/backgrounds) |+| |+|+|+|+|+|+|+|
|
||||
|[centered](addons/centered) |+| |+|+| |+|+| |+|
|
||||
|[events](addons/events) |+| |+|+|+|+|+|+| |
|
||||
|[graphql](addons/graphql) |+| | | | | | | | |
|
||||
|[info](addons/info) |+| | | | | | | | |
|
||||
|[jest](addons/jest) |+| | |+| | |+| | |
|
||||
|[knobs](addons/knobs) |+|+|+|+|+|+|+|+|+|
|
||||
|[links](addons/links) |+|+|+|+|+|+|+| |+|
|
||||
|[notes](addons/notes) |+| |+|+|+|+|+| |+|
|
||||
|[options](addons/options) |+|+|+|+|+|+|+| |+|
|
||||
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |+|
|
||||
|[storysource](addons/storysource)|+| |+|+|+|+|+|+|+|
|
||||
|[viewport](addons/viewport) |+| |+|+|+|+|+|+|+|
|
||||
|
68
CHANGELOG.md
68
CHANGELOG.md
@ -1,3 +1,67 @@
|
||||
# 4.0.0-alpha.16
|
||||
|
||||
2018-August-06
|
||||
|
||||
#### Features
|
||||
|
||||
- Make addon-options work with story parameters [#3958](https://github.com/storybooks/storybook/pull/3958)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- [BUG FIX] Use fixed version of react-dev-utils [#3959](https://github.com/storybooks/storybook/pull/3959)
|
||||
- Inline emotion css calls that require theme to avoid using state [#3950](https://github.com/storybooks/storybook/pull/3950)
|
||||
|
||||
#### Dependency Upgrades
|
||||
|
||||
- Upgrade even more dependencies [#3964](https://github.com/storybooks/storybook/pull/3964)
|
||||
- More dependency upgrades (major version bumps) [#3957](https://github.com/storybooks/storybook/pull/3957)
|
||||
- UPGRADE all minor dependencies [#3954](https://github.com/storybooks/storybook/pull/3954)
|
||||
|
||||
# 4.0.0-alpha.15
|
||||
|
||||
2018-August-03
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
- dependencies(vue): Update vue-loader to 15.x.x [#3911](https://github.com/storybooks/storybook/pull/3911)
|
||||
|
||||
#### Features
|
||||
|
||||
- Horizontal display for addon-knobs radios UI [#3922](https://github.com/storybooks/storybook/pull/3922)
|
||||
- Add customizePage method to imageSnapshot [#3930](https://github.com/storybooks/storybook/pull/3930)
|
||||
- Add additional device options to addon-viewport [#3918](https://github.com/storybooks/storybook/pull/3918)
|
||||
- Support different extensions for "config" and "addons" files [#3913](https://github.com/storybooks/storybook/pull/3913)
|
||||
- Add radio buttons knob type #3872 [#3894](https://github.com/storybooks/storybook/pull/3894)
|
||||
- Added arrow to a11y addon HeaderBar [#3788](https://github.com/storybooks/storybook/pull/3788)
|
||||
- Fix addons panel when using preact [#3882](https://github.com/storybooks/storybook/pull/3882)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- Fix typo in addon-viewport [#3942](https://github.com/storybooks/storybook/pull/3942)
|
||||
- Fix knobs for React < 16.3 [#3866](https://github.com/storybooks/storybook/pull/3866)
|
||||
|
||||
#### Maintenance
|
||||
|
||||
- Improve BettercodeHub [#3941](https://github.com/storybooks/storybook/pull/3941)
|
||||
- REFACTOR layout and REMOVE usplit component [#3914](https://github.com/storybooks/storybook/pull/3914)
|
||||
- Group deprecated stories [#3846](https://github.com/storybooks/storybook/pull/3846)
|
||||
- MOVE ui into it's own group [#3884](https://github.com/storybooks/storybook/pull/3884)
|
||||
|
||||
#### Dependency Upgrades
|
||||
|
||||
- Use react-dev-utils@next [#3852](https://github.com/storybooks/storybook/pull/3852)
|
||||
|
||||
# 3.4.10
|
||||
|
||||
2018-August-03
|
||||
|
||||
NOTE: `3.4.9` publish failed
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
- addons-jest: bug with the jest parameter [#3923](https://github.com/storybooks/storybook/pull/3923)
|
||||
- addon-info: fix copy button styling [#3896](https://github.com/storybooks/storybook/pull/3896)
|
||||
|
||||
# 4.0.0-alpha.14
|
||||
|
||||
2018-July-11
|
||||
@ -302,9 +366,12 @@
|
||||
|
||||
2018-May-17
|
||||
|
||||
NOTE: As part of the generic addon decorators, we've reversed the order of addon-knob's `select` knob keys/values, which had been called `selectV2` prior to this breaking change.
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
- Support webpack4 modules format [#3576](https://github.com/storybooks/storybook/pull/3576)
|
||||
- Generic addon decorators [#3555](https://github.com/storybooks/storybook/pull/3555)
|
||||
|
||||
#### Features
|
||||
|
||||
@ -312,7 +379,6 @@
|
||||
|
||||
#### Maintenance
|
||||
|
||||
- Generic addon decorators [#3555](https://github.com/storybooks/storybook/pull/3555)
|
||||
- Refactor transitional decorator from addon-notes [#3559](https://github.com/storybooks/storybook/pull/3559)
|
||||
|
||||
# 3.4.5
|
||||
|
@ -8,11 +8,11 @@ This repo uses yarn workspaces, so you should install `yarn@1.3.2` or higher as
|
||||
|
||||
## Issues
|
||||
|
||||
No software is bug free. So, if you got an issue, follow these steps:
|
||||
No software is bug-free. So, if you got an issue, follow these steps:
|
||||
|
||||
- Search the [issue list](https://github.com/storybooks/storybook/issues?utf8=%E2%9C%93&q=) for current and old issues.
|
||||
- If you find an existing issue, please UPVOTE the issue by adding a "thumbs-up reaction". We use this to help prioritize issues!
|
||||
- If none of that is helping, create an issue with with following information:
|
||||
- If none of that is helping, create an issue with the following information:
|
||||
- Clear title (shorter is better).
|
||||
- Describe the issue in clear language.
|
||||
- Share error logs, screenshots and etc.
|
||||
@ -53,7 +53,7 @@ You can use the `--update` flag to update snapshots or screenshots as needed.
|
||||
|
||||
You can also pick suites from CLI. Suites available are listed below.
|
||||
|
||||
##### Core & React & Vue Tests
|
||||
##### Core & React & Vue & Svelte Tests
|
||||
|
||||
`yarn test --core`
|
||||
|
||||
@ -71,8 +71,15 @@ Before these tests are ran, the project must be bootstrapped with the React Nati
|
||||
|
||||
`yarn test --image`
|
||||
|
||||
This option executes tests from `<rootdir>/examples/cra-kitchen-sink`
|
||||
In order for the image snapshots to be correctly generated, you must have static build of the storybook up-to-date.
|
||||
This option executes tests from `<rootdir>/examples/official-storybook`
|
||||
In order for the image snapshots to be correctly generated, you must have static build of the storybook up-to-date :
|
||||
|
||||
```javascript
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
cd ../..
|
||||
yarn test --image
|
||||
```
|
||||
|
||||
Puppeteer is used to launch and grab screenshots of example pages, while jest is used to assert matching images. (just like integration tests)
|
||||
|
||||
@ -88,7 +95,7 @@ After that, the `run` directory content will be compared with `snapshots`. You c
|
||||
|
||||
yarn test --cli --update
|
||||
|
||||
In that case, please check the git diff before commiting to make sure it only contains the intended changes.
|
||||
In that case, please check the git diff before committing to make sure it only contains the intended changes.
|
||||
|
||||
#### 2c. Link `storybook` and any other required dependencies:
|
||||
|
||||
@ -133,7 +140,7 @@ A good way to do that is using the example `cra-kitchen-sink` app embedded in th
|
||||
|
||||
If you follow that process, you can then link to the github repository in the issue. See <https://github.com/storybooks/storybook/issues/708#issuecomment-290589886> for an example.
|
||||
|
||||
**NOTE**: If your issue involves a webpack config, create-react-app will prevent you from modifying the _app's_ webpack config, however you can still modify storybook's to mirror your app's version of storybook. Alternatively, use `yarn eject` in the CRA app to get a modifiable webpack config.
|
||||
**NOTE**: If your issue involves a webpack config, create-react-app will prevent you from modifying the _app's_ webpack config, however you can still modify storybook's to mirror your app's version of the storybook. Alternatively, use `yarn eject` in the CRA app to get a modifiable webpack config.
|
||||
|
||||
### Updating Tests
|
||||
|
||||
@ -157,7 +164,7 @@ We welcome your contributions. There are many ways you can help us. This is few
|
||||
- Work on [API](https://github.com/storybooks/storybook/labels/enhancement%3A%20api), [Addons](https://github.com/storybooks/storybook/labels/enhancement%3A%20addons), [UI](https://github.com/storybooks/storybook/labels/enhancement%3A%20ui) or [Webpack](https://github.com/storybooks/storybook/labels/enhancement%3A%20webpack) use enhancements and new [features](https://github.com/storybooks/storybook/labels/feature%20request).
|
||||
- Add more [tests](https://codecov.io/gh/storybooks/storybook/tree/master/packages) (specially for the [UI](https://codecov.io/gh/storybooks/storybook/tree/master/packages/storybook-ui/src)).
|
||||
|
||||
Before you submit a new PR, make you to run `yarn test`. Do not submit a PR if tests are failing. If you need any help, create an issue and ask.
|
||||
Before you submit a new PR, make sure you run `yarn test`. Do not submit a PR if tests are failing. If you need any help, create an issue and ask.
|
||||
|
||||
### Reviewing PRs
|
||||
|
||||
@ -238,7 +245,7 @@ _This method is slow_
|
||||
|
||||
Within the `examples` folder of the Storybook repo, you will find kitchen sink examples of storybook implementations for the various platforms that storybook supports.
|
||||
|
||||
Not only do these show many of the options and addons available, they are also automatically linked to all the development packages. We highly encourage you to use these to develop/test contributions on.
|
||||
Not only do these show many of the options and add-ons available, they are also automatically linked to all the development packages. We highly encourage you to use these to develop/test contributions on.
|
||||
|
||||
#### React and Vue
|
||||
|
||||
@ -249,8 +256,8 @@ Not only do these show many of the options and addons available, they are also a
|
||||
|
||||
#### Linking Storybook
|
||||
|
||||
Storybook is broken up into sub-projects that you can install as you need them. For this example we will be working with `@storybook/react`.
|
||||
**Note:** You need to `yarn link` from inside the sub project you are working on **_NOT_** the storybook root directory
|
||||
Storybook is broken up into sub-projects that you can install as you need them. For this example, we will be working with `@storybook/react`.
|
||||
**Note:** You need to `yarn link` from inside the subproject you are working on **_NOT_** the storybook root directory
|
||||
|
||||
1. `cd app/react`
|
||||
2. `yarn link`
|
||||
@ -264,7 +271,7 @@ _Make sure `yarn dev` is running_
|
||||
|
||||
##### 1. Setup storybook in your project
|
||||
|
||||
First we are going to install storyboook, then we are going to link `@storybook/react` into our project. This will replace `node_modules/@storybook/react` with a symlink to our local version of storybook.
|
||||
First we are going to install storybook, then we are going to link `@storybook/react` into our project. This will replace `node_modules/@storybook/react` with a symlink to our local version of storybook.
|
||||
|
||||
1. `getstorybook`
|
||||
2. `yarn storybook`
|
||||
@ -302,7 +309,7 @@ The current manual release sequence is as follows:
|
||||
|
||||
- Generate a changelog and verify the release by hand
|
||||
- Push the changelog to master or the release branch
|
||||
- Clean, build, and publish the release
|
||||
- Clean, build and publish the release
|
||||
- Cut and paste the changelog to the github release page, and mark it as a (pre-) release
|
||||
|
||||
This sequence applies to both releases and pre-releases, but differs slightly between the two.
|
||||
|
140
MIGRATION.md
140
MIGRATION.md
@ -2,38 +2,56 @@
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [From version 3.4.x to 4.0.x](#from-version-34x-to-40x)
|
||||
- [Keyboard shortcuts moved](#keyboard-shortcuts-moved)
|
||||
- [Removed addWithInfo](#removed-add-with-info)
|
||||
- [Removed RN addons](#removed-rn-addons)
|
||||
- [Storyshots changes](#storyshots-changes)
|
||||
- [Webpack 4](#webpack-4)
|
||||
- [Babel 7](#babel-7)
|
||||
- [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)
|
||||
- [Refactored Knobs](#refactored-knobs)
|
||||
- [Storyshots Jest configuration](#storyshots-jest-configuration)
|
||||
- [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)
|
||||
- [Keyboard shortcuts moved](#keyboard-shortcuts-moved)
|
||||
- [Removed addWithInfo](#removed-add-with-info)
|
||||
- [Removed RN addons](#removed-rn-addons)
|
||||
- [Storyshots changes](#storyshots-changes)
|
||||
- [Webpack 4](#webpack-4)
|
||||
- [Babel 7](#babel-7)
|
||||
- [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)
|
||||
- [Refactored Knobs](#refactored-knobs)
|
||||
- [Storyshots Jest configuration](#storyshots-jest-configuration)
|
||||
- [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 3.4.x to 4.0
|
||||
|
||||
With 4.0 as our first major release in over a year, we've collected a lot of cleanup tasks. All deprecations have been marked for months, so we hope that there will be no significant impact on your project.
|
||||
|
||||
### Generic addons
|
||||
|
||||
4.x introduces generic addon decorators that are not tied to specific view layers [#3555](https://github.com/storybooks/storybook/pull/3555). So for example:
|
||||
|
||||
```js
|
||||
import { number } from "@storybook/addon-knobs/react";
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```js
|
||||
import { number } from "@storybook/addon-knobs";
|
||||
```
|
||||
|
||||
### Knobs select ordering
|
||||
|
||||
4.0 also reversed the order of addon-knob's `select` knob keys/values, which had been called `selectV2` prior to this breaking change. See the knobs [package README](https://github.com/storybooks/storybook/blob/master/addons/knobs/README.md#select) for usage.
|
||||
|
||||
### Keyboard shortcuts moved
|
||||
|
||||
- Addon Panel to `Z`
|
||||
- Stories Panel to `X`
|
||||
- Show Search to `O`
|
||||
- Addon Panel right side to `G`
|
||||
- Addon Panel to `Z`
|
||||
- Stories Panel to `X`
|
||||
- Show Search to `O`
|
||||
- Addon Panel right side to `G`
|
||||
|
||||
### Removed addWithInfo
|
||||
|
||||
@ -45,16 +63,16 @@ The `@storybook/react-native` had built-in addons (`addon-actions` and `addon-li
|
||||
|
||||
### Storyshots Changes
|
||||
|
||||
1. `imageSnapshot` test function was extracted from `addon-storyshots`
|
||||
and moved to a new package - `addon-storyshots-puppeteer` that now will
|
||||
be dependant on puppeteer. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-puppeteer)
|
||||
2. `getSnapshotFileName` export was replaced with the `Stories2SnapsConverter`
|
||||
class that now can be overridden for a custom implementation of the
|
||||
snapshot-name generation. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#stories2snapsconverter)
|
||||
3. Storybook that was configured with Webpack's `require.context()` feature
|
||||
will need to add a babel plugin to polyfill this functionality.
|
||||
A possible plugin might be [babel-plugin-require-context-hook](https://github.com/smrq/babel-plugin-require-context-hook).
|
||||
[README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#configure-jest-to-work-with-webpacks-requirecontext)
|
||||
1. `imageSnapshot` test function was extracted from `addon-storyshots`
|
||||
and moved to a new package - `addon-storyshots-puppeteer` that now will
|
||||
be dependant on puppeteer. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-puppeteer)
|
||||
2. `getSnapshotFileName` export was replaced with the `Stories2SnapsConverter`
|
||||
class that now can be overridden for a custom implementation of the
|
||||
snapshot-name generation. [README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#stories2snapsconverter)
|
||||
3. Storybook that was configured with Webpack's `require.context()` feature
|
||||
will need to add a babel plugin to polyfill this functionality.
|
||||
A possible plugin might be [babel-plugin-require-context-hook](https://github.com/smrq/babel-plugin-require-context-hook).
|
||||
[README](https://github.com/storybooks/storybook/tree/master/addons/storyshots/storyshots-core#configure-jest-to-work-with-webpacks-requirecontext)
|
||||
|
||||
### Webpack 4
|
||||
|
||||
@ -82,9 +100,11 @@ Also read on if you're using `addon-knobs`: we advise an update to your code for
|
||||
### `babel-core` is now a peer dependency ([#2494](https://github.com/storybooks/storybook/pull/2494))
|
||||
|
||||
This affects you if you don't use babel in your project. You may need to add `babel-core` as dev dependency:
|
||||
|
||||
```
|
||||
npm install --save-dev babel-core
|
||||
```
|
||||
|
||||
This was done to support different major versions of babel.
|
||||
|
||||
### Base webpack config now contains vital plugins ([#1775](https://github.com/storybooks/storybook/pull/1775))
|
||||
@ -98,7 +118,7 @@ Knobs users: there was a bug in 3.2.x where using the knobs addon imported all f
|
||||
In the case of React or React-Native, import knobs like this:
|
||||
|
||||
```js
|
||||
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/react';
|
||||
import { withKnobs, text, boolean, number } from "@storybook/addon-knobs/react";
|
||||
```
|
||||
|
||||
In the case of Vue: `import { ... } from '@storybook/addon-knobs/vue';`
|
||||
@ -126,14 +146,14 @@ We're in the process of upgrading our addons APIs. As a first step, we've upgrad
|
||||
Here's an example of using Notes and Info in 3.2 with the new API.
|
||||
|
||||
```js
|
||||
storiesOf('composition', module)
|
||||
.add('new addons api',
|
||||
withInfo('see Notes panel for composition info')(
|
||||
withNotes({ text: 'Composition: Info(Notes())' })(context =>
|
||||
<MyComponent name={context.story} />
|
||||
)
|
||||
)
|
||||
);
|
||||
storiesOf("composition", module).add(
|
||||
"new addons api",
|
||||
withInfo("see Notes panel for composition info")(
|
||||
withNotes({ text: "Composition: Info(Notes())" })(context => (
|
||||
<MyComponent name={context.story} />
|
||||
))
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
It's not beautiful, but we'll be adding a more convenient/idiomatic way of using these [withX primitives](https://gist.github.com/shilman/792dc25550daa9c2bf37238f4ef7a398) in Storybook 3.3.
|
||||
@ -156,8 +176,8 @@ We have deprecated the use of `head.html` for including scripts/styles/etc. into
|
||||
|
||||
Now we use:
|
||||
|
||||
- `preview-head.html` for including extra content into the preview pane.
|
||||
- `manager-head.html` for including extra content into the manager window.
|
||||
- `preview-head.html` for including extra content into the preview pane.
|
||||
- `manager-head.html` for including extra content into the manager window.
|
||||
|
||||
[Read our docs](https://storybook.js.org/configurations/add-custom-head-tags/) for more details.
|
||||
|
||||
@ -239,17 +259,17 @@ We used to ship 2 addons with every single installation of storybook: `actions`
|
||||
|
||||
If you **are** using these addons, migrating is simple:
|
||||
|
||||
- add the addons you use to your `package.json`.
|
||||
- update your code:
|
||||
change `addons.js` like so:
|
||||
```js
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
||||
```
|
||||
change `x.story.js` like so:
|
||||
```js
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
```
|
||||
- add the addons you use to your `package.json`.
|
||||
- update your code:
|
||||
change `addons.js` like so:
|
||||
```js
|
||||
import "@storybook/addon-actions/register";
|
||||
import "@storybook/addon-links/register";
|
||||
```
|
||||
change `x.story.js` like so:
|
||||
```js
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { linkTo } from "@storybook/addon-links";
|
||||
```
|
||||
|
@ -7,7 +7,7 @@
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://www.browserstack.com/automate/public-build/<badge_key>)
|
||||
<!-- [](https://www.browserstack.com/automate/public-build/<badge_key>) -->
|
||||
|
||||
* * *
|
||||
|
||||
@ -16,7 +16,7 @@ It allows you to browse a component library, view the different states of each c
|
||||
|
||||
## Intro
|
||||
|
||||

|
||||

|
||||
|
||||
Storybook runs outside of your app. This allows you to develop UI components in isolation, which can improve component reuse, testability, and development speed. You can build quickly without having to worry about application-specific dependencies.
|
||||
|
||||
@ -77,6 +77,7 @@ For additional help, join us [in our Slack](https://now-examples-slackin-rrirkqo
|
||||
- [Mithril](app/mithril) <sup>alpha</sup>
|
||||
- [Marko](app/marko) <sup>alpha</sup>
|
||||
- [HTML](app/html) <sup>alpha</sup>
|
||||
- [Svelte](app/svelte) <sup>alpha</sup>
|
||||
|
||||
### Sub Projects
|
||||
|
||||
@ -115,6 +116,7 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
|
||||
- [Mithril](https://storybooks-mithril.netlify.com/)
|
||||
- [Marko](https://storybooks-marko.netlify.com/)
|
||||
- [HTML](https://storybooks-html.netlify.com/)
|
||||
- [Svelte](https://storybooks-svelte.netlify.com/)
|
||||
|
||||
### 3.4
|
||||
- [React Official](https://release-3-4--storybooks-official.netlify.com)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -25,14 +25,14 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/client-logger": "4.0.0-alpha.14",
|
||||
"@storybook/components": "4.0.0-alpha.14",
|
||||
"@storybook/core-events": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/client-logger": "4.0.0-alpha.16",
|
||||
"@storybook/components": "4.0.0-alpha.16",
|
||||
"@storybook/core-events": "4.0.0-alpha.16",
|
||||
"axe-core": "^3.0.3",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-emotion": "^9.1.3"
|
||||
"prop-types": "^15.6.2",
|
||||
"react-emotion": "^9.2.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
@ -35,21 +35,27 @@ class Panel extends Component {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.channel.on(CHECK_EVENT_ID, this.onUpdate);
|
||||
this.props.channel.on(STORY_RENDERED, this.requestCheck);
|
||||
this.props.channel.on(RERUN_EVENT_ID, this.requestCheck);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.on(CHECK_EVENT_ID, this.onUpdate);
|
||||
channel.on(STORY_RENDERED, this.requestCheck);
|
||||
channel.on(RERUN_EVENT_ID, this.requestCheck);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.active && this.props.active) {
|
||||
const { active } = this.props;
|
||||
|
||||
if (!prevProps.active && active) {
|
||||
this.requestCheck();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.channel.removeListener(CHECK_EVENT_ID, this.onUpdate);
|
||||
this.props.channel.removeListener(STORY_RENDERED, this.requestCheck);
|
||||
this.props.channel.removeListener(RERUN_EVENT_ID, this.requestCheck);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.removeListener(CHECK_EVENT_ID, this.onUpdate);
|
||||
channel.removeListener(STORY_RENDERED, this.requestCheck);
|
||||
channel.removeListener(RERUN_EVENT_ID, this.requestCheck);
|
||||
}
|
||||
|
||||
onUpdate = ({ passes, violations }) => {
|
||||
@ -60,8 +66,10 @@ class Panel extends Component {
|
||||
};
|
||||
|
||||
requestCheck = () => {
|
||||
if (this.props.active) {
|
||||
this.props.channel.emit(REQUEST_CHECK_EVENT_ID);
|
||||
const { channel, active } = this.props;
|
||||
|
||||
if (active) {
|
||||
channel.emit(REQUEST_CHECK_EVENT_ID);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -40,7 +40,9 @@ Element.propTypes = {
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
const Elements = ({ elements, passes }) => (
|
||||
<ol>
|
||||
{elements.map((element, index) => <Element passes={passes} element={element} key={index} />)}
|
||||
{elements.map((element, index) => (
|
||||
<Element passes={passes} element={element} key={index} />
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import { Icons } from '@storybook/components';
|
||||
|
||||
import Info from './Info';
|
||||
import Tags from './Tags';
|
||||
@ -55,7 +56,19 @@ class Item extends Component {
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<HeaderBar onClick={this.onToggle}>{item.description}</HeaderBar>
|
||||
<HeaderBar onClick={this.onToggle}>
|
||||
<Icons.ChevronRight
|
||||
size={10}
|
||||
color="#9DA5AB"
|
||||
style={{
|
||||
marginRight: '5px',
|
||||
marginBottom: '2px',
|
||||
transform: `rotate(${open ? 90 : 0}deg)`,
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
}}
|
||||
/>
|
||||
{item.description}
|
||||
</HeaderBar>
|
||||
{open && <Info item={item} />}
|
||||
{open && <Elements elements={item.nodes} passes={passes} />}
|
||||
{open && <Tags tags={item.tags} />}
|
||||
|
@ -61,7 +61,11 @@ Rule.propTypes = {
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
function Rules({ rules, passes }) {
|
||||
return (
|
||||
<List>{rules.map((rule, index) => <Rule passes={passes} rule={rule} key={index} />)}</List>
|
||||
<List>
|
||||
{rules.map((rule, index) => (
|
||||
<Rule passes={passes} rule={rule} key={index} />
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
Rules.propTypes = {
|
||||
|
@ -17,7 +17,13 @@ const Item = styled('div')(({ theme }) => ({
|
||||
}));
|
||||
|
||||
function Tags({ tags }) {
|
||||
return <Wrapper>{tags.map(tag => <Item key={tag}>{tag}</Item>)}</Wrapper>;
|
||||
return (
|
||||
<Wrapper>
|
||||
{tags.map(tag => (
|
||||
<Item key={tag}>{tag}</Item>
|
||||
))}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
Tags.propTypes = {
|
||||
tags: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook Addon Actions
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
---
|
||||
|
||||
Storybook Addon Actions can be used to display data received by event handlers in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -20,19 +20,19 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/components": "4.0.0-alpha.14",
|
||||
"@storybook/core-events": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/components": "4.0.0-alpha.16",
|
||||
"@storybook/core-events": "4.0.0-alpha.16",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"emotion-theming": "^9.1.2",
|
||||
"emotion-theming": "^9.2.6",
|
||||
"global": "^4.3.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"make-error": "^1.3.4",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-emotion": "^9.1.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-emotion": "^9.2.6",
|
||||
"react-inspector": "^2.3.0",
|
||||
"uuid": "^3.2.1"
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
4
addons/actions/src/constants.js
Normal file
4
addons/actions/src/constants.js
Normal file
@ -0,0 +1,4 @@
|
||||
export const ADDON_ID = 'storybook/actions';
|
||||
export const PANEL_ID = `${ADDON_ID}/actions-panel`;
|
||||
export const EVENT_ID = `${ADDON_ID}/action-event`;
|
||||
export const CYCLIC_KEY = '$___storybook.isCyclic';
|
@ -6,8 +6,8 @@ import deepEqual from 'deep-equal';
|
||||
import { CYCLIC_KEY, retrocycle } from '../../lib';
|
||||
import { isObject } from '../../lib/util';
|
||||
|
||||
import ActionLoggerComponent from '../../components/ActionLogger/';
|
||||
import { EVENT_ID } from '../../';
|
||||
import ActionLoggerComponent from '../../components/ActionLogger';
|
||||
import { EVENT_ID } from '../..';
|
||||
|
||||
export default class ActionLogger extends React.Component {
|
||||
constructor(props, ...args) {
|
||||
@ -18,12 +18,16 @@ export default class ActionLogger extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.channel.on(EVENT_ID, this._actionListener);
|
||||
this.stopListeningOnStory = this.props.api.onStory(this._storyChangeListener);
|
||||
const { channel, api } = this.props;
|
||||
|
||||
channel.on(EVENT_ID, this._actionListener);
|
||||
this.stopListeningOnStory = api.onStory(this._storyChangeListener);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.channel.removeListener(EVENT_ID, this._actionListener);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.removeListener(EVENT_ID, this._actionListener);
|
||||
if (this.stopListeningOnStory) {
|
||||
this.stopListeningOnStory();
|
||||
}
|
||||
@ -37,9 +41,11 @@ export default class ActionLogger extends React.Component {
|
||||
}
|
||||
|
||||
addAction(action) {
|
||||
let { actions = [] } = this.state;
|
||||
actions = [...actions];
|
||||
|
||||
action.data.args = action.data.args.map(arg => retrocycle(arg)); // eslint-disable-line
|
||||
const isCyclic = !!action.data.args.find(arg => isObject(arg) && arg[CYCLIC_KEY]);
|
||||
const actions = [...this.state.actions];
|
||||
const previous = actions.length && actions[0];
|
||||
|
||||
if (previous && !isCyclic && deepEqual(previous.data, action.data, { strict: true })) {
|
||||
@ -56,9 +62,10 @@ export default class ActionLogger extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { actions = [] } = this.state;
|
||||
const { active } = this.props;
|
||||
const props = {
|
||||
actions: this.state.actions,
|
||||
actions,
|
||||
onClear: () => this.clearActions(),
|
||||
};
|
||||
return active ? <ActionLoggerComponent {...props} /> : null;
|
||||
|
@ -8,8 +8,16 @@ import {
|
||||
} from './preview';
|
||||
|
||||
// addons, panels and events get unique names using a prefix
|
||||
export const ADDON_ID = 'storybook/actions';
|
||||
export const PANEL_ID = `${ADDON_ID}/actions-panel`;
|
||||
export const EVENT_ID = `${ADDON_ID}/action-event`;
|
||||
import { ADDON_ID, PANEL_ID, EVENT_ID } from './constants';
|
||||
|
||||
export { action, actions, decorate, configureActions, decorateAction, withActions };
|
||||
export {
|
||||
action,
|
||||
actions,
|
||||
decorate,
|
||||
configureActions,
|
||||
decorateAction,
|
||||
withActions,
|
||||
ADDON_ID,
|
||||
PANEL_ID,
|
||||
EVENT_ID,
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { DecycleError } from './errors';
|
||||
|
||||
import { getPropertiesList, typeReplacer, omitProperty } from './util';
|
||||
|
||||
import { CYCLIC_KEY } from './';
|
||||
import { CYCLIC_KEY } from '../constants';
|
||||
|
||||
import { objectType } from './types';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import reviver from './reviver';
|
||||
import { muteProperty } from './util';
|
||||
import { CYCLIC_KEY } from './';
|
||||
import { CYCLIC_KEY } from '../constants';
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const pathReg = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\([\\"/bfnrt]|u[0-9a-zA-Z]{4}))*")])*$/;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import dateType from '../';
|
||||
import dateType from '..';
|
||||
|
||||
const date = new Date(1512137134873);
|
||||
const isoString = date.toISOString();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import functionType from '../';
|
||||
import functionType from '..';
|
||||
import reservedKeywords from '../reservedKeywords';
|
||||
import createFunction from '../createFunction';
|
||||
import createBoundFunction from '../createBoundFunction';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import infinityType from '../';
|
||||
import infinityType from '..';
|
||||
|
||||
describe('Infinity', () => {
|
||||
it('Recognizes Infinity', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import nanType from '../';
|
||||
import nanType from '..';
|
||||
|
||||
describe('NaN', () => {
|
||||
it('Recognizes NaN', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import objectType from '../';
|
||||
import objectType from '..';
|
||||
import { DEPTH_KEY } from '../configureDepth';
|
||||
|
||||
describe('Object', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import regExpType from '../';
|
||||
import regExpType from '..';
|
||||
|
||||
const regExp = /aRegExp/g;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import symbolType from '../';
|
||||
import symbolType from '..';
|
||||
|
||||
const symbol = Symbol('S');
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import undefinedType from '../';
|
||||
import undefinedType from '..';
|
||||
|
||||
describe('undefined', () => {
|
||||
it('Recognizes undefined', () => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 } from '.';
|
||||
|
||||
export function register() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { action, configureActions } from '../../';
|
||||
import { action, configureActions } from '../..';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { config } from '../configureActions';
|
||||
import { configureActions } from '../../';
|
||||
import { configureActions } from '../..';
|
||||
|
||||
describe('Configure Actions', () => {
|
||||
it('can configure actions', () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import addons from '@storybook/addons';
|
||||
import uuid from 'uuid/v1';
|
||||
import { action } from '../';
|
||||
import { action } from '..';
|
||||
import { undefinedType, symbolType } from '../../lib/types';
|
||||
|
||||
jest.mock('uuid/v1');
|
||||
|
@ -1,6 +1,6 @@
|
||||
import uuid from 'uuid/v1';
|
||||
import addons from '@storybook/addons';
|
||||
import { EVENT_ID } from '../';
|
||||
import { EVENT_ID } from '../constants';
|
||||
import { canConfigureName, prepareArguments } from '../lib/util';
|
||||
import { config } from './configureActions';
|
||||
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook Addon Backgrounds
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
---
|
||||
|
||||
Storybook Background Addon can be used to change background colors inside the preview in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "A storybook addon to show different backgrounds for your preview",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -24,15 +24,15 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/core-events": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/core-events": "4.0.0-alpha.16",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-emotion": "^9.1.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-emotion": "^9.2.6",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^2.5.16"
|
||||
"vue": "^2.5.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import Events from './events';
|
||||
import Events from './constants';
|
||||
import Swatch from './Swatch';
|
||||
|
||||
const Wrapper = styled('div')({
|
||||
@ -76,19 +75,11 @@ export default class BackgroundPanel extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { channel } = props;
|
||||
|
||||
// A channel is explicitly passed in for testing
|
||||
if (channel) {
|
||||
this.channel = channel;
|
||||
} else {
|
||||
this.channel = addons.getChannel();
|
||||
}
|
||||
|
||||
this.state = { backgrounds: [] };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { api, channel } = this.props;
|
||||
this.iframe = document.getElementById(storybookIframe);
|
||||
|
||||
if (!this.iframe) {
|
||||
@ -99,29 +90,33 @@ export default class BackgroundPanel extends Component {
|
||||
this.iframe.style[prop] = style.iframe[prop];
|
||||
});
|
||||
|
||||
const { api } = this.props;
|
||||
channel.on(Events.SET, data => {
|
||||
const backgrounds = [...data];
|
||||
|
||||
this.channel.on(Events.SET, backgrounds => {
|
||||
this.setState({ backgrounds });
|
||||
const currentBackground = api.getQueryParam('background');
|
||||
const current = api.getQueryParam('background');
|
||||
const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0];
|
||||
|
||||
if (currentBackground && backgrounds.some(bg => bg.value === currentBackground)) {
|
||||
this.updateIframe(currentBackground);
|
||||
} else if (backgrounds.filter(x => x.default).length) {
|
||||
const defaultBgs = backgrounds.filter(x => x.default);
|
||||
this.updateIframe(defaultBgs[0].value);
|
||||
// debugger;
|
||||
|
||||
if (current && backgrounds.find(bg => bg.value === current)) {
|
||||
this.updateIframe(current);
|
||||
} else if (defaultOrFirst) {
|
||||
this.updateIframe(defaultOrFirst.value);
|
||||
api.setQueryParams({ background: defaultOrFirst.value });
|
||||
}
|
||||
});
|
||||
|
||||
this.channel.on(Events.UNSET, () => {
|
||||
channel.on(Events.UNSET, () => {
|
||||
this.setState({ backgrounds: [] });
|
||||
this.updateIframe('none');
|
||||
});
|
||||
}
|
||||
|
||||
setBackgroundFromSwatch = background => {
|
||||
const { api } = this.props;
|
||||
this.updateIframe(background);
|
||||
this.props.api.setQueryParams({ background });
|
||||
api.setQueryParams({ background });
|
||||
};
|
||||
|
||||
updateIframe(background) {
|
||||
@ -130,7 +125,8 @@ export default class BackgroundPanel extends Component {
|
||||
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
const backgrounds = [...this.state.backgrounds];
|
||||
const { backgrounds = [] } = this.state;
|
||||
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import { shallow, mount } from 'enzyme';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import BackgroundPanel from '../BackgroundPanel';
|
||||
import Events from '../events';
|
||||
import Events from '../constants';
|
||||
|
||||
const backgrounds = [
|
||||
{ name: 'black', value: '#000000' },
|
||||
{ name: 'black', value: '#000000', default: true },
|
||||
{ name: 'secondary', value: 'rgb(123,123,123)' },
|
||||
{ name: 'tertiary', value: 'rgba(123,123,123,.5)' },
|
||||
{ name: 'An image', value: 'url(http://placehold.it/350x150)' },
|
||||
|
7
addons/backgrounds/src/constants.js
Normal file
7
addons/backgrounds/src/constants.js
Normal file
@ -0,0 +1,7 @@
|
||||
export const ADDON_ID = 'storybook-addon-background';
|
||||
export const PANEL_ID = `${ADDON_ID}/background-panel`;
|
||||
|
||||
export default {
|
||||
SET: `${ADDON_ID}:set`,
|
||||
UNSET: `${ADDON_ID}:unset`,
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
const ADDON_ID = 'backgrounds';
|
||||
|
||||
export default {
|
||||
SET: `${ADDON_ID}:set`,
|
||||
UNSET: `${ADDON_ID}:unset`,
|
||||
};
|
@ -2,7 +2,7 @@ import addons, { makeDecorator } from '@storybook/addons';
|
||||
import CoreEvents from '@storybook/core-events';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
import Events from './events';
|
||||
import Events from './constants';
|
||||
|
||||
let prevBackgrounds;
|
||||
|
||||
@ -12,12 +12,13 @@ const subscription = () => () => {
|
||||
};
|
||||
|
||||
export const withBackgrounds = makeDecorator({
|
||||
name: 'backgrounds',
|
||||
name: 'withBackgrounds',
|
||||
parameterName: 'backgrounds',
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: true,
|
||||
wrapper: (getStory, context, { options, parameters }) => {
|
||||
const backgrounds = parameters || options;
|
||||
const data = parameters || options || [];
|
||||
const backgrounds = Array.isArray(data) ? data : Object.values(data);
|
||||
|
||||
if (backgrounds.length === 0) {
|
||||
return getStory(context);
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import { ADDON_ID, PANEL_ID } from './constants';
|
||||
import BackgroundPanel from './BackgroundPanel';
|
||||
|
||||
const ADDON_ID = 'storybook-addon-background';
|
||||
const PANEL_ID = `${ADDON_ID}/background-panel`;
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.addPanel(PANEL_ID, {
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook Centered Decorator
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook Centered Decorator can be used to center components inside the preview in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
@ -56,6 +47,25 @@ storiesOf('MyComponent', module)
|
||||
}));
|
||||
```
|
||||
|
||||
example for Svelte:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/svelte';
|
||||
import Centered from '@storybook/addon-centered/svelte';
|
||||
|
||||
import Component from '../Component.svelte';
|
||||
|
||||
storiesOf('Addon|Centered', module)
|
||||
.addDecorator(Centered)
|
||||
.add('rounded', () => ({
|
||||
Component,
|
||||
data: {
|
||||
rounded: true,
|
||||
text: "Look, I'm centered!",
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
@ -105,7 +115,7 @@ storiesOf('Addon|Centered', module)
|
||||
)
|
||||
.addDecorator(centered)
|
||||
.add('centered template', () => ({
|
||||
template: `<storybook-button-component
|
||||
template: `<storybook-button-component
|
||||
[text]="text" (onClick)="onClick($event)">
|
||||
</storybook-button-component>`,
|
||||
props: {
|
||||
@ -146,6 +156,19 @@ configure(function () {
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Svelte:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/svelte';
|
||||
import Centered from '@storybook/addon-centered/svelte';
|
||||
|
||||
addDecorator(Centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-centered",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "Storybook decorator to center components",
|
||||
"license": "MIT",
|
||||
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
|
||||
|
16
addons/centered/src/components/Centered.svelte
Normal file
16
addons/centered/src/components/Centered.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="svelte-centered-wrapper" style="{style}">
|
||||
<div class="svelte-centered-container" style="{innerStyle}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
style: '',
|
||||
innerStyle: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
21
addons/centered/src/helpers/json2CSS.js
Normal file
21
addons/centered/src/helpers/json2CSS.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { document } from 'global';
|
||||
|
||||
/**
|
||||
* Not all frameworks support an object for the style attribute but we want all to
|
||||
* consume `styles.json`. Since `styles.json` uses standard style properties for keys,
|
||||
* we can just set them on an element and then get the string result of that element's
|
||||
* `style` attribute. This also means that invalid styles are filtered out.
|
||||
*
|
||||
* @param {Object} jsonStyles
|
||||
* @returns {string}
|
||||
* @see https://stackoverflow.com/questions/38533544/jsx-css-to-inline-styles
|
||||
*/
|
||||
export default function jsonToCss(jsonStyles) {
|
||||
const frag = document.createElement('div');
|
||||
|
||||
Object.keys(jsonStyles).forEach(key => {
|
||||
frag.style[key] = jsonStyles[key];
|
||||
});
|
||||
|
||||
return frag.getAttribute('style');
|
||||
}
|
25
addons/centered/src/svelte.js
Normal file
25
addons/centered/src/svelte.js
Normal file
@ -0,0 +1,25 @@
|
||||
import Centered from './components/Centered.svelte';
|
||||
import styles from './styles';
|
||||
import json2CSS from './helpers/json2CSS';
|
||||
|
||||
const centeredStyles = {
|
||||
/** @type {string} */
|
||||
style: json2CSS(styles.style),
|
||||
/** @type {string} */
|
||||
innerStyle: json2CSS(styles.innerStyle),
|
||||
};
|
||||
|
||||
/**
|
||||
* This functionality works by passing the svelte story component into another
|
||||
* svelte component that has the single purpose of centering the story component
|
||||
* using a wrapper and container.
|
||||
*
|
||||
* We use the special element <svelte:component /> to achieve this.
|
||||
*
|
||||
* @see https://svelte.technology/guide#svelte-component
|
||||
*/
|
||||
export default function(storyFn) {
|
||||
const { Component: OriginalComponent, data, on } = storyFn();
|
||||
|
||||
return { Component: OriginalComponent, data, on, Wrapper: Centered, WrapperData: centeredStyles };
|
||||
}
|
1
addons/centered/svelte.js
Normal file
1
addons/centered/svelte.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/svelte');
|
@ -1,14 +1,5 @@
|
||||
# Storybook Addon Events
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
This [storybook](https://storybooks.js.org) ([source](https://github.com/storybooks/storybook)) addon allows you to add events for your stories.
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-events",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "Add events to your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -19,13 +19,13 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/core-events": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/core-events": "4.0.0-alpha.16",
|
||||
"format-json": "^1.0.3",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-emotion": "^9.1.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-emotion": "^9.2.6",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"react-textarea-autosize": "^6.1.0",
|
||||
"react-textarea-autosize": "^7.0.4",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -101,20 +101,6 @@ class Item extends Component {
|
||||
isTextAreaShowed: false,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps = ({ payload }, { prevPayload }) => {
|
||||
if (payload !== prevPayload) {
|
||||
const payloadString = json.plain(payload);
|
||||
|
||||
return {
|
||||
failed: false,
|
||||
payload: getJSONFromString(payloadString),
|
||||
payloadString,
|
||||
prevPayload,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
onChange = ({ target: { value } }) => {
|
||||
const newState = {
|
||||
payloadString: value,
|
||||
@ -131,9 +117,12 @@ class Item extends Component {
|
||||
};
|
||||
|
||||
onEmitClick = () => {
|
||||
this.props.onEmit({
|
||||
name: this.props.name,
|
||||
payload: this.state.payload,
|
||||
const { onEmit, name } = this.props;
|
||||
const { payload } = this.state;
|
||||
|
||||
onEmit({
|
||||
name,
|
||||
payload,
|
||||
});
|
||||
};
|
||||
|
||||
@ -143,9 +132,23 @@ class Item extends Component {
|
||||
}));
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps = ({ payload }, { prevPayload }) => {
|
||||
if (payload !== prevPayload) {
|
||||
const payloadString = json.plain(payload);
|
||||
|
||||
return {
|
||||
failed: false,
|
||||
payload: getJSONFromString(payloadString),
|
||||
payloadString,
|
||||
prevPayload,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title, name } = this.props;
|
||||
const { failed, isTextAreaShowed } = this.state;
|
||||
const { failed, isTextAreaShowed, payloadString } = this.state;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
@ -158,7 +161,7 @@ class Item extends Component {
|
||||
<StyledTextarea
|
||||
shown={isTextAreaShowed}
|
||||
failed={failed}
|
||||
value={this.state.payloadString}
|
||||
value={payloadString}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
{isTextAreaShowed ? (
|
||||
|
@ -28,11 +28,15 @@ export default class Events extends Component {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.channel.on(EVENTS.ADD, this.onAdd);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.on(EVENTS.ADD, this.onAdd);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.channel.removeListener(EVENTS.ADD, this.onAdd);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.removeListener(EVENTS.ADD, this.onAdd);
|
||||
}
|
||||
|
||||
onAdd = events => {
|
||||
@ -40,7 +44,9 @@ export default class Events extends Component {
|
||||
};
|
||||
|
||||
onEmit = event => {
|
||||
this.props.channel.emit(EVENTS.EMIT, event);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.emit(EVENTS.EMIT, event);
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -48,7 +54,9 @@ export default class Events extends Component {
|
||||
const { active } = this.props;
|
||||
return active ? (
|
||||
<Wrapper>
|
||||
{events.map(event => <Event key={event.name} {...event} onEmit={this.onEmit} />)}
|
||||
{events.map(event => (
|
||||
<Event key={event.name} {...event} onEmit={this.onEmit} />
|
||||
))}
|
||||
</Wrapper>
|
||||
) : null;
|
||||
}
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook GraphiQL Addon
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook GraphQL Addon can be used to display the GraphiQL IDE with example queries in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-graphql",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "Storybook addon to display the GraphiQL IDE",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -23,7 +23,7 @@
|
||||
"global": "^4.3.2",
|
||||
"graphiql": "^0.11.11",
|
||||
"graphql": "^0.13.2",
|
||||
"prop-types": "^15.6.1"
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './style';
|
||||
|
||||
export default function FullScreen(props) {
|
||||
return <div style={style.wrapper}>{props.children}</div>;
|
||||
export default function FullScreen({ children }) {
|
||||
return <div style={style.wrapper}>{children}</div>;
|
||||
}
|
||||
|
||||
FullScreen.defaultProps = { children: null };
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook Info Addon
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
---
|
||||
|
||||
Storybook Info Addon will show additional information for your stories in [Storybook](https://storybook.js.org).
|
||||
Useful when you want to display usage or other types of documentation alongside your story.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-info",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "A Storybook addon to show additional information for your stories.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,21 +13,21 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/client-logger": "4.0.0-alpha.14",
|
||||
"@storybook/components": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/client-logger": "4.0.0-alpha.16",
|
||||
"@storybook/components": "4.0.0-alpha.16",
|
||||
"core-js": "2.5.7",
|
||||
"global": "^4.3.2",
|
||||
"marksy": "^6.0.3",
|
||||
"nested-object-assign": "^1.0.1",
|
||||
"prop-types": "^15.6.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-addons-create-fragment": "^15.5.3",
|
||||
"react-emotion": "^9.1.3",
|
||||
"react-emotion": "^9.2.6",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-test-renderer": "^16.4.0"
|
||||
"react-test-renderer": "^16.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
.emotion-4 {
|
||||
.emotion-2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -22,7 +22,7 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.emotion-2 {
|
||||
.emotion-1 {
|
||||
overflow: hidden;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
@ -38,12 +38,12 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.emotion-2:hover {
|
||||
.emotion-1:hover {
|
||||
background-color: #f4f7fa;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.emotion-2:active {
|
||||
.emotion-1:active {
|
||||
background-color: #e9ecef;
|
||||
border-color: #ccc;
|
||||
}
|
||||
@ -124,8 +124,8 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
function func(x) {
|
||||
return x + 1;
|
||||
}
|
||||
return x + 1;
|
||||
}
|
||||
</h1>
|
||||
<h2>
|
||||
[object Object]
|
||||
@ -307,7 +307,7 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
>
|
||||
<Styled(pre)>
|
||||
<pre
|
||||
className="emotion-4 emotion-5"
|
||||
className="emotion-2"
|
||||
>
|
||||
<div>
|
||||
<Node
|
||||
@ -1220,14 +1220,14 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
styles={Object {}}
|
||||
>
|
||||
<button
|
||||
className="emotion-2 emotion-3"
|
||||
className="emotion-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Styled(div)
|
||||
toggled={false}
|
||||
>
|
||||
<div
|
||||
className="emotion-0 emotion-1"
|
||||
className="emotion-0"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
@ -1250,55 +1250,6 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
</Styled(pre)>
|
||||
</Pre>
|
||||
</div>
|
||||
<div>
|
||||
<h1
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #EEE",
|
||||
"fontSize": "25px",
|
||||
"margin": "20px 0 0 0",
|
||||
"padding": "0 0 5px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
Prop Types
|
||||
</h1>
|
||||
<div
|
||||
key="TestComponent_0"
|
||||
>
|
||||
<h2
|
||||
style={
|
||||
Object {
|
||||
"margin": "20px 0 0 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
"
|
||||
TestComponent
|
||||
" Component
|
||||
</h2>
|
||||
<Component
|
||||
excludedPropTypes={Array []}
|
||||
maxPropArrayLength={3}
|
||||
maxPropObjectKeys={3}
|
||||
maxPropStringLength={50}
|
||||
type={[Function]}
|
||||
>
|
||||
<PropTable
|
||||
excludedPropTypes={Array []}
|
||||
maxPropArrayLength={3}
|
||||
maxPropObjectKeys={3}
|
||||
maxPropStringLength={50}
|
||||
propDefinitions={Array []}
|
||||
type={[Function]}
|
||||
>
|
||||
<small>
|
||||
No propTypes defined!
|
||||
</small>
|
||||
</PropTable>
|
||||
</Component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1308,7 +1259,7 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
|
||||
`;
|
||||
|
||||
exports[`addon Info should render <Info /> and markdown 1`] = `
|
||||
.emotion-4 {
|
||||
.emotion-2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -1329,7 +1280,7 @@ exports[`addon Info should render <Info /> and markdown 1`] = `
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.emotion-2 {
|
||||
.emotion-1 {
|
||||
overflow: hidden;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
@ -1345,12 +1296,12 @@ exports[`addon Info should render <Info /> and markdown 1`] = `
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.emotion-2:hover {
|
||||
.emotion-1:hover {
|
||||
background-color: #f4f7fa;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.emotion-2:active {
|
||||
.emotion-1:active {
|
||||
background-color: #e9ecef;
|
||||
border-color: #ccc;
|
||||
}
|
||||
@ -1433,8 +1384,8 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)"
|
||||
<div>
|
||||
<h1>
|
||||
function func(x) {
|
||||
return x + 1;
|
||||
}
|
||||
return x + 1;
|
||||
}
|
||||
</h1>
|
||||
<h2>
|
||||
[object Object]
|
||||
@ -1707,7 +1658,7 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)"
|
||||
>
|
||||
<Styled(pre)>
|
||||
<pre
|
||||
className="emotion-4 emotion-5"
|
||||
className="emotion-2"
|
||||
>
|
||||
<div>
|
||||
<Node
|
||||
@ -2620,14 +2571,14 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)"
|
||||
styles={Object {}}
|
||||
>
|
||||
<button
|
||||
className="emotion-2 emotion-3"
|
||||
className="emotion-1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Styled(div)
|
||||
toggled={false}
|
||||
>
|
||||
<div
|
||||
className="emotion-0 emotion-1"
|
||||
className="emotion-0"
|
||||
>
|
||||
<div
|
||||
style={
|
||||
@ -2650,55 +2601,6 @@ containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)"
|
||||
</Styled(pre)>
|
||||
</Pre>
|
||||
</div>
|
||||
<div>
|
||||
<h1
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #EEE",
|
||||
"fontSize": "25px",
|
||||
"margin": "20px 0 0 0",
|
||||
"padding": "0 0 5px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
Prop Types
|
||||
</h1>
|
||||
<div
|
||||
key="TestComponent_0"
|
||||
>
|
||||
<h2
|
||||
style={
|
||||
Object {
|
||||
"margin": "20px 0 0 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
"
|
||||
TestComponent
|
||||
" Component
|
||||
</h2>
|
||||
<Component
|
||||
excludedPropTypes={Array []}
|
||||
maxPropArrayLength={3}
|
||||
maxPropObjectKeys={3}
|
||||
maxPropStringLength={50}
|
||||
type={[Function]}
|
||||
>
|
||||
<PropTable
|
||||
excludedPropTypes={Array []}
|
||||
maxPropArrayLength={3}
|
||||
maxPropObjectKeys={3}
|
||||
maxPropStringLength={50}
|
||||
propDefinitions={Array []}
|
||||
type={[Function]}
|
||||
>
|
||||
<small>
|
||||
No propTypes defined!
|
||||
</small>
|
||||
</PropTable>
|
||||
</Component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -76,7 +76,10 @@ export default function Node(props) {
|
||||
if (!children) {
|
||||
return (
|
||||
<div style={containerStyleCopy}>
|
||||
<span style={tagStyle}><{name}</span>
|
||||
<span style={tagStyle}>
|
||||
<
|
||||
{name}
|
||||
</span>
|
||||
<Props
|
||||
node={node}
|
||||
singleLine
|
||||
@ -94,7 +97,10 @@ export default function Node(props) {
|
||||
return (
|
||||
<div>
|
||||
<div style={containerStyleCopy}>
|
||||
<span style={tagStyle}><{name}</span>
|
||||
<span style={tagStyle}>
|
||||
<
|
||||
{name}
|
||||
</span>
|
||||
<Props
|
||||
node={node}
|
||||
maxPropsIntoLine={maxPropsIntoLine}
|
||||
@ -115,7 +121,11 @@ export default function Node(props) {
|
||||
/>
|
||||
))}
|
||||
<div style={containerStyleCopy}>
|
||||
<span style={tagStyle}></{name}></span>
|
||||
<span style={tagStyle}>
|
||||
</
|
||||
{name}
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,9 +9,16 @@ const stylesheet = {
|
||||
};
|
||||
|
||||
export default function Props(props) {
|
||||
const { maxPropsIntoLine, maxPropArrayLength, maxPropObjectKeys, maxPropStringLength } = props;
|
||||
const nodeProps = props.node.props;
|
||||
const { defaultProps } = props.node.type;
|
||||
const {
|
||||
maxPropsIntoLine,
|
||||
maxPropArrayLength,
|
||||
maxPropObjectKeys,
|
||||
maxPropStringLength,
|
||||
node,
|
||||
singleLine,
|
||||
} = props;
|
||||
const nodeProps = node.props;
|
||||
const { defaultProps } = node.type;
|
||||
if (!nodeProps || typeof nodeProps !== 'object') {
|
||||
return <span />;
|
||||
}
|
||||
@ -26,7 +33,7 @@ export default function Props(props) {
|
||||
);
|
||||
|
||||
const breakIntoNewLines = names.length > maxPropsIntoLine;
|
||||
const endingSpace = props.singleLine ? ' ' : '';
|
||||
const endingSpace = singleLine ? ' ' : '';
|
||||
|
||||
const items = [];
|
||||
names.forEach((name, i) => {
|
||||
@ -34,7 +41,8 @@ export default function Props(props) {
|
||||
<span key={name}>
|
||||
{breakIntoNewLines ? (
|
||||
<span>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
</span>
|
||||
) : (
|
||||
' '
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint no-underscore-dangle: 0 */
|
||||
|
||||
import React, { createElement } from 'react';
|
||||
import React, { Component, createElement } from 'react';
|
||||
import { polyfill } from 'react-lifecycles-compat';
|
||||
import PropTypes from 'prop-types';
|
||||
import global from 'global';
|
||||
@ -16,7 +16,7 @@ const { STORYBOOK_REACT_CLASSES } = global;
|
||||
|
||||
const getName = type => type.displayName || type.name;
|
||||
|
||||
const stylesheet = {
|
||||
const stylesheetBase = {
|
||||
button: {
|
||||
base: {
|
||||
fontFamily: 'sans-serif',
|
||||
@ -101,29 +101,34 @@ const stylesheet = {
|
||||
},
|
||||
};
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
class Story extends Component {
|
||||
constructor(props, ...args) {
|
||||
super(props, ...args);
|
||||
this.state = {
|
||||
open: false,
|
||||
};
|
||||
this.marksy = marksy({
|
||||
createElement,
|
||||
elements: this.props.components,
|
||||
elements: props.components,
|
||||
});
|
||||
}
|
||||
|
||||
_renderStory() {
|
||||
return <div style={this.state.stylesheet.infoStory}>{this.props.children}</div>;
|
||||
const { stylesheet } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
return <div style={stylesheet.infoStory}>{children}</div>;
|
||||
}
|
||||
|
||||
_renderInline() {
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this._renderInlineHeader()}
|
||||
{this._renderStory()}
|
||||
<div style={this.state.stylesheet.infoPage}>
|
||||
<div style={this.state.stylesheet.infoBody}>
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>
|
||||
{this._getInfoContent()}
|
||||
{this._getComponentDescription()}
|
||||
{this._getSourceCode()}
|
||||
@ -135,25 +140,30 @@ class Story extends React.Component {
|
||||
}
|
||||
|
||||
_renderInlineHeader() {
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
const infoHeader = this._getInfoHeader();
|
||||
|
||||
return (
|
||||
infoHeader && (
|
||||
<div style={this.state.stylesheet.infoPage}>
|
||||
<div style={this.state.stylesheet.infoBody}>{infoHeader}</div>
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>{infoHeader}</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_renderOverlay() {
|
||||
const { stylesheet, open } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
const buttonStyle = {
|
||||
...this.state.stylesheet.button.base,
|
||||
...this.state.stylesheet.button.topRight,
|
||||
...stylesheet.button.base,
|
||||
...stylesheet.button.topRight,
|
||||
};
|
||||
|
||||
const infoStyle = Object.assign({}, this.state.stylesheet.info);
|
||||
if (!this.state.open) {
|
||||
const infoStyle = Object.assign({}, stylesheet.info);
|
||||
if (!open) {
|
||||
infoStyle.display = 'none';
|
||||
}
|
||||
|
||||
@ -169,7 +179,7 @@ class Story extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={this.state.stylesheet.children}>{this.props.children}</div>
|
||||
<div style={stylesheet.children}>{children}</div>
|
||||
<button type="button" style={buttonStyle} onClick={openOverlay}>
|
||||
Show Info
|
||||
</button>
|
||||
@ -177,8 +187,8 @@ class Story extends React.Component {
|
||||
<button type="button" style={buttonStyle} onClick={closeOverlay}>
|
||||
×
|
||||
</button>
|
||||
<div style={this.state.stylesheet.infoPage}>
|
||||
<div style={this.state.stylesheet.infoBody}>
|
||||
<div style={stylesheet.infoPage}>
|
||||
<div style={stylesheet.infoBody}>
|
||||
{this._getInfoHeader()}
|
||||
{this._getInfoContent()}
|
||||
{this._getComponentDescription()}
|
||||
@ -192,38 +202,36 @@ class Story extends React.Component {
|
||||
}
|
||||
|
||||
_getInfoHeader() {
|
||||
if (!this.props.context || !this.props.showHeader) {
|
||||
const { stylesheet } = this.state;
|
||||
const { context, showHeader } = this.props;
|
||||
|
||||
if (!context || !showHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={this.state.stylesheet.header.body}>
|
||||
<h1 style={this.state.stylesheet.header.h1}>{this.props.context.kind}</h1>
|
||||
<h2 style={this.state.stylesheet.header.h2}>{this.props.context.story}</h2>
|
||||
<div style={stylesheet.header.body}>
|
||||
<h1 style={stylesheet.header.h1}>{context.kind}</h1>
|
||||
<h2 style={stylesheet.header.h2}>{context.story}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_getInfoContent() {
|
||||
if (!this.props.info) {
|
||||
const { info, showInline } = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
if (!info) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (React.isValidElement(this.props.info)) {
|
||||
if (React.isValidElement(info)) {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
this.props.showInline
|
||||
? this.state.stylesheet.jsxInfoContent
|
||||
: this.state.stylesheet.infoContent
|
||||
}
|
||||
>
|
||||
{this.props.info}
|
||||
</div>
|
||||
<div style={showInline ? stylesheet.jsxInfoContent : stylesheet.infoContent}>{info}</div>
|
||||
);
|
||||
}
|
||||
|
||||
const lines = this.props.info.split('\n');
|
||||
const lines = info.split('\n');
|
||||
while (lines[0].trim() === '') {
|
||||
lines.shift();
|
||||
}
|
||||
@ -233,15 +241,16 @@ class Story extends React.Component {
|
||||
padding = matches[0].length;
|
||||
}
|
||||
const source = lines.map(s => s.slice(padding)).join('\n');
|
||||
return <div style={this.state.stylesheet.infoContent}>{this.marksy(source).tree}</div>;
|
||||
return <div style={stylesheet.infoContent}>{this.marksy(source).tree}</div>;
|
||||
}
|
||||
|
||||
_getComponentDescription() {
|
||||
const { context } = this.props;
|
||||
let retDiv = null;
|
||||
|
||||
if (Object.keys(STORYBOOK_REACT_CLASSES).length) {
|
||||
Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => {
|
||||
if (STORYBOOK_REACT_CLASSES[key].name === this.props.context.story) {
|
||||
if (STORYBOOK_REACT_CLASSES[key].name === context.story) {
|
||||
retDiv = <div>{STORYBOOK_REACT_CLASSES[key].docgenInfo.description}</div>;
|
||||
}
|
||||
});
|
||||
@ -251,22 +260,25 @@ class Story extends React.Component {
|
||||
}
|
||||
|
||||
_getSourceCode() {
|
||||
if (!this.props.showSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
showSource,
|
||||
maxPropsIntoLine,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
children,
|
||||
} = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
|
||||
if (!showSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={this.state.stylesheet.source.h1}>Story Source</h1>
|
||||
<h1 style={stylesheet.source.h1}>Story Source</h1>
|
||||
<Pre>
|
||||
{React.Children.map(this.props.children, (root, idx) => (
|
||||
{React.Children.map(children, (root, idx) => (
|
||||
<Node
|
||||
key={idx}
|
||||
node={root}
|
||||
@ -283,63 +295,67 @@ class Story extends React.Component {
|
||||
}
|
||||
|
||||
_getPropTables() {
|
||||
const types = new Map();
|
||||
|
||||
if (this.props.propTables === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.props.children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.propTables) {
|
||||
this.props.propTables.forEach(type => {
|
||||
types.set(type, true);
|
||||
});
|
||||
}
|
||||
|
||||
// depth-first traverse and collect types
|
||||
const extract = children => {
|
||||
if (!children) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(children)) {
|
||||
children.forEach(extract);
|
||||
return;
|
||||
}
|
||||
if (children.props && children.props.children) {
|
||||
extract(children.props.children);
|
||||
}
|
||||
if (
|
||||
typeof children === 'string' ||
|
||||
typeof children.type === 'string' ||
|
||||
(Array.isArray(this.props.propTablesExclude) && // also ignore excluded types
|
||||
~this.props.propTablesExclude.indexOf(children.type)) // eslint-disable-line no-bitwise
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (children.type && !types.has(children.type)) {
|
||||
types.set(children.type, true);
|
||||
}
|
||||
};
|
||||
|
||||
// extract components from children
|
||||
extract(this.props.children);
|
||||
|
||||
const array = Array.from(types.keys());
|
||||
array.sort((a, b) => getName(a) > getName(b));
|
||||
|
||||
const {
|
||||
children,
|
||||
propTablesExclude,
|
||||
maxPropObjectKeys,
|
||||
maxPropArrayLength,
|
||||
maxPropStringLength,
|
||||
excludedPropTypes,
|
||||
} = this.props;
|
||||
const propTables = array.map((type, i) => (
|
||||
let { propTables } = this.props;
|
||||
const { stylesheet } = this.state;
|
||||
const types = new Map();
|
||||
|
||||
if (propTables === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (propTables) {
|
||||
propTables.forEach(type => {
|
||||
types.set(type, true);
|
||||
});
|
||||
}
|
||||
|
||||
// depth-first traverse and collect types
|
||||
const extract = innerChildren => {
|
||||
if (!innerChildren) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(innerChildren)) {
|
||||
innerChildren.forEach(extract);
|
||||
return;
|
||||
}
|
||||
if (innerChildren.props && innerChildren.props.innerChildren) {
|
||||
extract(innerChildren.props.innerChildren);
|
||||
}
|
||||
if (
|
||||
typeof innerChildren === 'string' ||
|
||||
typeof innerChildren.type === 'string' ||
|
||||
(Array.isArray(propTablesExclude) && // also ignore excluded types
|
||||
~propTablesExclude.indexOf(innerChildren.type)) // eslint-disable-line no-bitwise
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (innerChildren.type && !types.has(innerChildren.type)) {
|
||||
types.set(innerChildren.type, true);
|
||||
}
|
||||
};
|
||||
|
||||
// extract components from children
|
||||
extract(children);
|
||||
|
||||
const array = Array.from(types.keys());
|
||||
array.sort((a, b) => getName(a) > getName(b));
|
||||
|
||||
propTables = array.map((type, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={`${getName(type)}_${i}`}>
|
||||
<h2 style={this.state.stylesheet.propTableHead}>"{getName(type)}" Component</h2>
|
||||
<h2 style={stylesheet.propTableHead}>"{getName(type)}" Component</h2>
|
||||
<this.props.PropTable
|
||||
type={type}
|
||||
maxPropObjectKeys={maxPropObjectKeys}
|
||||
@ -356,19 +372,20 @@ class Story extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={this.state.stylesheet.source.h1}>Prop Types</h1>
|
||||
<h1 style={stylesheet.source.h1}>Prop Types</h1>
|
||||
{propTables}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
// <ThemeProvider theme={this.state.stylesheet}></ThemeProvider>
|
||||
return this.props.showInline ? this._renderInline() : this._renderOverlay();
|
||||
const { showInline } = this.props;
|
||||
// <ThemeProvider theme={stylesheet}></ThemeProvider>
|
||||
return showInline ? this._renderInline() : this._renderOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
Story.getDerivedStateFromProps = ({ styles }) => ({ stylesheet: styles(stylesheet) });
|
||||
Story.getDerivedStateFromProps = ({ styles }) => ({ stylesheet: styles(stylesheetBase) });
|
||||
|
||||
Story.displayName = 'Story';
|
||||
|
||||
|
@ -67,10 +67,12 @@ const propsFromPropTypes = type => {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
if (!props[property]) {
|
||||
props[property] = { property };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
props[property].defaultValue = value;
|
||||
});
|
||||
}
|
||||
@ -84,10 +86,12 @@ export default function makeTableComponent(Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
const propDefinitionsMap = hasDocgen(props.type)
|
||||
? propsFromDocgen(props.type)
|
||||
: propsFromPropTypes(props.type);
|
||||
const propDefinitions = Object.values(propDefinitionsMap);
|
||||
/* eslint-enable react/destructuring-assignment */
|
||||
|
||||
return <Component propDefinitions={propDefinitions} {...props} />;
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ export class Code extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { language, code } = this.props;
|
||||
const codeStyle = {
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
backgroundColor: '#fafafa',
|
||||
@ -31,12 +32,12 @@ export class Code extends React.Component {
|
||||
overflowX: 'scroll',
|
||||
};
|
||||
|
||||
const className = this.props.language ? `language-${this.props.language}` : '';
|
||||
const className = language ? `language-${language}` : '';
|
||||
|
||||
return (
|
||||
<pre style={preStyle} className={className}>
|
||||
<code style={codeStyle} className={className}>
|
||||
{this.props.code}
|
||||
{code}
|
||||
</code>
|
||||
</pre>
|
||||
);
|
||||
@ -52,14 +53,14 @@ Code.defaultProps = {
|
||||
code: null,
|
||||
};
|
||||
|
||||
export function Blockquote(props) {
|
||||
export function Blockquote({ children }) {
|
||||
const style = {
|
||||
fontSize: '1.88em',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
borderLeft: '8px solid #fafafa',
|
||||
padding: '1rem',
|
||||
};
|
||||
return <blockquote style={style}>{props.children}</blockquote>;
|
||||
return <blockquote style={style}>{children}</blockquote>;
|
||||
}
|
||||
|
||||
Blockquote.propTypes = { children: PropTypes.node };
|
||||
|
@ -11,7 +11,7 @@ const propTypes = {
|
||||
id: PropTypes.string,
|
||||
};
|
||||
|
||||
export function H1(props) {
|
||||
export function H1({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
borderBottom: '1px solid #eee',
|
||||
@ -21,8 +21,8 @@ export function H1(props) {
|
||||
fontSize: '40px',
|
||||
};
|
||||
return (
|
||||
<h1 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h1 id={id} style={styles}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
@ -30,7 +30,7 @@ export function H1(props) {
|
||||
H1.defaultProps = defaultProps;
|
||||
H1.propTypes = propTypes;
|
||||
|
||||
export function H2(props) {
|
||||
export function H2({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
fontWeight: 600,
|
||||
@ -39,8 +39,8 @@ export function H2(props) {
|
||||
fontSize: '30px',
|
||||
};
|
||||
return (
|
||||
<h2 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h2 id={id} style={styles}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
@ -48,7 +48,7 @@ export function H2(props) {
|
||||
H2.defaultProps = defaultProps;
|
||||
H2.propTypes = propTypes;
|
||||
|
||||
export function H3(props) {
|
||||
export function H3({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
fontWeight: 600,
|
||||
@ -58,8 +58,8 @@ export function H3(props) {
|
||||
textTransform: 'uppercase',
|
||||
};
|
||||
return (
|
||||
<h3 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h3 id={id} style={styles}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
@ -67,7 +67,7 @@ export function H3(props) {
|
||||
H3.defaultProps = defaultProps;
|
||||
H3.propTypes = propTypes;
|
||||
|
||||
export function H4(props) {
|
||||
export function H4({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
fontWeight: 600,
|
||||
@ -76,8 +76,8 @@ export function H4(props) {
|
||||
fontSize: '20px',
|
||||
};
|
||||
return (
|
||||
<h4 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h4 id={id} style={styles}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
@ -85,7 +85,7 @@ export function H4(props) {
|
||||
H4.defaultProps = defaultProps;
|
||||
H4.propTypes = propTypes;
|
||||
|
||||
export function H5(props) {
|
||||
export function H5({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
fontWeight: 600,
|
||||
@ -94,8 +94,8 @@ export function H5(props) {
|
||||
fontSize: '18px',
|
||||
};
|
||||
return (
|
||||
<h5 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h5 id={id} style={styles}>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
}
|
||||
@ -103,7 +103,7 @@ export function H5(props) {
|
||||
H5.defaultProps = defaultProps;
|
||||
H5.propTypes = propTypes;
|
||||
|
||||
export function H6(props) {
|
||||
export function H6({ id, children }) {
|
||||
const styles = {
|
||||
...baseFonts,
|
||||
fontWeight: 400,
|
||||
@ -112,8 +112,8 @@ export function H6(props) {
|
||||
fontSize: '18px',
|
||||
};
|
||||
return (
|
||||
<h6 id={props.id} style={styles}>
|
||||
{props.children}
|
||||
<h6 id={id} style={styles}>
|
||||
{children}
|
||||
</h6>
|
||||
);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ const Button = styled('button')(
|
||||
borderColor: '#ccc',
|
||||
},
|
||||
},
|
||||
props => props.styles
|
||||
({ styles }) => styles
|
||||
);
|
||||
|
||||
const ContentWrapper = styled('div')(
|
||||
@ -32,19 +32,19 @@ const ContentWrapper = styled('div')(
|
||||
transition: 'transform .2s ease',
|
||||
height: 16,
|
||||
},
|
||||
props => ({
|
||||
...props.styles,
|
||||
transform: props.toggled ? 'translateY(0px)' : 'translateY(-100%) translateY(-6px)',
|
||||
({ styles, toggled }) => ({
|
||||
...styles,
|
||||
transform: toggled ? 'translateY(0px)' : 'translateY(-100%) translateY(-6px)',
|
||||
})
|
||||
);
|
||||
|
||||
function CopyButton(props) {
|
||||
const { copyButton = {}, copyButtonContent } = props.theme;
|
||||
function CopyButton({ theme, onClick, toggled }) {
|
||||
const { copyButton = {}, copyButtonContent } = theme;
|
||||
const { toggleText = 'Copied!', text = 'Copy', ...copyButtonStyles } = copyButton;
|
||||
|
||||
return (
|
||||
<Button onClick={props.onClick} styles={copyButtonStyles}>
|
||||
<ContentWrapper styles={copyButtonContent} toggled={props.toggled}>
|
||||
<Button onClick={onClick} styles={copyButtonStyles}>
|
||||
<ContentWrapper styles={copyButtonContent} toggled={toggled}>
|
||||
<div style={{ marginBottom: 6 }}>{toggleText}</div>
|
||||
<div>{text}</div>
|
||||
</ContentWrapper>
|
||||
|
@ -19,7 +19,7 @@ const StyledPre = styled('pre')(
|
||||
lineHeight: 1.5,
|
||||
overflowX: 'scroll',
|
||||
},
|
||||
props => props.styles
|
||||
({ styles }) => styles
|
||||
);
|
||||
|
||||
class Pre extends React.Component {
|
||||
@ -49,12 +49,14 @@ class Pre extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { pre } = this.props.theme;
|
||||
const { theme, children } = this.props;
|
||||
const { pre } = theme;
|
||||
const { copied } = this.state;
|
||||
|
||||
return (
|
||||
<StyledPre styles={pre}>
|
||||
<div ref={this.setRef}>{this.props.children}</div>
|
||||
<CopyButton onClick={this.handleClick} toggled={this.state.copied} />
|
||||
<div ref={this.setRef}>{children}</div>
|
||||
<CopyButton onClick={this.handleClick} toggled={copied} />
|
||||
</StyledPre>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { baseFonts } from '@storybook/components';
|
||||
const defaultProps = { children: null };
|
||||
const propTypes = { children: PropTypes.node };
|
||||
|
||||
export function P(props) {
|
||||
export function P({ children }) {
|
||||
const style = {
|
||||
...baseFonts,
|
||||
fontSize: '15px',
|
||||
@ -15,41 +15,41 @@ export function P(props) {
|
||||
// <a> and <pre> elements, which is why <div>
|
||||
// is used as the outputted element when parsing
|
||||
// marksy content rather than <p>.
|
||||
return <div style={style}>{props.children}</div>;
|
||||
return <div style={style}>{children}</div>;
|
||||
}
|
||||
|
||||
P.defaultProps = defaultProps;
|
||||
P.propTypes = propTypes;
|
||||
|
||||
export function LI(props) {
|
||||
export function LI({ children }) {
|
||||
const style = {
|
||||
...baseFonts,
|
||||
fontSize: '15px',
|
||||
};
|
||||
return <li style={style}>{props.children}</li>;
|
||||
return <li style={style}>{children}</li>;
|
||||
}
|
||||
|
||||
LI.defaultProps = defaultProps;
|
||||
LI.propTypes = propTypes;
|
||||
|
||||
export function UL(props) {
|
||||
export function UL({ children }) {
|
||||
const style = {
|
||||
...baseFonts,
|
||||
fontSize: '15px',
|
||||
};
|
||||
return <ul style={style}>{props.children}</ul>;
|
||||
return <ul style={style}>{children}</ul>;
|
||||
}
|
||||
|
||||
UL.defaultProps = defaultProps;
|
||||
UL.propTypes = propTypes;
|
||||
|
||||
export function A(props) {
|
||||
export function A({ href, children }) {
|
||||
const style = {
|
||||
color: '#3498db',
|
||||
};
|
||||
return (
|
||||
<a href={props.href} target="_blank" rel="noopener noreferrer" style={style}>
|
||||
{props.children}
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" style={style}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import { TypeInfo, getPropTypes } from './proptypes';
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
// TODO: FIX this dependency cycle
|
||||
/* eslint-disable import/no-cycle */
|
||||
import Shape from './Shape';
|
||||
import OneOfType from './OneOfType';
|
||||
import ArrayOf from './ArrayOf';
|
||||
@ -9,6 +11,7 @@ import OneOf from './OneOf';
|
||||
import InstanceOf from './InstanceOf';
|
||||
import Signature from './Signature';
|
||||
import Literal from './Literal';
|
||||
/* eslint-enable import/no-cycle */
|
||||
|
||||
import { TypeInfo } from './proptypes';
|
||||
|
||||
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { HighlightButton } from '@storybook/components';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
import PropertyLabel from './PropertyLabel';
|
||||
|
||||
@ -18,8 +19,9 @@ class Shape extends React.Component {
|
||||
}
|
||||
|
||||
handleToggle = () => {
|
||||
const { minimized } = this.state;
|
||||
this.setState({
|
||||
minimized: !this.state.minimized,
|
||||
minimized: !minimized,
|
||||
});
|
||||
};
|
||||
|
||||
@ -33,34 +35,35 @@ class Shape extends React.Component {
|
||||
|
||||
render() {
|
||||
const { propType, depth } = this.props;
|
||||
const { hover, minimized } = this.state;
|
||||
|
||||
const propTypes = getPropTypes(propType);
|
||||
return (
|
||||
<span>
|
||||
<HighlightButton
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
highlight={this.state.hover}
|
||||
highlight={hover}
|
||||
onClick={this.handleToggle}
|
||||
>
|
||||
{'{'}
|
||||
</HighlightButton>
|
||||
<HighlightButton onClick={this.handleToggle}>...</HighlightButton>
|
||||
{!this.state.minimized &&
|
||||
{!minimized &&
|
||||
Object.keys(propTypes).map(childProperty => (
|
||||
<div key={childProperty} style={{ marginLeft: depth * MARGIN_SIZE }}>
|
||||
<PropertyLabel
|
||||
property={childProperty}
|
||||
required={propTypes[childProperty].required}
|
||||
/>
|
||||
<PrettyPropType depth={depth + 1} propType={propTypes[childProperty]} />
|
||||
,
|
||||
<PrettyPropType depth={depth + 1} propType={propTypes[childProperty]} />,
|
||||
</div>
|
||||
))}
|
||||
|
||||
<HighlightButton
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
highlight={this.state.hover}
|
||||
highlight={hover}
|
||||
onClick={this.handleToggle}
|
||||
>
|
||||
{'}'}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { withInfo, setDefaults } from './';
|
||||
import { withInfo, setDefaults } from '.';
|
||||
import externalMdDocs from '../README.md';
|
||||
|
||||
/* eslint-disable */
|
||||
@ -30,9 +30,10 @@ const testMarkdown = `# Test story
|
||||
containing **bold**, *cursive* text, \`code\` and [a link](https://github.com)`;
|
||||
|
||||
describe('addon Info', () => {
|
||||
const story = context => (
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const storyFn = ({ story }) => (
|
||||
<div>
|
||||
It's a {context.story} story:
|
||||
It's a {story} story:
|
||||
<TestComponent
|
||||
func={x => x + 1}
|
||||
obj={{ a: 'a', b: 'b' }}
|
||||
@ -44,22 +45,22 @@ describe('addon Info', () => {
|
||||
</div>
|
||||
);
|
||||
it('should render <Info /> and markdown', () => {
|
||||
const Info = withInfo(testMarkdown)(story);
|
||||
const Info = withInfo(testMarkdown)(storyFn);
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
});
|
||||
it('should render <Info /> and external markdown', () => {
|
||||
const Info = withInfo(externalMdDocs)(story);
|
||||
const Info = withInfo(externalMdDocs)(storyFn);
|
||||
|
||||
expect(mount(<Info />)).toMatchSnapshot();
|
||||
});
|
||||
it('should render with text options', () => {
|
||||
const Info = withInfo({ text: 'some text here' })(story);
|
||||
const Info = withInfo({ text: 'some text here' })(storyFn);
|
||||
mount(<Info />);
|
||||
});
|
||||
it('should render with missed info', () => {
|
||||
setDefaults(testOptions);
|
||||
const Info = withInfo()(story);
|
||||
const Info = withInfo()(storyFn);
|
||||
mount(<Info />);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-jest",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "React storybook addon that show component jest report",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -25,11 +25,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/components": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/components": "4.0.0-alpha.16",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-emotion": "^9.1.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-emotion": "^9.2.6",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -45,7 +45,10 @@ const SuiteTotals = styled(({ successNumber, failedNumber, result, className })
|
||||
{failedNumber > 0 && <div style={{ color: colors.error }}>{failedNumber} failed</div>}
|
||||
<div>{result.assertionResults.length} total</div>
|
||||
<div>
|
||||
<strong>{result.endTime - result.startTime}ms</strong>
|
||||
<strong>
|
||||
{result.endTime - result.startTime}
|
||||
ms
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
))({
|
||||
|
@ -35,8 +35,10 @@ const StackTrace = styled(({ trace, className }) => (
|
||||
.join('')
|
||||
.trim()
|
||||
.split(/\n/)
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
.map((traceLine, traceLineIndex) => <div key={traceLineIndex}>{traceLine.trim()}</div>)}
|
||||
.map((traceLine, traceLineIndex) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<div key={traceLineIndex}>{traceLine.trim()}</div>
|
||||
))}
|
||||
</details>
|
||||
))({
|
||||
background: 'silver',
|
||||
@ -217,7 +219,9 @@ export const FailedResult = styled(({ fullName, title, status, failureMessages,
|
||||
</Indicator>
|
||||
</Head>
|
||||
{/* eslint-disable react/no-array-index-key */}
|
||||
{failureMessages.map((msg, i) => <Message msg={msg} key={i} />)}
|
||||
{failureMessages.map((msg, i) => (
|
||||
<Message msg={msg} key={i} />
|
||||
))}
|
||||
</div>
|
||||
))({
|
||||
display: 'block',
|
||||
|
@ -13,6 +13,7 @@ const provideTests = Component =>
|
||||
}).isRequired,
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
active: true,
|
||||
};
|
||||
@ -30,10 +31,12 @@ const provideTests = Component =>
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { channel } = this.props;
|
||||
|
||||
if (this.stopListeningOnStory) {
|
||||
this.stopListeningOnStory();
|
||||
}
|
||||
this.props.channel.removeListener('storybook/tests/add_tests', this.onAddTests);
|
||||
channel.removeListener('storybook/tests/add_tests', this.onAddTests);
|
||||
}
|
||||
|
||||
onAddTests = ({ kind, storyName, tests }) => {
|
||||
|
@ -2,7 +2,7 @@ import addons from '@storybook/addons';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
const findTestResults = (testFiles, jestTestResults, jestTestFilesExt) =>
|
||||
Array.from(testFiles).map(name => {
|
||||
Object.values(testFiles).map(name => {
|
||||
if (jestTestResults && jestTestResults.testResults) {
|
||||
return {
|
||||
name,
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Storybook Addon Knobs
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook Addon Knobs allow you to edit React props dynamically using the Storybook UI.
|
||||
You can also use Knobs as a dynamic variable inside stories in [Storybook](https://storybook.js.org).
|
||||
|
||||
@ -264,6 +255,23 @@ const value = select(label, options, defaultValue, groupId);
|
||||
|
||||
> You can also provide options as an array like this: `['red', 'blue', 'yellow']`
|
||||
|
||||
|
||||
### radio buttons
|
||||
|
||||
Allows you to get a value from a list of radio buttons from the user.
|
||||
|
||||
```js
|
||||
import { radios } from '@storybook/addon-knobs';
|
||||
|
||||
const options = {
|
||||
Kiwi: 'kiwi',
|
||||
Guava: 'guava',
|
||||
Watermelon: 'watermelon',
|
||||
};
|
||||
|
||||
const = radios(name, options, defaultValue);
|
||||
```
|
||||
|
||||
### files
|
||||
|
||||
Allows you to get a value from a file input from the user.
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-knobs",
|
||||
"version": "4.0.0-alpha.14",
|
||||
"version": "4.0.0-alpha.16",
|
||||
"description": "Storybook Addon Prop Editor Component",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,23 +13,23 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.14",
|
||||
"@storybook/components": "4.0.0-alpha.14",
|
||||
"@storybook/core-events": "4.0.0-alpha.14",
|
||||
"@storybook/addons": "4.0.0-alpha.16",
|
||||
"@storybook/components": "4.0.0-alpha.16",
|
||||
"@storybook/core-events": "4.0.0-alpha.16",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"escape-html": "^1.0.3",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"qs": "^6.5.2",
|
||||
"react-color": "^2.14.1",
|
||||
"react-datetime": "^2.14.0",
|
||||
"react-emotion": "^9.1.3",
|
||||
"react-datetime": "^2.15.0",
|
||||
"react-emotion": "^9.2.6",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^2.5.16"
|
||||
"vue": "^2.5.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -27,18 +27,22 @@ export default class Panel extends PureComponent {
|
||||
this.lastEdit = getTimestamp();
|
||||
this.loadedFromUrl = false;
|
||||
}
|
||||
componentDidMount() {
|
||||
this.props.channel.on('addon:knobs:setKnobs', this.setKnobs);
|
||||
this.props.channel.on('addon:knobs:setOptions', this.setOptions);
|
||||
|
||||
this.stopListeningOnStory = this.props.api.onStory(() => {
|
||||
componentDidMount() {
|
||||
const { channel, api } = this.props;
|
||||
channel.on('addon:knobs:setKnobs', this.setKnobs);
|
||||
channel.on('addon:knobs:setOptions', this.setOptions);
|
||||
|
||||
this.stopListeningOnStory = api.onStory(() => {
|
||||
this.setState({ knobs: {} });
|
||||
this.props.channel.emit('addon:knobs:reset');
|
||||
channel.emit('addon:knobs:reset');
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
|
||||
this.stopListeningOnStory();
|
||||
}
|
||||
|
||||
@ -75,7 +79,9 @@ export default class Panel extends PureComponent {
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
this.props.channel.emit('addon:knobs:reset');
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.emit('addon:knobs:reset');
|
||||
};
|
||||
|
||||
copy = () => {
|
||||
@ -93,7 +99,9 @@ export default class Panel extends PureComponent {
|
||||
};
|
||||
|
||||
emitChange = changedKnob => {
|
||||
this.props.channel.emit('addon:knobs:knobChange', changedKnob);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.emit('addon:knobs:knobChange', changedKnob);
|
||||
};
|
||||
|
||||
handleChange = changedKnob => {
|
||||
@ -110,7 +118,9 @@ export default class Panel extends PureComponent {
|
||||
};
|
||||
|
||||
handleClick = knob => {
|
||||
this.props.channel.emit('addon:knobs:knobClick', knob);
|
||||
const { channel } = this.props;
|
||||
|
||||
channel.emit('addon:knobs:knobClick', knob);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -15,14 +15,16 @@ const InvalidType = () => <span>Invalid Type</span>;
|
||||
|
||||
export default class PropForm extends Component {
|
||||
makeChangeHandler(name, type) {
|
||||
const { onFieldChange } = this.props;
|
||||
return value => {
|
||||
const change = { name, type, value };
|
||||
this.props.onFieldChange(change);
|
||||
|
||||
onFieldChange(change);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { knobs } = this.props;
|
||||
const { knobs, onFieldClick } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@ -32,7 +34,7 @@ export default class PropForm extends Component {
|
||||
|
||||
return (
|
||||
<Field key={knob.name} label={!knob.hideLabel && `${knob.name}`}>
|
||||
<InputType knob={knob} onChange={changeHandler} onClick={this.props.onFieldClick} />
|
||||
<InputType knob={knob} onChange={changeHandler} onClick={onFieldClick} />
|
||||
</Field>
|
||||
);
|
||||
})}
|
||||
|
44
addons/knobs/src/components/__tests__/RadioButtons.js
Normal file
44
addons/knobs/src/components/__tests__/RadioButtons.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import RadioType from '../types/Radio';
|
||||
|
||||
describe('Radio', () => {
|
||||
let knob;
|
||||
|
||||
beforeEach(() => {
|
||||
knob = {
|
||||
name: 'Color',
|
||||
value: '#319C16',
|
||||
options: {
|
||||
Green: '#319C16',
|
||||
Red: '#FF2B2B',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('displays value of button input', () => {
|
||||
it('correctly renders labels', () => {
|
||||
const wrapper = shallow(<RadioType knob={knob} />);
|
||||
|
||||
const greenLabel = wrapper.find('label').first();
|
||||
expect(greenLabel.text()).toEqual('Green');
|
||||
});
|
||||
|
||||
it('sets value on the radio buttons', () => {
|
||||
const wrapper = shallow(<RadioType knob={knob} />);
|
||||
|
||||
const greenInput = wrapper.find('input').first();
|
||||
expect(greenInput.prop('value')).toEqual('#319C16');
|
||||
});
|
||||
|
||||
it('marks the correct checkbox as checked', () => {
|
||||
const wrapper = shallow(<RadioType knob={knob} />);
|
||||
|
||||
const greenInput = wrapper.find('input').first();
|
||||
const redInput = wrapper.find('input').last();
|
||||
|
||||
expect(greenInput.prop('checked')).toEqual(true);
|
||||
expect(redInput.prop('checked')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
@ -12,14 +12,17 @@ function formatArray(value, separator) {
|
||||
|
||||
class ArrayType extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.knob.value !== this.props.knob.value;
|
||||
const { knob } = this.props;
|
||||
|
||||
return nextProps.knob.value !== knob.value;
|
||||
}
|
||||
|
||||
handleChange = e => {
|
||||
const { knob } = this.props;
|
||||
const { knob, onChange } = this.props;
|
||||
const { value } = e.target;
|
||||
const newVal = formatArray(value, knob.separator);
|
||||
this.props.onChange(newVal);
|
||||
|
||||
onChange(newVal);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -27,16 +27,22 @@ class ColorType extends React.Component {
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', this.handleWindowMouseDown);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.knob.value !== this.props.knob.value;
|
||||
const { knob } = this.props;
|
||||
|
||||
return nextProps.knob.value !== knob.value;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleWindowMouseDown);
|
||||
}
|
||||
|
||||
handleWindowMouseDown = e => {
|
||||
if (!this.state.displayColorPicker) return;
|
||||
if (this.popover.contains(e.target)) return;
|
||||
const { displayColorPicker } = this.state;
|
||||
if (!displayColorPicker || this.popover.contains(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
displayColorPicker: false,
|
||||
@ -44,13 +50,17 @@ class ColorType extends React.Component {
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const { displayColorPicker } = this.state;
|
||||
|
||||
this.setState({
|
||||
displayColorPicker: !this.state.displayColorPicker,
|
||||
displayColorPicker: !displayColorPicker,
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = color => {
|
||||
this.props.onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
|
||||
const { onChange } = this.props;
|
||||
|
||||
onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -10,12 +10,16 @@ const DateInput = styled(Datetime)(style);
|
||||
|
||||
class DateType extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.knob.value !== this.props.knob.value;
|
||||
const { knob } = this.props;
|
||||
|
||||
return nextProps.knob.value !== knob.value;
|
||||
}
|
||||
|
||||
handleChange = date => {
|
||||
const { onChange } = this.props;
|
||||
const value = date.valueOf();
|
||||
this.props.onChange(value);
|
||||
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -34,10 +34,13 @@ const RangeWrapper = styled('div')({
|
||||
|
||||
class NumberType extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.knob.value !== this.props.knob.value;
|
||||
const { knob } = this.props;
|
||||
|
||||
return nextProps.knob.value !== knob.value;
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
const { onChange } = this.props;
|
||||
const { value } = event.target;
|
||||
|
||||
let parsedValue = Number(value);
|
||||
@ -46,7 +49,7 @@ class NumberType extends React.Component {
|
||||
parsedValue = null;
|
||||
}
|
||||
|
||||
this.props.onChange(parsedValue);
|
||||
onChange(parsedValue);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -22,6 +22,8 @@ class ObjectType extends Component {
|
||||
|
||||
handleChange = e => {
|
||||
const { value } = e.target;
|
||||
const { json: stateJson } = this.state;
|
||||
const { knob, onChange } = this.props;
|
||||
|
||||
try {
|
||||
const json = JSON.parse(value.trim());
|
||||
@ -30,8 +32,8 @@ class ObjectType extends Component {
|
||||
json,
|
||||
failed: false,
|
||||
});
|
||||
if (deepEqual(this.props.knob.value, this.state.json)) {
|
||||
this.props.onChange(json);
|
||||
if (deepEqual(knob.value, stateJson)) {
|
||||
onChange(json);
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
|
71
addons/knobs/src/components/types/Radio.js
Normal file
71
addons/knobs/src/components/types/Radio.js
Normal file
@ -0,0 +1,71 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = {
|
||||
label: {
|
||||
fontSize: 11,
|
||||
padding: '5px',
|
||||
},
|
||||
group: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
},
|
||||
};
|
||||
|
||||
class RadiosType extends Component {
|
||||
renderRadioButtonList({ options }) {
|
||||
if (Array.isArray(options)) {
|
||||
return options.map(val => this.renderRadioButton(val, val));
|
||||
}
|
||||
return Object.keys(options).map(key => this.renderRadioButton(key, options[key]));
|
||||
}
|
||||
|
||||
renderRadioButton(label, value) {
|
||||
const opts = { label, value };
|
||||
const { onChange, knob } = this.props;
|
||||
const { name } = knob;
|
||||
const id = `${name}-${opts.value}`;
|
||||
|
||||
return (
|
||||
<div key={id}>
|
||||
<input
|
||||
type="radio"
|
||||
id={id}
|
||||
name={name}
|
||||
value={opts.value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
checked={value === knob.value}
|
||||
/>
|
||||
<label style={styles.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { knob } = this.props;
|
||||
|
||||
return <div style={styles.group}>{this.renderRadioButtonList(knob)}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
RadiosType.defaultProps = {
|
||||
knob: {},
|
||||
onChange: value => value,
|
||||
};
|
||||
|
||||
RadiosType.propTypes = {
|
||||
knob: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
}),
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
RadiosType.serialize = value => value;
|
||||
RadiosType.deserialize = value => value;
|
||||
|
||||
export default RadiosType;
|
@ -5,12 +5,16 @@ import { Textarea } from '@storybook/components';
|
||||
|
||||
class TextType extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.knob.value !== this.props.knob.value;
|
||||
const { knob } = this.props;
|
||||
|
||||
return nextProps.knob.value !== knob.value;
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
const { onChange } = this.props;
|
||||
const { value } = event.target;
|
||||
this.props.onChange(value);
|
||||
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -4,6 +4,7 @@ import ColorType from './Color';
|
||||
import BooleanType from './Boolean';
|
||||
import ObjectType from './Object';
|
||||
import SelectType from './Select';
|
||||
import RadiosType from './Radio';
|
||||
import ArrayType from './Array';
|
||||
import DateType from './Date';
|
||||
import ButtonType from './Button';
|
||||
@ -16,6 +17,7 @@ export default {
|
||||
boolean: BooleanType,
|
||||
object: ObjectType,
|
||||
select: SelectType,
|
||||
radios: RadiosType,
|
||||
array: ArrayType,
|
||||
date: DateType,
|
||||
button: ButtonType,
|
||||
|
@ -52,6 +52,10 @@ export function select(name, options, value, groupId) {
|
||||
return manager.knob(name, { type: 'select', selectV2: true, options, value, groupId });
|
||||
}
|
||||
|
||||
export function radios(name, options, value, groupId) {
|
||||
return manager.knob(name, { type: 'radios', options, value, groupId });
|
||||
}
|
||||
|
||||
export function array(name, value, separator = ',', groupId) {
|
||||
return manager.knob(name, { type: 'array', value, separator, groupId });
|
||||
}
|
||||
|
@ -1,14 +1,5 @@
|
||||
# Story Links Addon
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
The Storybook Links addon can be used to create links that navigate between stories in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user