mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-05 03:01:07 +08:00
Merge branch 'next' into react-native/emotion
This commit is contained in:
commit
44a538f9ab
@ -7,10 +7,9 @@ docs/public
|
||||
storybook-static
|
||||
built-storybooks
|
||||
lib/cli/test
|
||||
scripts/storage
|
||||
*.bundle.js
|
||||
*.js.map
|
||||
*.ts
|
||||
*.tsx
|
||||
|
||||
!.remarkrc.js
|
||||
!.babelrc.js
|
||||
|
63
.eslintrc.js
63
.eslintrc.js
@ -1,23 +1,41 @@
|
||||
const error = 2;
|
||||
const warn = 1;
|
||||
const ignore = 0;
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:import/react-native',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
],
|
||||
plugins: ['prettier', 'jest', 'import', 'react', 'jsx-a11y', 'json', 'html'],
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: { ecmaVersion: 8, sourceType: 'module' },
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'prettier',
|
||||
'jest',
|
||||
'import',
|
||||
'react',
|
||||
'jsx-a11y',
|
||||
'json',
|
||||
'html',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
env: { es6: true, node: true, 'jest/globals': true },
|
||||
settings: {
|
||||
'import/core-modules': ['enzyme'],
|
||||
'import/ignore': ['node_modules\\/(?!@storybook)'],
|
||||
'import/resolver': { node: { extensions: ['.js', '.ts'] } },
|
||||
'import/resolver': { node: { extensions: ['.js', '.ts', '.tsx', '.mjs'] } },
|
||||
'html/html-extensions': ['.html'],
|
||||
},
|
||||
rules: {
|
||||
@ -30,6 +48,7 @@ module.exports = {
|
||||
{
|
||||
js: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
mjs: 'never',
|
||||
},
|
||||
],
|
||||
@ -42,11 +61,11 @@ module.exports = {
|
||||
'**/example/**',
|
||||
'*.js',
|
||||
'**/*.test.js',
|
||||
'**/*.stories.js',
|
||||
'**/*.stories.*',
|
||||
'**/scripts/*.js',
|
||||
'**/stories/**/*.js',
|
||||
'**/__tests__/**/*.js',
|
||||
'**/.storybook/**/*.js',
|
||||
'**/.storybook/**/*.*',
|
||||
],
|
||||
peerDependencies: true,
|
||||
},
|
||||
@ -94,13 +113,21 @@ module.exports = {
|
||||
error,
|
||||
{ allow: ['__STORYBOOK_CLIENT_API__', '__STORYBOOK_ADDONS_CHANNEL__'] },
|
||||
],
|
||||
'@typescript-eslint/no-var-requires': ignore,
|
||||
'@typescript-eslint/camelcase': ignore,
|
||||
'@typescript-eslint/no-unused-vars': ignore,
|
||||
'@typescript-eslint/explicit-member-accessibility': ignore,
|
||||
'@typescript-eslint/explicit-function-return-type': ignore,
|
||||
'@typescript-eslint/no-explicit-any': ignore, // would prefer to enable this
|
||||
'@typescript-eslint/no-use-before-define': ignore, // this is duplicated
|
||||
'@typescript-eslint/interface-name-prefix': ignore, // I don't agree
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/**',
|
||||
'**/*.test.js',
|
||||
'**/*.stories.js',
|
||||
'**/*.test.*',
|
||||
'**/*.stories.*',
|
||||
'**/storyshots/**/stories/**',
|
||||
'docs/src/new-components/lib/StoryLinkWrapper.js',
|
||||
'docs/src/stories/**',
|
||||
@ -110,5 +137,25 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{ files: '**/.storybook/config.js', rules: { 'global-require': ignore } },
|
||||
{
|
||||
files: ['**/*.stories.*'],
|
||||
rules: {
|
||||
'no-console': ignore,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.tsx', '**/*.ts'],
|
||||
rules: {
|
||||
'react/prop-types': ignore, // we should use types
|
||||
'no-dupe-class-members': ignore, // this is called overloads in typescript
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'no-var': ignore, // this is how typescript works
|
||||
'spaced-comment': ignore,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
5
.github/automention.yml
vendored
5
.github/automention.yml
vendored
@ -1,4 +1,4 @@
|
||||
'app: angular': ['kroeder', 'igor-dv']
|
||||
'app: angular': ['kroeder', 'igor-dv', 'MaximSagan']
|
||||
'app: ember': ['gabrielcsapo']
|
||||
'app: html': ['Hypnosphi']
|
||||
'app: marko': ['nm123github']
|
||||
@ -10,9 +10,10 @@
|
||||
'api: addons': ['ndelangen']
|
||||
'addon: a11y': ['CodeByAlex', 'Armanio', 'jsomsanith']
|
||||
'addon: contexts': ['leoyli']
|
||||
'addon: storysource': ['igor-dv', 'libetl']
|
||||
'addon: docs': ['shilman', 'elevatebart']
|
||||
'addon: info': ['shilman', 'elevatebart']
|
||||
'addon: knobs': ['leoyli', 'Armanio']
|
||||
'addon: storysource': ['igor-dv', 'libetl']
|
||||
typescript: ['kroeder', 'gaetanmaisse', 'ndelangen']
|
||||
theming: ['ndelangen', 'domyen']
|
||||
cra: ['mrmckeb']
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,3 +25,5 @@ integration/__image_snapshots__/__diff_output__
|
||||
lib/*.jar
|
||||
lib/**/dll
|
||||
.expo/packager-info.json
|
||||
scripts/storage
|
||||
htpasswd
|
||||
|
@ -6,7 +6,7 @@
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|+|+|+|+|+|
|
||||
|[backgrounds](addons/backgrounds) |+|*|+|+|+|+|+|+|+|+|+|+|
|
||||
|[centered](addons/centered) |+| |+|+| |+|+| |+| |+|+|
|
||||
|[contexts](addons/contexts) |+| |+| | | | | | | | | |
|
||||
|[contexts](addons/contexts) |+| |+| | | | | | | | |+|
|
||||
|[events](addons/events) |+| |+|+|+|+|+|+| | |+|+|
|
||||
|[graphql](addons/graphql) |+| | | | | | | | | | | |
|
||||
|[google-analytics](addons/google-analytics) |+|+|+|+|+|+|+|+|+|+|+|+|
|
||||
|
97
CHANGELOG.md
97
CHANGELOG.md
@ -1,35 +1,114 @@
|
||||
## 5.1.0-alpha.37 (May 1, 2019)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Core: Fix regression with deep linking ([#6688](https://github.com/storybooks/storybook/pull/6688))
|
||||
* Addon-contexts: No cancel option in UI if the context have no param ([#6669](https://github.com/storybooks/storybook/pull/6669))
|
||||
* CLI: Fix `sb init` for projects with frozen lock files ([#6629](https://github.com/storybooks/storybook/pull/6629))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* CLI: Refactor how we install dev dependencies in cli ([#6695](https://github.com/storybooks/storybook/pull/6695))
|
||||
|
||||
## 5.0.11 (April 28, 2019)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Polymer: Fix re-rendering lit-html elements after non-lit-html element ([#5868](https://github.com/storybooks/storybook/pull/5868))
|
||||
- Addon-knobs: Check color knob value before applying uppercase ([#6598](https://github.com/storybooks/storybook/pull/6598))
|
||||
- Angular: Fix sourceMap property of angulars webpack config ([#6535](https://github.com/storybooks/storybook/pull/6535))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- UI: Add missing props in stories ([#6353](https://github.com/storybooks/storybook/pull/6353))
|
||||
|
||||
## 5.1.0-alpha.36 (April 27, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* Addon-contexts: Preact support ([#6660](https://github.com/storybooks/storybook/pull/6660))
|
||||
* Angular: Allow optional component declaration ([#6346](https://github.com/storybooks/storybook/pull/6346))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* CLI: Fix `sb init` for projects with frozen lock files ([#6629](https://github.com/storybooks/storybook/pull/6629))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
* [Snyk] Fix for 1 vulnerable dependencies ([#6647](https://github.com/storybooks/storybook/pull/6647))
|
||||
|
||||
## 5.1.0-alpha.35 (April 27, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* Addon-notes: use @storybook/router <Link> to render links in notes ([#6398](https://github.com/storybooks/storybook/pull/6398))
|
||||
* Angular: Support default `storybook` project configuration ([#6484](https://github.com/storybooks/storybook/pull/6484))
|
||||
* Addon-contexts: Improve Vue integration ([#6632](https://github.com/storybooks/storybook/pull/6632))
|
||||
* Addon-a11y: Design enhancements ([#6563](https://github.com/storybooks/storybook/pull/6563))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* UI: `active` PropTypes on MobileLayout ([#6241](https://github.com/storybooks/storybook/pull/6241))
|
||||
* Core: Fix css import when sideEffects is false ([#6650](https://github.com/storybooks/storybook/pull/6650))
|
||||
* Core: Fix infinite loop with special characters in kind names ([#6607](https://github.com/storybooks/storybook/pull/6607))
|
||||
* UI: Fix 'Escape' onKeyUp event doesn't work ([#6578](https://github.com/storybooks/storybook/pull/6578))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* UI: Add missing props in stories ([#6353](https://github.com/storybooks/storybook/pull/6353))
|
||||
* Build: tslint, and use eslint for everything ([#6621](https://github.com/storybooks/storybook/pull/6621))
|
||||
* Build: deploy to local registry ([#6619](https://github.com/storybooks/storybook/pull/6619))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
* Bump ts-node from 8.0.3 to 8.1.0 ([#6585](https://github.com/storybooks/storybook/pull/6585))
|
||||
* Bump semver from 5.7.0 to 6.0.0 ([#6580](https://github.com/storybooks/storybook/pull/6580))
|
||||
* Bump react-color from 2.17.0 to 2.17.1 ([#6583](https://github.com/storybooks/storybook/pull/6583))
|
||||
|
||||
## 5.1.0-alpha.34 (April 24, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
- Addon-contexts: Add URL query param feature ([#6601](https://github.com/storybooks/storybook/pull/6601))
|
||||
- UI: Add classNames to sidebar nav elements ([#6571](https://github.com/storybooks/storybook/pull/6571))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-knobs: Check color knob value before applying uppercase ([#6598](https://github.com/storybooks/storybook/pull/6598))
|
||||
- React-native: Restore title in section header ([#6599](https://github.com/storybooks/storybook/pull/6599))
|
||||
|
||||
## 5.1.0-alpha.33 (April 23, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* React: Add support for create-react-app@3.0.0 ([#6560](https://github.com/storybooks/storybook/pull/6560))
|
||||
- React: Add support for create-react-app@3.0.0 ([#6560](https://github.com/storybooks/storybook/pull/6560))
|
||||
|
||||
## 5.1.0-alpha.32 (April 22, 2019)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Addon-contexts: bug-fixing, testing, typing ([#6572](https://github.com/storybooks/storybook/pull/6572))
|
||||
- Addon-contexts: bug-fixing, testing, typing ([#6572](https://github.com/storybooks/storybook/pull/6572))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
* CHANGE opn to open ([#6567](https://github.com/storybooks/storybook/pull/6567))
|
||||
- CHANGE opn to open ([#6567](https://github.com/storybooks/storybook/pull/6567))
|
||||
|
||||
## 5.1.0-alpha.31 (April 19, 2019)
|
||||
|
||||
### Features
|
||||
|
||||
* Addon-backgrounds: Emit event on updating background ([#6561](https://github.com/storybooks/storybook/pull/6561))
|
||||
* Addon-contexts: Merge into monorepo ([#6559](https://github.com/storybooks/storybook/pull/6559))
|
||||
- Addon-backgrounds: Emit event on updating background ([#6561](https://github.com/storybooks/storybook/pull/6561))
|
||||
- Addon-contexts: Merge into monorepo ([#6559](https://github.com/storybooks/storybook/pull/6559))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Angular: Fix sourceMap property of angulars webpack config ([#6535](https://github.com/storybooks/storybook/pull/6535))
|
||||
* Addon-jest: Fix result display ([#6539](https://github.com/storybooks/storybook/pull/6539))
|
||||
- Angular: Fix sourceMap property of angulars webpack config ([#6535](https://github.com/storybooks/storybook/pull/6535))
|
||||
- Addon-jest: Fix result display ([#6539](https://github.com/storybooks/storybook/pull/6539))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
* Bump ember-source from 3.8.1 to 3.9.1 ([#6531](https://github.com/storybooks/storybook/pull/6531))
|
||||
* Bump typescript from 3.4.2 to 3.4.3 ([#6528](https://github.com/storybooks/storybook/pull/6528))
|
||||
- Bump ember-source from 3.8.1 to 3.9.1 ([#6531](https://github.com/storybooks/storybook/pull/6531))
|
||||
- Bump typescript from 3.4.2 to 3.4.3 ([#6528](https://github.com/storybooks/storybook/pull/6528))
|
||||
|
||||
## 5.0.10 (April 18, 2019)
|
||||
|
||||
|
121
CONTRIBUTING.md
121
CONTRIBUTING.md
@ -28,14 +28,16 @@ To test your project against the current latest version of storybook, you can cl
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
yarn bootstrap
|
||||
```
|
||||
|
||||
The bootstrap command might ask which sections of the codebase you want to bootstrap. Unless you're going to work with ReactNative or the Documentation, you can keep the default.
|
||||
|
||||
You can also pick directly from CLI:
|
||||
|
||||
yarn bootstrap --core
|
||||
```sh
|
||||
yarn bootstrap --core
|
||||
```
|
||||
|
||||
#### 2a. Run unit tests
|
||||
|
||||
@ -67,7 +69,7 @@ Before the tests are run, the project must be bootstrapped with core. You can ac
|
||||
This option executes tests from `<rootdir>/examples/official-storybook`
|
||||
In order for the image snapshots to be correctly generated, you must have a static build of the storybook up-to-date :
|
||||
|
||||
```javascript
|
||||
```sh
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
cd ../..
|
||||
@ -80,68 +82,118 @@ Puppeteer is used to launch and grab screenshots of example pages, while jest is
|
||||
|
||||
If you made any changes to the `lib/cli` package, the easiest way to verify that it doesn't break anything is to run e2e tests:
|
||||
|
||||
yarn test --cli
|
||||
```sh
|
||||
yarn test --cli
|
||||
```
|
||||
|
||||
This will run a bash script located at `lib/cli/test/run_tests.sh`. It will copy the contents of `fixtures` into a temporary `run` directory, run `getstorybook` in each of the subdirectories, and check that storybook starts successfully using `yarn storybook --smoke-test`.
|
||||
|
||||
After that, the `run` directory content will be compared with `snapshots`. You can update the snapshots by passing an `--update` flag:
|
||||
|
||||
yarn test --cli --update
|
||||
```sh
|
||||
yarn test --cli --update
|
||||
```
|
||||
|
||||
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
|
||||
#### 2c. Run Linter
|
||||
|
||||
If you want to test your own existing project using the GitHub version of storybook, you need to `link` the packages you use in your project.
|
||||
We use eslint as a linter for all code (including typescript code).
|
||||
|
||||
All you have to run is:
|
||||
|
||||
```sh
|
||||
cd app/react
|
||||
yarn link
|
||||
|
||||
cd <your-project>
|
||||
yarn link @storybook/react
|
||||
|
||||
# repeat with whichever other parts of the monorepo you are using.
|
||||
yarn lint
|
||||
```
|
||||
|
||||
It can be immensely helpful to get feedback in your editor, if you're using VsCode, you should install the `eslint` plugin and configure it with these settings:
|
||||
|
||||
```plaintext
|
||||
"eslint.autoFixOnSave": true,
|
||||
"eslint.packageManager": "yarn",
|
||||
"eslint.options": {
|
||||
"cache": true,
|
||||
"cacheLocation": ".cache/eslint",
|
||||
"extensions": [".js", ".jsx", ".mjs", ".json", ".ts", ".tsx"]
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
{"language": "typescript", "autoFix": true },
|
||||
{"language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
"eslint.alwaysShowStatus": true
|
||||
```
|
||||
|
||||
This should enable auto-fix for all source files, and give linting warnings and errors within your editor.
|
||||
|
||||
### Reproductions
|
||||
|
||||
#### In the monorepo
|
||||
|
||||
The best way to help figure out an issue you are having is to produce a minimal reproduction against the `master` branch.
|
||||
|
||||
A good way to do that is using the example `cra-kitchen-sink` app embedded in this repository:
|
||||
|
||||
```sh
|
||||
# Download and build this repository:
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
# Download and build this repository:
|
||||
git clone https://github.com/storybooks/storybook.git
|
||||
cd storybook
|
||||
yarn install
|
||||
yarn bootstrap --core
|
||||
|
||||
# make changes to try and reproduce the problem, such as adding components + stories
|
||||
cd examples/cra-kitchen-sink
|
||||
yarn storybook
|
||||
# make changes to try and reproduce the problem, such as adding components + stories
|
||||
cd examples/cra-kitchen-sink
|
||||
yarn storybook
|
||||
|
||||
# see if you can see the problem, if so, commit it:
|
||||
git checkout "branch-describing-issue"
|
||||
git add -A
|
||||
git commit -m "reproduction for issue #123"
|
||||
# see if you can see the problem, if so, commit it:
|
||||
git checkout "branch-describing-issue"
|
||||
git add -A
|
||||
git commit -m "reproduction for issue #123"
|
||||
|
||||
# fork the storybook repo to your account, then add the resulting remote
|
||||
git remote add <your-username> https://github.com/<your-username>/storybook.git
|
||||
git push -u <your-username> master
|
||||
# fork the storybook repo to your account, then add the resulting remote
|
||||
git remote add <your-username> https://github.com/<your-username>/storybook.git
|
||||
git push -u <your-username> master
|
||||
```
|
||||
|
||||
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 the storybook. Alternatively, use `yarn eject` in the CRA app to get a modifiable webpack config.
|
||||
|
||||
#### Outside the monorepo
|
||||
|
||||
Sometimes your storybook is deeply ingrained in your own setup and it's hard to create a minimal viable reproduction somewhere else.
|
||||
|
||||
Inside the storybook repo we have a script that allows you to test the packages inside this repo in your own seperate project.
|
||||
|
||||
You can use `npm link` on all packages, but npm linking is cumbersome and has subtle differences from what happens in a registry-based installation.
|
||||
So the way our script works is that it:
|
||||
|
||||
- sets up a npm registry running on your own local machine
|
||||
- changes your default registry to this local one
|
||||
- builds all packages in the storybook repo
|
||||
- publishes all packages as latest
|
||||
|
||||
Our script leaves the local registry running, for **as long as you keep it running** you can install storybook packages from this local registry.
|
||||
|
||||
- Navigate to your own project and then change `package.json` so the storybook packages match the version of the one you just published.
|
||||
- Then just do the normal install procedure using `yarn` or `npm`
|
||||
- Start using your storybook as normally.
|
||||
|
||||
If you've made a change to storybook's codebase and would want this change to be reflected in your app:
|
||||
|
||||
- Ensure the storybook packages are transpiled, by either having run `yarn dev` or `yarn bootstrap --core`.
|
||||
- Go to the terminal where the local regitry is running and press `<Enter>`. This will kick off a new publish.
|
||||
- Run the install procedure again in your local repo, (you may need to clean out node_modules first).
|
||||
- Restart your storybook.
|
||||
|
||||
### Updating Tests
|
||||
|
||||
Before any contributes are submitted in a PR, make sure to add or update meaningful tests. A PR that has failing tests will be regarded as a “Work in Progress” and will not be merged until all tests pass.
|
||||
When creating new unit test files, the tests should adhere to a particular folder structure and naming convention, as defined below.
|
||||
|
||||
```sh
|
||||
#Proper naming convention and structure for js tests files
|
||||
# Proper naming convention and structure for js tests files
|
||||
+-- parentFolder
|
||||
| +-- [filename].js
|
||||
| +-- [filename].test.js
|
||||
@ -149,14 +201,9 @@ When creating new unit test files, the tests should adhere to a particular folde
|
||||
|
||||
## Pull Requests (PRs)
|
||||
|
||||
We welcome your contributions. There are many ways you can help us. This is few of those ways:
|
||||
We welcome all contributions. There are many ways you can help us. This is few of those ways:
|
||||
|
||||
- Fix typos and add more [documentation](https://github.com/storybooks/storybook/labels/needs%20docs).
|
||||
- Try to fix some [bugs](https://github.com/storybooks/storybook/labels/bug).
|
||||
- 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) (especially for the [UI](https://codecov.io/gh/storybooks/storybook/tree/master/packages/storybook-ui/src)).
|
||||
|
||||
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.
|
||||
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, the best way is to [join the discord server and ask in the maintenance channel](https://discord.gg/sMFvFsG).
|
||||
|
||||
### Reviewing PRs
|
||||
|
||||
|
@ -4,7 +4,7 @@ This storybook addon can be helpful to make your UI components more accessible.
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting started
|
||||
|
||||
@ -58,7 +58,7 @@ addParameters({
|
||||
a11y: {
|
||||
// ... axe options
|
||||
element: '#root', // optional selector which element to inspect
|
||||
config: {} // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
|
||||
config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
|
||||
options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)
|
||||
},
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -26,12 +26,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/client-logger": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/client-logger": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"axe-core": "^3.2.2",
|
||||
"common-tags": "^1.8.0",
|
||||
"core-js": "^2.6.5",
|
||||
@ -40,6 +40,7 @@
|
||||
"memoizerific": "^1.11.3",
|
||||
"react": "^16.8.4",
|
||||
"react-redux": "^7.0.2",
|
||||
"react-sizeme": "^2.5.2",
|
||||
"redux": "^4.0.1",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import { ThemeProvider, themes, convert } from '@storybook/theming';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { ScrollArea } from '@storybook/components';
|
||||
|
||||
import { A11YPanel } from './A11YPanel.tsx';
|
||||
import { A11YPanel } from './A11YPanel';
|
||||
import { EVENTS } from '../constants';
|
||||
|
||||
function createApi() {
|
||||
|
@ -6,12 +6,12 @@ import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { ActionBar, Icons, ScrollArea } from '@storybook/components';
|
||||
|
||||
import { AxeResults, Result } from 'axe-core';
|
||||
import { API } from '@storybook/api';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Report } from './Report';
|
||||
import { Tabs } from './Tabs';
|
||||
import { EVENTS } from '../constants';
|
||||
import { API } from '@storybook/api';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import store, { clearElements } from '../redux-config';
|
||||
|
||||
export enum RuleType {
|
||||
@ -175,10 +175,9 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
|
||||
label: <Violations>{violations.length} Violations</Violations>,
|
||||
panel: (
|
||||
<Report
|
||||
passes={false}
|
||||
items={violations}
|
||||
type={RuleType.VIOLATION}
|
||||
empty="No a11y violations found."
|
||||
empty="No accessibility violations found."
|
||||
/>
|
||||
),
|
||||
items: violations,
|
||||
@ -188,10 +187,9 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
|
||||
label: <Passes>{passes.length} Passes</Passes>,
|
||||
panel: (
|
||||
<Report
|
||||
passes
|
||||
items={passes}
|
||||
type={RuleType.PASS}
|
||||
empty="No a11y check passed."
|
||||
empty="No accessibility checks passed."
|
||||
/>
|
||||
),
|
||||
items: passes,
|
||||
@ -201,10 +199,9 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
|
||||
label: <Incomplete>{incomplete.length} Incomplete</Incomplete>,
|
||||
panel: (
|
||||
<Report
|
||||
passes={false}
|
||||
items={incomplete}
|
||||
type={RuleType.INCOMPLETION}
|
||||
empty="No a11y incomplete found."
|
||||
empty="No accessibility checks incomplete."
|
||||
/>
|
||||
),
|
||||
items: incomplete,
|
||||
|
@ -34,7 +34,7 @@ const ColorIcon = styled.span(
|
||||
})
|
||||
);
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface ColorBlindnessProps {}
|
||||
|
||||
interface ColorBlindnessState {
|
||||
@ -63,7 +63,8 @@ export class ColorBlindness extends Component<ColorBlindnessProps, ColorBlindnes
|
||||
};
|
||||
|
||||
onVisibilityChange = (s: boolean) => {
|
||||
if (this.state.expanded !== s) {
|
||||
const { expanded } = this.state;
|
||||
if (expanded !== s) {
|
||||
this.setState({ expanded: s });
|
||||
}
|
||||
};
|
||||
@ -83,7 +84,7 @@ export class ColorBlindness extends Component<ColorBlindnessProps, ColorBlindnes
|
||||
'mono',
|
||||
].map(i => ({
|
||||
id: i,
|
||||
title: i,
|
||||
title: i.charAt(0).toUpperCase() + i.slice(1),
|
||||
onClick: () => {
|
||||
this.setFilter(i);
|
||||
},
|
||||
|
@ -14,25 +14,25 @@ const Item = styled.li({
|
||||
const ItemTitle = styled.span(({ theme }) => ({
|
||||
borderBottom: `1px solid ${theme.appBorderColor}`,
|
||||
width: '100%',
|
||||
display: 'inline-block',
|
||||
display: 'flex',
|
||||
paddingBottom: '6px',
|
||||
marginBottom: '6px',
|
||||
justifyContent: 'space-between',
|
||||
}));
|
||||
|
||||
const HighlightToggleElement = styled.span({
|
||||
fontWeight: 'normal',
|
||||
float: 'right',
|
||||
alignSelf: 'center',
|
||||
paddingRight: '15px',
|
||||
input: { margin: 0 },
|
||||
});
|
||||
|
||||
interface ElementProps {
|
||||
element: NodeResult;
|
||||
passes: boolean;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
const Element: FunctionComponent<ElementProps> = ({ element, passes, type }) => {
|
||||
const Element: FunctionComponent<ElementProps> = ({ element, type }) => {
|
||||
const { any, all, none } = element;
|
||||
const rules = [...any, ...all, ...none];
|
||||
const highlightToggleId = `${type}-${element.target[0]}`;
|
||||
@ -51,21 +51,21 @@ const Element: FunctionComponent<ElementProps> = ({ element, passes, type }) =>
|
||||
/>
|
||||
</HighlightToggleElement>
|
||||
</ItemTitle>
|
||||
<Rules rules={rules} passes={passes} />
|
||||
<Rules rules={rules} />
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
interface ElementsProps {
|
||||
elements: NodeResult[];
|
||||
passes: boolean;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
export const Elements: FunctionComponent<ElementsProps> = ({ elements, passes, type }) => (
|
||||
export const Elements: FunctionComponent<ElementsProps> = ({ elements, type }) => (
|
||||
<ol>
|
||||
{elements.map((element, index) => (
|
||||
<Element passes={passes} element={element} key={index} type={type} />
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Element element={element} key={index} type={type} />
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider, themes, convert } from '@storybook/theming';
|
||||
import HighlightToggle from './HighlightToggle.tsx';
|
||||
import store from '../../redux-config.tsx';
|
||||
import HighlightToggle from './HighlightToggle';
|
||||
import store from '../../redux-config';
|
||||
|
||||
function ThemedHighlightToggle(props) {
|
||||
return (
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { styled, themes, convert } from '@storybook/theming';
|
||||
@ -10,6 +11,7 @@ import { IFRAME } from '../../constants';
|
||||
|
||||
export class HighlightedElementData {
|
||||
originalOutline: string;
|
||||
|
||||
isHighlighted: boolean;
|
||||
}
|
||||
|
||||
@ -50,6 +52,7 @@ function getElementBySelectorPath(elementPath: string): HTMLElement {
|
||||
}
|
||||
|
||||
function setElementOutlineStyle(targetElement: HTMLElement, outlineStyle: string): void {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
targetElement.style.outline = outlineStyle;
|
||||
}
|
||||
|
||||
@ -65,6 +68,7 @@ function areAllRequiredElementsHighlighted(
|
||||
);
|
||||
}).length;
|
||||
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return highlightedCount === 0
|
||||
? CheckBoxStates.UNCHECKED
|
||||
: highlightedCount === elementsToHighlight.length
|
||||
@ -99,58 +103,31 @@ class HighlightToggle extends Component<ToggleProps> {
|
||||
private checkBoxRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
componentDidMount() {
|
||||
this.props.elementsToHighlight.forEach(element => {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (targetElement && !this.props.highlightedElementsMap.has(targetElement)) {
|
||||
if (targetElement && !highlightedElementsMap.has(targetElement)) {
|
||||
this.saveElementDataToMap(targetElement, false, targetElement.style.outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<ToggleProps>): void {
|
||||
const { indeterminate } = this.props;
|
||||
if (this.checkBoxRef.current) {
|
||||
this.checkBoxRef.current.indeterminate = this.props.indeterminate;
|
||||
this.checkBoxRef.current.indeterminate = indeterminate;
|
||||
}
|
||||
}
|
||||
|
||||
highlightRuleLocation(targetElement: HTMLElement, addHighlight: boolean): void {
|
||||
if (!targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (addHighlight) {
|
||||
setElementOutlineStyle(targetElement, `${colorsByType[this.props.type]} dotted 1px`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.highlightedElementsMap.has(targetElement)) {
|
||||
setElementOutlineStyle(
|
||||
targetElement,
|
||||
this.props.highlightedElementsMap.get(targetElement).originalOutline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
saveElementDataToMap(
|
||||
targetElement: HTMLElement,
|
||||
isHighlighted: boolean,
|
||||
originalOutline: string
|
||||
): void {
|
||||
const data: HighlightedElementData = new HighlightedElementData();
|
||||
data.isHighlighted = isHighlighted;
|
||||
data.originalOutline = originalOutline;
|
||||
const payload = { element: targetElement, highlightedElementData: data };
|
||||
this.props.addElement(payload);
|
||||
}
|
||||
|
||||
onToggle = (): void => {
|
||||
this.props.elementsToHighlight.forEach(element => {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (!this.props.highlightedElementsMap.has(targetElement)) {
|
||||
if (!highlightedElementsMap.has(targetElement)) {
|
||||
return;
|
||||
}
|
||||
const originalOutline = this.props.highlightedElementsMap.get(targetElement).originalOutline;
|
||||
const { isHighlighted } = this.props.highlightedElementsMap.get(targetElement);
|
||||
const { originalOutline } = highlightedElementsMap.get(targetElement);
|
||||
const { isHighlighted } = highlightedElementsMap.get(targetElement);
|
||||
const { isToggledOn } = this.props;
|
||||
if ((isToggledOn && isHighlighted) || (!isToggledOn && !isHighlighted)) {
|
||||
const addHighlight = !isToggledOn && !isHighlighted;
|
||||
@ -160,16 +137,49 @@ class HighlightToggle extends Component<ToggleProps> {
|
||||
});
|
||||
};
|
||||
|
||||
highlightRuleLocation(targetElement: HTMLElement, addHighlight: boolean): void {
|
||||
const { highlightedElementsMap, type } = this.props;
|
||||
if (!targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (addHighlight) {
|
||||
setElementOutlineStyle(targetElement, `${colorsByType[type]} dotted 1px`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (highlightedElementsMap.has(targetElement)) {
|
||||
setElementOutlineStyle(
|
||||
targetElement,
|
||||
highlightedElementsMap.get(targetElement).originalOutline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
saveElementDataToMap(
|
||||
targetElement: HTMLElement,
|
||||
isHighlighted: boolean,
|
||||
originalOutline: string
|
||||
): void {
|
||||
const { addElement: localAddElement } = this.props;
|
||||
const data: HighlightedElementData = new HighlightedElementData();
|
||||
data.isHighlighted = isHighlighted;
|
||||
data.originalOutline = originalOutline;
|
||||
const payload = { element: targetElement, highlightedElementData: data };
|
||||
localAddElement(payload);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { toggleId, elementsToHighlight, isToggledOn } = this.props;
|
||||
return (
|
||||
<Checkbox
|
||||
ref={this.checkBoxRef}
|
||||
id={this.props.toggleId}
|
||||
id={toggleId}
|
||||
type="checkbox"
|
||||
aria-label="Highlight result"
|
||||
disabled={!this.props.elementsToHighlight.length}
|
||||
disabled={!elementsToHighlight.length}
|
||||
onChange={this.onToggle}
|
||||
checked={this.props.isToggledOn}
|
||||
checked={isToggledOn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import HighlightToggle from './HighlightToggle';
|
||||
|
||||
const Wrapper = styled.div(({ theme }) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
borderBottom: `1px solid ${theme.appBorderColor}`,
|
||||
'&:hover': {
|
||||
background: theme.background.hoverable,
|
||||
@ -49,14 +50,12 @@ const HighlightToggleElement = styled.span({
|
||||
fontWeight: 'normal',
|
||||
float: 'right',
|
||||
marginRight: '15px',
|
||||
marginTop: '10px',
|
||||
|
||||
alignSelf: 'center',
|
||||
input: { margin: 0 },
|
||||
});
|
||||
|
||||
interface ItemProps {
|
||||
item: Result;
|
||||
passes: boolean;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
@ -75,7 +74,7 @@ export class Item extends Component<ItemProps, ItemState> {
|
||||
}));
|
||||
|
||||
render() {
|
||||
const { item, passes, type } = this.props;
|
||||
const { item, type } = this.props;
|
||||
const { open } = this.state;
|
||||
const highlightToggleId = `${type}-${item.id}`;
|
||||
|
||||
@ -104,7 +103,7 @@ export class Item extends Component<ItemProps, ItemState> {
|
||||
{open ? (
|
||||
<Fragment>
|
||||
<Info item={item} key="info" />
|
||||
<Elements elements={item.nodes} passes={passes} type={type} key="elements" />
|
||||
<Elements elements={item.nodes} type={type} key="elements" />
|
||||
<Tags tags={item.tags} key="tags" />
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { Icons } from '@storybook/components';
|
||||
import { Badge, Icons } from '@storybook/components';
|
||||
import { CheckResult } from 'axe-core';
|
||||
import { SizeMe } from 'react-sizeme';
|
||||
import { RuleType } from '../A11YPanel';
|
||||
|
||||
const impactColors = {
|
||||
minor: '#f1c40f',
|
||||
@ -15,18 +16,33 @@ const impactColors = {
|
||||
const List = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '4px',
|
||||
paddingBottom: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '4px',
|
||||
fontWeight: '400',
|
||||
} as any);
|
||||
|
||||
const Item = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: '6px',
|
||||
const Item = styled.div(({ elementWidth }: { elementWidth: number }) => {
|
||||
const maxWidthBeforeBreak = 407;
|
||||
return {
|
||||
flexDirection: elementWidth > maxWidthBeforeBreak ? 'row' : 'inherit',
|
||||
marginBottom: elementWidth > maxWidthBeforeBreak ? '6px' : '12px',
|
||||
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
|
||||
};
|
||||
});
|
||||
|
||||
const StyledBadge = styled(Badge)(({ status }: { status: string }) => ({
|
||||
padding: '2px 8px',
|
||||
marginBottom: '3px',
|
||||
minWidth: '65px',
|
||||
maxWidth: 'fit-content',
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const Message = styled.div({
|
||||
paddingLeft: '6px',
|
||||
paddingRight: '23px',
|
||||
});
|
||||
|
||||
const Status = styled.div(({ passes, impact }: { passes: boolean; impact: string }) => ({
|
||||
@ -40,30 +56,64 @@ const Status = styled.div(({ passes, impact }: { passes: boolean; impact: string
|
||||
},
|
||||
}));
|
||||
|
||||
interface RuleProps {
|
||||
rule: CheckResult;
|
||||
passes: boolean;
|
||||
export enum ImpactValue {
|
||||
MINOR = 'minor',
|
||||
MODERATE = 'moderate',
|
||||
SERIOUS = 'serious',
|
||||
CRITICAL = 'critical',
|
||||
}
|
||||
|
||||
const Rule: FunctionComponent<RuleProps> = ({ rule, passes }) => (
|
||||
<Item>
|
||||
<Status passes={passes || undefined} impact={rule.impact}>
|
||||
{passes ? <Icons icon="check" /> : <Icons icon="cross" />}
|
||||
</Status>
|
||||
<Message>{rule.message}</Message>
|
||||
</Item>
|
||||
);
|
||||
interface RuleProps {
|
||||
rule: CheckResult;
|
||||
}
|
||||
|
||||
const formatSeverityText = (severity: string) => {
|
||||
return severity
|
||||
.charAt(0)
|
||||
.toUpperCase()
|
||||
.concat(severity.slice(1));
|
||||
};
|
||||
|
||||
const Rule: FunctionComponent<RuleProps> = ({ rule }) => {
|
||||
let badgeType: any = null;
|
||||
switch (rule.impact) {
|
||||
case ImpactValue.CRITICAL:
|
||||
badgeType = 'critical';
|
||||
break;
|
||||
case ImpactValue.SERIOUS:
|
||||
badgeType = 'negative';
|
||||
break;
|
||||
case ImpactValue.MODERATE:
|
||||
badgeType = 'warning';
|
||||
break;
|
||||
case ImpactValue.MINOR:
|
||||
badgeType = 'neutral';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<SizeMe refreshMode="debounce">
|
||||
{({ size }: { size: any }) => (
|
||||
<Item elementWidth={size.width}>
|
||||
<StyledBadge status={badgeType}>{formatSeverityText(rule.impact)}</StyledBadge>
|
||||
<Message>{rule.message}</Message>
|
||||
</Item>
|
||||
)}
|
||||
</SizeMe>
|
||||
);
|
||||
};
|
||||
|
||||
interface RulesProps {
|
||||
rules: CheckResult[];
|
||||
passes: boolean;
|
||||
}
|
||||
|
||||
export const Rules: FunctionComponent<RulesProps> = ({ rules, passes }) => {
|
||||
export const Rules: FunctionComponent<RulesProps> = ({ rules }) => {
|
||||
return (
|
||||
<List>
|
||||
{rules.map((rule, index) => (
|
||||
<Rule passes={passes} rule={rule} key={index} />
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Rule rule={rule} key={index} />
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
|
@ -125,6 +125,7 @@ exports[`HighlightToggle component should match snapshot 1`] = `
|
||||
"app": "#F6F9FC",
|
||||
"bar": "#FFFFFF",
|
||||
"content": "#FFFFFF",
|
||||
"critical": "#FF4400",
|
||||
"gridCellSize": 10,
|
||||
"hoverable": "rgba(0,0,0,.05)",
|
||||
"negative": "#FEDED2",
|
||||
@ -256,6 +257,7 @@ exports[`HighlightToggle component should match snapshot 1`] = `
|
||||
"color": Object {
|
||||
"ancillary": "#22a699",
|
||||
"border": "rgba(0,0,0,.1)",
|
||||
"critical": "#FFFFFF",
|
||||
"dark": "#666666",
|
||||
"darker": "#444444",
|
||||
"darkest": "#333333",
|
||||
|
@ -7,14 +7,13 @@ import { RuleType } from '../A11YPanel';
|
||||
export interface ReportProps {
|
||||
items: Result[];
|
||||
empty: string;
|
||||
passes: boolean;
|
||||
type: RuleType;
|
||||
}
|
||||
|
||||
export const Report: FunctionComponent<ReportProps> = ({ items, empty, type, passes }) => (
|
||||
export const Report: FunctionComponent<ReportProps> = ({ items, empty, type }) => (
|
||||
<Fragment>
|
||||
{items && items.length ? (
|
||||
items.map(item => <Item passes={passes} item={item} key={`${type}:${item.id}`} type={type} />)
|
||||
items.map(item => <Item item={item} key={`${type}:${item.id}`} type={type} />)
|
||||
) : (
|
||||
<Placeholder key="placeholder">{empty}</Placeholder>
|
||||
)}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { Component, SyntheticEvent } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { styled, themes } from '@storybook/theming';
|
||||
import { NodeResult, Result } from 'axe-core';
|
||||
import { SizeMe } from 'react-sizeme';
|
||||
import store, { clearElements } from '../redux-config';
|
||||
import HighlightToggle from './Report/HighlightToggle';
|
||||
import { NodeResult, Result } from 'axe-core';
|
||||
import { RuleType } from './A11YPanel';
|
||||
|
||||
// TODO: reuse the Tabs component from @storybook/theming instead
|
||||
// of re-building identical functionality
|
||||
// TODO: reuse the Tabs component from @storybook/theming instead of re-building identical functionality
|
||||
|
||||
const Container = styled.div({
|
||||
width: '100%',
|
||||
@ -18,26 +18,34 @@ const Container = styled.div({
|
||||
const HighlightToggleLabel = styled.label(({ theme }) => ({
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
marginBottom: '3px',
|
||||
marginRight: '3px',
|
||||
color: theme.color.dark,
|
||||
}));
|
||||
|
||||
const GlobalToggleWrapper = styled.div(({ theme }) => ({
|
||||
padding: '10px 15px 10px 0',
|
||||
cursor: 'pointer',
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
height: 40,
|
||||
border: 'none',
|
||||
const GlobalToggle = styled.div(({ elementWidth }: { elementWidth: number }) => {
|
||||
const maxWidthBeforeBreak = 450;
|
||||
return {
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
padding: elementWidth > maxWidthBeforeBreak ? '12px 15px 10px 0' : '12px 0px 3px 12px',
|
||||
height: '40px',
|
||||
border: 'none',
|
||||
marginTop: elementWidth > maxWidthBeforeBreak ? '-40px' : '0px',
|
||||
float: elementWidth > maxWidthBeforeBreak ? 'right' : 'left',
|
||||
display: elementWidth > maxWidthBeforeBreak ? 'flex' : 'block',
|
||||
alignItems: 'center',
|
||||
width: elementWidth > maxWidthBeforeBreak ? 'auto' : '100%',
|
||||
borderBottom: elementWidth > maxWidthBeforeBreak ? 'none' : '1px solid rgba(0,0,0,.1)',
|
||||
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
||||
input: {
|
||||
marginLeft: 10,
|
||||
marginRight: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
},
|
||||
}));
|
||||
input: {
|
||||
marginLeft: '10',
|
||||
marginRight: '0',
|
||||
marginTop: '0',
|
||||
marginBottom: '0',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Item = styled.button(
|
||||
({ theme }) => ({
|
||||
@ -78,12 +86,12 @@ const List = styled.div(({ theme }) => ({
|
||||
}));
|
||||
|
||||
interface TabsProps {
|
||||
tabs: Array<{
|
||||
tabs: {
|
||||
label: JSX.Element;
|
||||
panel: JSX.Element;
|
||||
items: Result[];
|
||||
type: RuleType;
|
||||
}>;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface TabsState {
|
||||
@ -113,34 +121,41 @@ export class Tabs extends Component<TabsProps, TabsState> {
|
||||
const highlightToggleId = `${tabs[active].type}-global-checkbox`;
|
||||
const highlightLabel = `Highlight results`;
|
||||
return (
|
||||
<Container>
|
||||
<List>
|
||||
<TabsWrapper>
|
||||
{tabs.map((tab, index) => (
|
||||
<Item
|
||||
key={index}
|
||||
data-index={index}
|
||||
active={active === index}
|
||||
onClick={this.onToggle}
|
||||
>
|
||||
{tab.label}
|
||||
</Item>
|
||||
))}
|
||||
</TabsWrapper>
|
||||
<GlobalToggleWrapper>
|
||||
<HighlightToggleLabel htmlFor={highlightToggleId}>
|
||||
{highlightLabel}
|
||||
</HighlightToggleLabel>
|
||||
<HighlightToggle
|
||||
toggleId={highlightToggleId}
|
||||
type={tabs[active].type}
|
||||
elementsToHighlight={retrieveAllNodesFromResults(tabs[active].items)}
|
||||
label={highlightLabel}
|
||||
/>
|
||||
</GlobalToggleWrapper>
|
||||
</List>
|
||||
{tabs[active].panel}
|
||||
</Container>
|
||||
<SizeMe refreshMode="debounce">
|
||||
{({ size }: { size: any }) => (
|
||||
<Container>
|
||||
<List>
|
||||
<TabsWrapper>
|
||||
{tabs.map((tab, index) => (
|
||||
<Item
|
||||
/* eslint-disable-next-line react/no-array-index-key */
|
||||
key={index}
|
||||
data-index={index}
|
||||
active={active === index}
|
||||
onClick={this.onToggle}
|
||||
>
|
||||
{tab.label}
|
||||
</Item>
|
||||
))}
|
||||
</TabsWrapper>
|
||||
</List>
|
||||
{tabs[active].items.length > 0 ? (
|
||||
<GlobalToggle elementWidth={size.width}>
|
||||
<HighlightToggleLabel htmlFor={highlightToggleId}>
|
||||
{highlightLabel}
|
||||
</HighlightToggleLabel>
|
||||
<HighlightToggle
|
||||
toggleId={highlightToggleId}
|
||||
type={tabs[active].type}
|
||||
elementsToHighlight={retrieveAllNodesFromResults(tabs[active].items)}
|
||||
label={highlightLabel}
|
||||
/>
|
||||
</GlobalToggle>
|
||||
) : null}
|
||||
{tabs[active].panel}
|
||||
</Container>
|
||||
)}
|
||||
</SizeMe>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -103,133 +103,12 @@ exports[`A11YPanel should render loader when it's running 1`] = `
|
||||
`;
|
||||
|
||||
exports[`A11YPanel should render report 1`] = `
|
||||
.emotion-14 {
|
||||
.emotion-0 {
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.emotion-13 {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.emotion-10 {
|
||||
box-shadow: rgba(0,0,0,.1) 0 -1px 0 0 inset;
|
||||
background: rgba(0,0,0,.05);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.emotion-1 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-top: 3px solid transparent;
|
||||
border-bottom: 3px solid transparent;
|
||||
background: transparent;
|
||||
opacity: 1;
|
||||
border-bottom: 3px solid #1EA7FD;
|
||||
}
|
||||
|
||||
.emotion-1:focus {
|
||||
outline: 0 none;
|
||||
border-bottom: 3px solid #1EA7FD;
|
||||
}
|
||||
|
||||
.emotion-0 {
|
||||
color: #FF4400;
|
||||
}
|
||||
|
||||
.emotion-3 {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-top: 3px solid transparent;
|
||||
border-bottom: 3px solid transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.emotion-3:focus {
|
||||
outline: 0 none;
|
||||
border-bottom: 3px solid #1EA7FD;
|
||||
}
|
||||
|
||||
.emotion-2 {
|
||||
color: #66BF3C;
|
||||
}
|
||||
|
||||
.emotion-4 {
|
||||
color: #E69D00;
|
||||
}
|
||||
|
||||
.emotion-9 {
|
||||
padding: 10px 15px 10px 0;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.emotion-9 input {
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.emotion-7 {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.emotion-8 {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.emotion-12 {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.emotion-11 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.emotion-18 {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
@ -241,7 +120,7 @@ exports[`A11YPanel should render report 1`] = `
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.emotion-17 {
|
||||
.emotion-3 {
|
||||
border: 0 none;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
@ -264,16 +143,16 @@ exports[`A11YPanel should render report 1`] = `
|
||||
border-radius: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.emotion-17:not(:last-child) {
|
||||
.emotion-3:not(:last-child) {
|
||||
border-right: 1px solid rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.emotion-17 + * {
|
||||
.emotion-3 + * {
|
||||
border-left: 1px solid rgba(0,0,0,.1);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.emotion-17:focus {
|
||||
.emotion-3:focus {
|
||||
box-shadow: #1EA7FD 0 -3px 0 0 inset;
|
||||
outline: 0 none;
|
||||
}
|
||||
@ -334,27 +213,25 @@ exports[`A11YPanel should render report 1`] = `
|
||||
"inserted": Object {
|
||||
"0": true,
|
||||
"110qmus": true,
|
||||
"152wg9i": true,
|
||||
"1551xjo": true,
|
||||
"15paq49": true,
|
||||
"176o2y5": true,
|
||||
"1977chw": true,
|
||||
"1cwfnw4": true,
|
||||
"1fp6daz": true,
|
||||
"19mcg9j": true,
|
||||
"1ez3l8h": true,
|
||||
"1kbt4a0": true,
|
||||
"1l7fvsg": true,
|
||||
"1myfomu": true,
|
||||
"1s6ajii": true,
|
||||
"1vwgrhn": true,
|
||||
"4ryd4s": true,
|
||||
"6hqipu": true,
|
||||
"animation-u07e3c": true,
|
||||
"aq4p19": true,
|
||||
"fg630j": true,
|
||||
"g90fw7": true,
|
||||
"iau1th": true,
|
||||
"jb2puo": true,
|
||||
"kqzqgg": true,
|
||||
"l0u0ek": true,
|
||||
"nuzmgr": true,
|
||||
"qacwg0": true,
|
||||
"qb28": true,
|
||||
"snh8f7": true,
|
||||
@ -364,7 +241,7 @@ exports[`A11YPanel should render report 1`] = `
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"registered": Object {
|
||||
"emotion-14": "overflow-y:auto;overflow-x:auto;",
|
||||
"emotion-0": "overflow-y:auto;overflow-x:auto;",
|
||||
"css-1977chw": "height:10px;width:10px;min-width:10px;color:#999999;margin-right:10px;transition:transform 0.1s ease-in-out;align-self:center;display:inline-flex;",
|
||||
"css-1l7fvsg": "height:12px;width:12px;margin-right:4px;",
|
||||
"css-jb2puo": "height:12px;width:12px;margin-right:4px;animation:animation-u07e3c 1s linear infinite;;",
|
||||
@ -605,127 +482,37 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-14{overflow-y:auto;overflow-x:auto;}
|
||||
.emotion-0{overflow-y:auto;overflow-x:auto;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-13{width:100%;position:relative;min-height:100%;}
|
||||
.emotion-4{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-10{box-shadow:rgba(0,0,0,.1) 0 -1px 0 0 inset;background:rgba(0,0,0,.05);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;white-space:nowrap;}
|
||||
.emotion-3{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-1{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;opacity:1;border-bottom:3px solid #1EA7FD;}
|
||||
.emotion-3:not(:last-child){border-right:1px solid rgba(0,0,0,.1);}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-1:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
.emotion-3 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-0{color:#FF4400;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-3{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-3:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-2{color:#66BF3C;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-4{color:#E69D00;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-9{padding:10px 15px 10px 0;cursor:pointer;font-size:13px;height:40px;border:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-9 input{margin-left:10px;margin-right:0;margin-top:0;margin-bottom:0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-7{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#666666;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-8{cursor:not-allowed;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-12{padding:30px;text-align:center;color:#333333;font-size:13px;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-11{font-weight:700;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-18{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17:not(:last-child){border-right:1px solid rgba(0,0,0,.1);}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;}
|
||||
.emotion-3:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -767,13 +554,91 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-nuzmgr{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
.css-snh8f7{width:100%;position:relative;min-height:100%;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-nuzmgr:hover{background:rgba(0,0,0,.05);}
|
||||
.css-15paq49{box-shadow:rgba(0,0,0,.1) 0 -1px 0 0 inset;background:rgba(0,0,0,.05);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;white-space:nowrap;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1551xjo{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;opacity:1;border-bottom:3px solid #1EA7FD;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1551xjo:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qacwg0{color:#FF4400;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qb28{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qb28:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-fg630j{color:#66BF3C;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-iau1th{color:#E69D00;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1ez3l8h{cursor:pointer;font-size:14px;padding:12px 0px 3px 12px;height:40px;border:none;margin-top:0px;float:left;display:block;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1ez3l8h input{margin-left:10;margin-right:0;margin-top:0;margin-bottom:0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1kbt4a0{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-bottom:3px;margin-right:3px;color:#666666;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-vdhlfv{cursor:not-allowed;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-aq4p19{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-aq4p19:hover{background:rgba(0,0,0,.05);}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -803,13 +668,13 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1fp6daz{font-weight:normal;float:right;margin-right:15px;margin-top:10px;}
|
||||
.css-19mcg9j{font-weight:normal;float:right;margin-right:15px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1fp6daz input{margin:0;}
|
||||
.css-19mcg9j input{margin:0;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -823,8 +688,18 @@ exports[`A11YPanel should render report 1`] = `
|
||||
|
||||
.css-6hqipu{shape-rendering:inherit;-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0);display:inline-block;height:12px;width:12px;margin-right:4px;}
|
||||
</style>
|
||||
<style
|
||||
id="erd_scroll_detection_scrollbar_style"
|
||||
>
|
||||
/* Created by the element-resize-detector library. */
|
||||
.erd_scroll_detection_container > div::-webkit-scrollbar { display: none; }
|
||||
|
||||
.erd_scroll_detection_container_animation_active { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: erd_scroll_detection_container_animation; animation-name: erd_scroll_detection_container_animation; }
|
||||
@-webkit-keyframes erd_scroll_detection_container_animation { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
|
||||
@keyframes erd_scroll_detection_container_animation { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
|
||||
</style>
|
||||
</head>,
|
||||
"ctr": 37,
|
||||
"ctr": 35,
|
||||
"isSpeedy": false,
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
@ -833,127 +708,37 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-14{overflow-y:auto;overflow-x:auto;}
|
||||
.emotion-0{overflow-y:auto;overflow-x:auto;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-13{width:100%;position:relative;min-height:100%;}
|
||||
.emotion-4{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-10{box-shadow:rgba(0,0,0,.1) 0 -1px 0 0 inset;background:rgba(0,0,0,.05);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;white-space:nowrap;}
|
||||
.emotion-3{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-1{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;opacity:1;border-bottom:3px solid #1EA7FD;}
|
||||
.emotion-3:not(:last-child){border-right:1px solid rgba(0,0,0,.1);}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-1:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
.emotion-3 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-0{color:#FF4400;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-3{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-3:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-2{color:#66BF3C;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-4{color:#E69D00;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-9{padding:10px 15px 10px 0;cursor:pointer;font-size:13px;height:40px;border:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-9 input{margin-left:10px;margin-right:0;margin-top:0;margin-bottom:0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-7{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#666666;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-8{cursor:not-allowed;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-12{padding:30px;text-align:center;color:#333333;font-size:13px;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-11{font-weight:700;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-18{position:absolute;bottom:0;right:0;max-width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background:#FFFFFF;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17{border:0 none;padding:4px 10px;cursor:pointer;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#333333;background:#FFFFFF;font-size:12px;line-height:16px;font-weight:700;border-top:1px solid rgba(0,0,0,.1);border-left:1px solid rgba(0,0,0,.1);margin-left:-1px;border-radius:4px 0 0 0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17:not(:last-child){border-right:1px solid rgba(0,0,0,.1);}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17 + *{border-left:1px solid rgba(0,0,0,.1);border-radius:0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.emotion-17:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;}
|
||||
.emotion-3:focus{box-shadow:#1EA7FD 0 -3px 0 0 inset;outline:0 none;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -995,13 +780,91 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-nuzmgr{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
.css-snh8f7{width:100%;position:relative;min-height:100%;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-nuzmgr:hover{background:rgba(0,0,0,.05);}
|
||||
.css-15paq49{box-shadow:rgba(0,0,0,.1) 0 -1px 0 0 inset;background:rgba(0,0,0,.05);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;white-space:nowrap;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1551xjo{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;opacity:1;border-bottom:3px solid #1EA7FD;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1551xjo:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qacwg0{color:#FF4400;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qb28{-webkit-text-decoration:none;text-decoration:none;padding:10px 15px;cursor:pointer;font-weight:700;font-size:13px;line-height:1;height:40px;border:none;border-top:3px solid transparent;border-bottom:3px solid transparent;background:transparent;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-qb28:focus{outline:0 none;border-bottom:3px solid #1EA7FD;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-fg630j{color:#66BF3C;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-iau1th{color:#E69D00;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1ez3l8h{cursor:pointer;font-size:14px;padding:12px 0px 3px 12px;height:40px;border:none;margin-top:0px;float:left;display:block;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1ez3l8h input{margin-left:10;margin-right:0;margin-top:0;margin-bottom:0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1kbt4a0{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-bottom:3px;margin-right:3px;color:#666666;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-vdhlfv{cursor:not-allowed;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-aq4p19{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;border-bottom:1px solid rgba(0,0,0,.1);}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-aq4p19:hover{background:rgba(0,0,0,.05);}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -1031,13 +894,13 @@ exports[`A11YPanel should render report 1`] = `
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1fp6daz{font-weight:normal;float:right;margin-right:15px;margin-top:10px;}
|
||||
.css-19mcg9j{font-weight:normal;float:right;margin-right:15px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
>
|
||||
|
||||
.css-1fp6daz input{margin:0;}
|
||||
.css-19mcg9j input{margin:0;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
@ -1070,15 +933,15 @@ exports[`A11YPanel should render report 1`] = `
|
||||
vertical={true}
|
||||
>
|
||||
<Component
|
||||
className="emotion-14"
|
||||
className="emotion-0"
|
||||
horizontal={true}
|
||||
vertical={true}
|
||||
>
|
||||
<l
|
||||
className="emotion-14"
|
||||
className="emotion-0"
|
||||
>
|
||||
<div
|
||||
className="emotion-14"
|
||||
className="emotion-0"
|
||||
data-simplebar={true}
|
||||
>
|
||||
<div
|
||||
@ -1111,9 +974,8 @@ exports[`A11YPanel should render report 1`] = `
|
||||
Violations
|
||||
</ForwardRef(render)>,
|
||||
"panel": <Report
|
||||
empty="No a11y violations found."
|
||||
empty="No accessibility violations found."
|
||||
items={Array []}
|
||||
passes={false}
|
||||
type={0}
|
||||
/>,
|
||||
"type": 0,
|
||||
@ -1125,9 +987,8 @@ exports[`A11YPanel should render report 1`] = `
|
||||
Passes
|
||||
</ForwardRef(render)>,
|
||||
"panel": <Report
|
||||
empty="No a11y check passed."
|
||||
empty="No accessibility checks passed."
|
||||
items={Array []}
|
||||
passes={true}
|
||||
type={1}
|
||||
/>,
|
||||
"type": 1,
|
||||
@ -1139,9 +1000,8 @@ exports[`A11YPanel should render report 1`] = `
|
||||
Incomplete
|
||||
</ForwardRef(render)>,
|
||||
"panel": <Report
|
||||
empty="No a11y incomplete found."
|
||||
empty="No accessibility checks incomplete."
|
||||
items={Array []}
|
||||
passes={false}
|
||||
type={2}
|
||||
/>,
|
||||
"type": 2,
|
||||
@ -1149,163 +1009,39 @@ exports[`A11YPanel should render report 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-13"
|
||||
<SizeMe
|
||||
refreshMode="debounce"
|
||||
>
|
||||
<SizeMe(Component)
|
||||
onSize={[Function]}
|
||||
>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-10"
|
||||
>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-6"
|
||||
>
|
||||
<Styled(button)
|
||||
active={true}
|
||||
data-index={0}
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="emotion-1"
|
||||
data-index={0}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Styled(span)>
|
||||
<span
|
||||
className="emotion-0"
|
||||
>
|
||||
0
|
||||
Violations
|
||||
</span>
|
||||
</Styled(span)>
|
||||
</button>
|
||||
</Styled(button)>
|
||||
<Styled(button)
|
||||
active={false}
|
||||
data-index={1}
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="emotion-3"
|
||||
data-index={1}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Styled(span)>
|
||||
<span
|
||||
className="emotion-2"
|
||||
>
|
||||
0
|
||||
Passes
|
||||
</span>
|
||||
</Styled(span)>
|
||||
</button>
|
||||
</Styled(button)>
|
||||
<Styled(button)
|
||||
active={false}
|
||||
data-index={2}
|
||||
key="2"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="emotion-3"
|
||||
data-index={2}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Styled(span)>
|
||||
<span
|
||||
className="emotion-4"
|
||||
>
|
||||
0
|
||||
Incomplete
|
||||
</span>
|
||||
</Styled(span)>
|
||||
</button>
|
||||
</Styled(button)>
|
||||
</div>
|
||||
</Styled(div)>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-9"
|
||||
>
|
||||
<Styled(label)
|
||||
htmlFor="0-global-checkbox"
|
||||
>
|
||||
<label
|
||||
className="emotion-7"
|
||||
htmlFor="0-global-checkbox"
|
||||
>
|
||||
Highlight results
|
||||
</label>
|
||||
</Styled(label)>
|
||||
<ConnectFunction
|
||||
elementsToHighlight={Array []}
|
||||
label="Highlight results"
|
||||
toggleId="0-global-checkbox"
|
||||
type={0}
|
||||
>
|
||||
<HighlightToggle
|
||||
addElement={[Function]}
|
||||
elementsToHighlight={Array []}
|
||||
highlightedElementsMap={Map {}}
|
||||
indeterminate={false}
|
||||
isToggledOn={false}
|
||||
label="Highlight results"
|
||||
toggleId="0-global-checkbox"
|
||||
type={0}
|
||||
>
|
||||
<Styled(input)
|
||||
aria-label="Highlight result"
|
||||
checked={false}
|
||||
disabled={true}
|
||||
id="0-global-checkbox"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
>
|
||||
<input
|
||||
aria-label="Highlight result"
|
||||
checked={false}
|
||||
className="emotion-8"
|
||||
disabled={true}
|
||||
id="0-global-checkbox"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
</Styled(input)>
|
||||
</HighlightToggle>
|
||||
</ConnectFunction>
|
||||
</div>
|
||||
</Styled(div)>
|
||||
</div>
|
||||
</Styled(div)>
|
||||
<Report
|
||||
empty="No a11y violations found."
|
||||
items={Array []}
|
||||
passes={false}
|
||||
type={0}
|
||||
<SizeMeRenderer(Component)
|
||||
disablePlaceholder={false}
|
||||
explicitRef={[Function]}
|
||||
onSize={[Function]}
|
||||
size={
|
||||
Object {
|
||||
"height": undefined,
|
||||
"position": undefined,
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Placeholder
|
||||
key="placeholder"
|
||||
>
|
||||
<Styled(div)>
|
||||
<SizeMeReferenceWrapper>
|
||||
<SizeMePlaceholder>
|
||||
<div
|
||||
className="emotion-12"
|
||||
>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-11"
|
||||
>
|
||||
No a11y violations found.
|
||||
</div>
|
||||
</Styled(div)>
|
||||
</div>
|
||||
</Styled(div)>
|
||||
</Placeholder>
|
||||
</Report>
|
||||
</div>
|
||||
</Styled(div)>
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</SizeMePlaceholder>
|
||||
</SizeMeReferenceWrapper>
|
||||
</SizeMeRenderer(Component)>
|
||||
</SizeMe(Component)>
|
||||
</SizeMe>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
@ -1346,14 +1082,14 @@ exports[`A11YPanel should render report 1`] = `
|
||||
>
|
||||
<Styled(div)>
|
||||
<div
|
||||
className="emotion-18"
|
||||
className="emotion-4"
|
||||
>
|
||||
<ActionButton
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
className="emotion-17"
|
||||
className="emotion-3"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Rerun tests
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
|
||||
import { document } from 'global';
|
||||
import axe, { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core';
|
||||
import deprecate from 'util-deprecate';
|
||||
@ -35,7 +36,6 @@ const run = (element: ElementContext, config: Spec, options: RunOptions) => {
|
||||
.run(
|
||||
element || getElement(),
|
||||
options ||
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
({
|
||||
restoreScroll: true,
|
||||
} as RunOptions) // cast to RunOptions is necessary because axe types are not up to date
|
||||
|
@ -28,8 +28,10 @@ function rootReducer(state = initialState, action: any) {
|
||||
action.payload.highlightedElementData
|
||||
),
|
||||
};
|
||||
} else if (action.type === CLEAR_ELEMENTS) {
|
||||
for (let key of Array.from(state.highlightedElementsMap.keys())) {
|
||||
}
|
||||
if (action.type === CLEAR_ELEMENTS) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Array.from(state.highlightedElementsMap.keys())) {
|
||||
key.style.outline = state.highlightedElementsMap.get(key).originalOutline;
|
||||
state.highlightedElementsMap.delete(key);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { Fragment, FunctionComponent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { addons, types } from '@storybook/addons';
|
||||
import { ADDON_ID, PANEL_ID } from './constants';
|
||||
import { ColorBlindness } from './components/ColorBlindness';
|
||||
import { A11YPanel } from './components/A11YPanel';
|
||||
import { addons, types } from '@storybook/addons';
|
||||
|
||||
const Hidden = styled.div(() => ({
|
||||
'&, & svg': {
|
||||
|
1
addons/a11y/src/typings.d.ts
vendored
1
addons/a11y/src/typings.d.ts
vendored
@ -1 +1,2 @@
|
||||
declare module 'global';
|
||||
declare module 'react-sizeme';
|
||||
|
@ -4,7 +4,7 @@ Storybook Addon Actions can be used to display data received by event handlers i
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -21,11 +21,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"global": "^4.3.2",
|
||||
|
@ -26,8 +26,6 @@ const safeDeepEqual = (a: any, b: any): boolean => {
|
||||
};
|
||||
|
||||
export default class ActionLogger extends Component<ActionLoggerProps, ActionLoggerState> {
|
||||
private mounted: boolean;
|
||||
|
||||
constructor(props: ActionLoggerProps) {
|
||||
super(props);
|
||||
|
||||
@ -75,6 +73,8 @@ export default class ActionLogger extends Component<ActionLoggerProps, ActionLog
|
||||
this.setState({ actions: [] });
|
||||
};
|
||||
|
||||
private mounted: boolean;
|
||||
|
||||
render() {
|
||||
const { actions = [] } = this.state;
|
||||
const { active } = this.props;
|
||||
|
@ -8,8 +8,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti
|
||||
...options,
|
||||
};
|
||||
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
const handler = function action(...args: any[]) {
|
||||
const handler = function actionHandler(...args: any[]) {
|
||||
const channel = addons.getChannel();
|
||||
const id = uuid();
|
||||
const minDepth = 5; // anything less is really just storybook internals
|
||||
|
@ -30,7 +30,6 @@ const hasMatchInAncestry = (element: any, selector: any): boolean => {
|
||||
const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) => {
|
||||
const actionsObject = actionsFn(...args);
|
||||
return Object.entries(actionsObject).map(([key, action]) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, eventName, selector] = key.match(delegateEventSplitter);
|
||||
return {
|
||||
eventName,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "A storybook addon to show different backgrounds for your preview",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -25,12 +25,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/client-logger": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/client-logger": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"memoizerific": "^1.11.3",
|
||||
"react": "^16.8.4",
|
||||
|
@ -103,12 +103,14 @@ export class BackgroundSelector extends Component<{ api: API }, State> {
|
||||
};
|
||||
|
||||
change = ({ selected, name }: { selected: string; name: string }) => {
|
||||
this.props.api.emit(EVENTS.UPDATE, { selected, name });
|
||||
const { api } = this.props;
|
||||
api.emit(EVENTS.UPDATE, { selected, name });
|
||||
this.setState({ selected, expanded: false });
|
||||
};
|
||||
|
||||
onVisibilityChange = (s: boolean) => {
|
||||
if (this.state.expanded !== s) {
|
||||
const { expanded } = this.state;
|
||||
if (expanded !== s) {
|
||||
this.setState({ expanded: s });
|
||||
}
|
||||
};
|
||||
|
1
addons/centered/angular.d.ts
vendored
1
addons/centered/angular.d.ts
vendored
@ -1,4 +1,3 @@
|
||||
// tslint:disable-next-line:no-implicit-dependencies
|
||||
export interface ICollection {
|
||||
[p: string]: any;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-centered",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Storybook decorator to center components",
|
||||
"keywords": [
|
||||
"addon",
|
||||
|
@ -31,11 +31,13 @@ once then apply it everywhere**.
|
||||
use it to bridge with your favorite routing, state-management solutions, or even your own
|
||||
[React Context](https://reactjs.org/docs/context.html) provider.
|
||||
4. Offer chainable and granular configurations. It is even possible to fine-tune at per story level.
|
||||
5. Visual regression friendly. You can use this addon to driving the same story under different contexts to smoke
|
||||
testing important visual states.
|
||||
|
||||
## 🧰 Requirements
|
||||
|
||||
Make sure the version of your Storybook is above v5. Currently, this addon supports the following frameworks:
|
||||
**React**, and **Vue**. Other frameworks might get support in the near future (PRs are welcome!).
|
||||
Make sure the version of your Storybook is above v5. For the full list the current supported framework, see
|
||||
[Addon / Framework Support Table](../../ADDONS_SUPPORT.md).
|
||||
|
||||
## 🎬 Getting started
|
||||
|
||||
@ -56,8 +58,8 @@ To load your contextual setups for your stories globally, adding the following l
|
||||
see it near your `addon.js` file):
|
||||
|
||||
```js
|
||||
import { addDecorator } from '@storybook/react'; // or '@storybook/vue'
|
||||
import { withContexts } from '@storybook/addon-contexts/react'; // or '@storybook/addon-contexts/vue'
|
||||
import { addDecorator } from '@storybook/[framework]';
|
||||
import { withContexts } from '@storybook/addon-contexts/[framework]';
|
||||
import { contexts } from './configs/contexts'; // we will define the contextual setups later in API section
|
||||
|
||||
addDecorator(withContexts(contexts));
|
||||
@ -66,8 +68,8 @@ addDecorator(withContexts(contexts));
|
||||
Alternatively, just like other addons, you can use this addon only for a given set of stories:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react'; // or '@storybook/vue'
|
||||
import { withContexts } from '@storybook/addon-contexts/react'; // or '@storybook/addon-contexts/vue'
|
||||
import { storiesOf } from '@storybook/[framework]';
|
||||
import { withContexts } from '@storybook/addon-contexts/[framework]';
|
||||
import { contexts } from './configs/contexts';
|
||||
|
||||
const story = storiesOf('Component With Contexts', module).addDecorator(withContexts(contexts)); // use this addon with a default contextual environment setups
|
||||
@ -227,6 +229,9 @@ be shown at first in the toolbar menu in your Storybook.
|
||||
4. The addon will persist the selected params (the addon state) between stories at run-time (similar to other
|
||||
addons). If the active param were gone after story switching, it fallback to the default then the first. As a
|
||||
rule of thumbs, whenever collisions made possible, always the first wins.
|
||||
5. Query parameters are supported for pre-selecting contexts param, which comes handy for visual regression testing.
|
||||
You can do this by appending `&contexts=[name of contexts]=[name of param]` in the URL under iframe mode. Use `,`
|
||||
to separate multiple contexts (e.g. `&contexts=Theme=Forests,Language=Fr`).
|
||||
|
||||
## 📖 License
|
||||
|
||||
|
@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "@storybook/addon-contexts",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Storybook Addon Contexts",
|
||||
"keywords": [
|
||||
"storybook",
|
||||
"preact",
|
||||
"react",
|
||||
"vue"
|
||||
],
|
||||
"author": "Leo Y. Li",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/register.js",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"register.js",
|
||||
"preact.js",
|
||||
"react.js",
|
||||
"vue.js"
|
||||
],
|
||||
@ -23,15 +24,21 @@
|
||||
"directory": "addons/contexts"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
"prepare": "node ../../scripts/prepare.js",
|
||||
"dev:check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/channels": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33"
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"global": "*",
|
||||
"qs": "*"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"preact": "*",
|
||||
"react": "*",
|
||||
"vue": "*"
|
||||
},
|
||||
|
4
addons/contexts/preact.js
Normal file
4
addons/contexts/preact.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { withContexts } from './dist/preview/frameworks/preact';
|
||||
|
||||
export { withContexts };
|
||||
export default withContexts;
|
@ -1,7 +1,38 @@
|
||||
import { withContexts } from './preview/frameworks/react';
|
||||
export { withContexts };
|
||||
export default withContexts;
|
||||
import { makeDecorator, StoryWrapper } from '@storybook/addons';
|
||||
import { ContextsPreviewAPI } from './preview/ContextsPreviewAPI';
|
||||
import { ID, PARAM } from './shared/constants';
|
||||
import { AddonSetting, AnyFunctionReturns, ContextNode, PropsMap } from './shared/types.d';
|
||||
|
||||
console.error(
|
||||
`[addon-contexts] Deprecation warning: "import { withContexts } from 'addon-contexts'" has been deprecated. Please import from 'addon-contexts/react' instead.`
|
||||
);
|
||||
/**
|
||||
* This file serves a idiomatic facade of a Storybook decorator.
|
||||
*
|
||||
* Wrapper function get called whenever the Storybook rerender the view. This reflow logic is
|
||||
* framework agnostic; on the other hand, the framework specific bindings are the implementation
|
||||
* details hidden behind the passed `render` function.
|
||||
*
|
||||
* Here, we need a dedicated singleton as a state manager for preview (the addon API, in vanilla)
|
||||
* who is also knowing how to communicate with the Storybook manager (in React) via the Storybook
|
||||
* event system.
|
||||
*
|
||||
* @param {Render} render - framework specific bindings
|
||||
*/
|
||||
export type Render<T> = (...args: [ContextNode[], PropsMap, AnyFunctionReturns<T>]) => T;
|
||||
type CreateAddonDecorator = <T>(render: Render<T>) => (contexts: AddonSetting[]) => unknown;
|
||||
|
||||
export const createAddonDecorator: CreateAddonDecorator = render => {
|
||||
const wrapper: StoryWrapper = (getStory, context, settings: any) => {
|
||||
const { getContextNodes, getSelectionState, getPropsMap } = ContextsPreviewAPI();
|
||||
const nodes = getContextNodes(settings);
|
||||
const state = getSelectionState();
|
||||
const props = getPropsMap(nodes, state);
|
||||
return render(nodes, props, () => getStory(context));
|
||||
};
|
||||
|
||||
return makeDecorator({
|
||||
name: ID,
|
||||
parameterName: PARAM,
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: false,
|
||||
wrapper,
|
||||
});
|
||||
};
|
||||
|
@ -1,31 +0,0 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useChannel, Channel } from './libs/useChannel';
|
||||
import { ToolBar } from './ToolBar';
|
||||
import { REBOOT_MANAGER, UPDATE_MANAGER, UPDATE_PREVIEW } from '../constants';
|
||||
import { SelectionState, FCNoChildren } from '../types';
|
||||
|
||||
/**
|
||||
* Control addon states and addon-story interactions
|
||||
*/
|
||||
type AddonManager = FCNoChildren<{
|
||||
channel: Channel;
|
||||
}>;
|
||||
|
||||
export const AddonManager: AddonManager = ({ channel }) => {
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [state, setState] = useState<SelectionState>(undefined);
|
||||
const setSelected = useCallback(
|
||||
(nodeId, name) => setState((obj = {}) => ({ ...obj, [nodeId]: name })),
|
||||
[]
|
||||
);
|
||||
|
||||
// from preview
|
||||
useChannel(UPDATE_MANAGER, newNodes => setNodes(newNodes), []);
|
||||
useChannel(UPDATE_MANAGER, (_, newState) => newState && setState(newState), []);
|
||||
|
||||
// to preview
|
||||
useEffect(() => channel.emit(REBOOT_MANAGER), []);
|
||||
useEffect(() => state && channel.emit(UPDATE_PREVIEW, state), [state]);
|
||||
|
||||
return <ToolBar nodes={nodes} state={state || {}} setSelected={setSelected} />;
|
||||
};
|
32
addons/contexts/src/manager/ContextsManager.tsx
Normal file
32
addons/contexts/src/manager/ContextsManager.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useChannel } from './libs/useChannel';
|
||||
import { ToolBar } from './components/ToolBar';
|
||||
import { deserialize, serialize } from '../shared/serializers';
|
||||
import { PARAM, REBOOT_MANAGER, UPDATE_MANAGER, UPDATE_PREVIEW } from '../shared/constants';
|
||||
import { FCNoChildren, ManagerAPI } from '../shared/types.d';
|
||||
|
||||
/**
|
||||
* A smart component for handling manager-preview interactions.
|
||||
*/
|
||||
type ContextsManager = FCNoChildren<{
|
||||
api: ManagerAPI;
|
||||
}>;
|
||||
|
||||
export const ContextsManager: ContextsManager = ({ api }) => {
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [state, setState] = useState(deserialize(api.getQueryParam(PARAM)));
|
||||
const setSelected = useCallback(
|
||||
(nodeId, name) => setState(obj => ({ ...obj, [nodeId]: name })),
|
||||
[]
|
||||
);
|
||||
|
||||
// from preview
|
||||
useChannel(UPDATE_MANAGER, newNodes => setNodes(newNodes), []);
|
||||
|
||||
// to preview
|
||||
useEffect(() => api.emit(REBOOT_MANAGER), []);
|
||||
useEffect(() => api.emit(UPDATE_PREVIEW, state), [state]);
|
||||
useEffect(() => api.setQueryParams({ [PARAM]: serialize(state) }), [state]);
|
||||
|
||||
return <ToolBar nodes={nodes} state={state || {}} setSelected={setSelected} />;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { Separator } from '@storybook/components';
|
||||
import { ToolbarControl } from './ToolbarControl';
|
||||
import { ContextNode, FCNoChildren, SelectionState } from '../types';
|
||||
import { ContextNode, FCNoChildren, SelectionState } from '../../shared/types.d';
|
||||
|
||||
type ToolBar = FCNoChildren<{
|
||||
nodes: ContextNode[];
|
||||
@ -13,16 +13,14 @@ export const ToolBar: ToolBar = React.memo(({ nodes, state, setSelected }) =>
|
||||
nodes.length ? (
|
||||
<>
|
||||
<Separator />
|
||||
{nodes.map(({ components, ...forwardProps }) =>
|
||||
forwardProps.params.length > 1 ? (
|
||||
<ToolbarControl
|
||||
{...forwardProps}
|
||||
setSelected={setSelected}
|
||||
selected={state[forwardProps.nodeId]}
|
||||
key={forwardProps.nodeId}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
{nodes.map(({ components, ...forwardProps }) => (
|
||||
<ToolbarControl
|
||||
{...forwardProps}
|
||||
setSelected={setSelected}
|
||||
selected={state[forwardProps.nodeId]}
|
||||
key={forwardProps.nodeId}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : null
|
||||
);
|
@ -1,10 +1,10 @@
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { Icons, IconButton, WithTooltip } from '@storybook/components';
|
||||
import { ToolBarMenuOptions } from './ToolBarMenuOptions';
|
||||
import { ContextNode, FCNoChildren } from '../types';
|
||||
import { ContextNode, FCNoChildren } from '../../shared/types.d';
|
||||
|
||||
type ToolBarMenu = FCNoChildren<{
|
||||
icon: ContextNode['icon'];
|
||||
icon: ComponentProps<typeof Icons>['icon'];
|
||||
title: ContextNode['title'];
|
||||
active: boolean;
|
||||
expanded: boolean;
|
||||
@ -19,18 +19,17 @@ export const ToolBarMenu: ToolBarMenu = ({
|
||||
expanded,
|
||||
setExpanded,
|
||||
optionsProps,
|
||||
}) =>
|
||||
icon ? (
|
||||
<WithTooltip
|
||||
closeOnClick
|
||||
trigger="click"
|
||||
placement="top"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={setExpanded}
|
||||
tooltip={<ToolBarMenuOptions {...optionsProps} />}
|
||||
>
|
||||
<IconButton active={active} title={title}>
|
||||
<Icons icon={icon} />
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
) : null;
|
||||
}) => (
|
||||
<WithTooltip
|
||||
closeOnClick
|
||||
trigger="click"
|
||||
placement="top"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={setExpanded}
|
||||
tooltip={<ToolBarMenuOptions {...optionsProps} />}
|
||||
>
|
||||
<IconButton active={active} title={title}>
|
||||
<Icons icon={icon} />
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
);
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TooltipLinkList } from '@storybook/components';
|
||||
import { OPT_OUT } from '../constants';
|
||||
import { FCNoChildren } from '../types';
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
import { FCNoChildren } from '../../shared/types.d';
|
||||
|
||||
type ToolBarMenuOptions = FCNoChildren<{
|
||||
activeName: string;
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ToolBarMenu } from './ToolBarMenu';
|
||||
import { OPT_OUT } from '../constants';
|
||||
import { ContextNode, FCNoChildren, Omit } from '../types';
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
import { ContextNode, FCNoChildren, Omit } from '../../shared/types.d';
|
||||
|
||||
type ToolbarControl = FCNoChildren<
|
||||
Omit<
|
||||
@ -25,15 +25,14 @@ export const ToolbarControl: ToolbarControl = ({
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const paramNames = params.map(({ name }) => name);
|
||||
const activeName =
|
||||
// validate the selected name
|
||||
(paramNames.concat(OPT_OUT).includes(selected) && selected) ||
|
||||
// validate the integrity of the selected name
|
||||
([...paramNames, options.cancelable && OPT_OUT].includes(selected) && selected) ||
|
||||
// fallback to default
|
||||
(params.find(param => !!param.default) || { name: null }).name ||
|
||||
// fallback to the first
|
||||
params[0].name;
|
||||
const list = options.cancelable === false ? paramNames : [OPT_OUT, ...paramNames];
|
||||
const list = options.cancelable ? [OPT_OUT, ...paramNames] : paramNames;
|
||||
const props = {
|
||||
icon,
|
||||
title,
|
||||
active: activeName !== OPT_OUT,
|
||||
expanded,
|
||||
@ -48,5 +47,5 @@ export const ToolbarControl: ToolbarControl = ({
|
||||
},
|
||||
};
|
||||
|
||||
return options.disable || list.length < 2 ? null : <ToolBarMenu {...props} />;
|
||||
return icon && list.length && !options.disable ? <ToolBarMenu icon={icon} {...props} /> : null;
|
||||
};
|
@ -1,7 +1,6 @@
|
||||
export { Channel } from '@storybook/channels';
|
||||
import addons from '@storybook/addons';
|
||||
import { useEffect } from 'react';
|
||||
import { AnyFunctionReturns } from '../../types';
|
||||
import { AnyFunctionReturns } from '../../shared/types.d';
|
||||
|
||||
/**
|
||||
* The React hook version of Storybook Channel API.
|
||||
|
81
addons/contexts/src/preview/ContextsPreviewAPI.ts
Normal file
81
addons/contexts/src/preview/ContextsPreviewAPI.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { window } from 'global';
|
||||
import { parse } from 'qs';
|
||||
import { getContextNodes, getPropsMap, getRendererFrom, singleton } from './libs';
|
||||
import { deserialize } from '../shared/serializers';
|
||||
import {
|
||||
PARAM,
|
||||
REBOOT_MANAGER,
|
||||
UPDATE_PREVIEW,
|
||||
UPDATE_MANAGER,
|
||||
FORCE_RE_RENDER,
|
||||
SET_CURRENT_STORY,
|
||||
} from '../shared/constants';
|
||||
import { ContextNode, PropsMap, SelectionState } from '../shared/types.d';
|
||||
|
||||
/**
|
||||
* A singleton for handling preview-manager and one-time-only side-effects.
|
||||
*/
|
||||
export const ContextsPreviewAPI = singleton(() => {
|
||||
const channel = addons.getChannel();
|
||||
let contextsNodesMemo: ContextNode[] | null = null;
|
||||
let selectionState: SelectionState = {};
|
||||
|
||||
/**
|
||||
* URL query param can be used to predetermine the contexts a story should render,
|
||||
* which is useful for performing image snapshot testing or URL sharing.
|
||||
*/
|
||||
if (window && window.location) {
|
||||
selectionState = deserialize(parse(window.location.search)[PARAM]) || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* (Vue specific)
|
||||
* Vue will inject getter/setter watchers on the first rendering of the addon,
|
||||
* which is why we have to keep an internal reference and use `Object.assign` to notify the watcher.
|
||||
*/
|
||||
const reactivePropsMap = {};
|
||||
const updateReactiveSystem = (propsMap: PropsMap) => Object.assign(reactivePropsMap, propsMap);
|
||||
|
||||
/**
|
||||
* Preview-manager communications.
|
||||
*/
|
||||
// from manager
|
||||
channel.on(UPDATE_PREVIEW, state => {
|
||||
if (state) {
|
||||
selectionState = state;
|
||||
channel.emit(FORCE_RE_RENDER);
|
||||
}
|
||||
});
|
||||
channel.on(REBOOT_MANAGER, () => {
|
||||
channel.emit(UPDATE_MANAGER, contextsNodesMemo);
|
||||
});
|
||||
channel.on(SET_CURRENT_STORY, () => {
|
||||
// trash the memorization since the story-level setting may change (diffing it is much expensive)
|
||||
contextsNodesMemo = null;
|
||||
});
|
||||
|
||||
// to manager
|
||||
const getContextNodesWithSideEffects: typeof getContextNodes = (...arg) => {
|
||||
if (contextsNodesMemo === null) {
|
||||
contextsNodesMemo = getContextNodes(...arg);
|
||||
channel.emit(UPDATE_MANAGER, contextsNodesMemo);
|
||||
}
|
||||
return contextsNodesMemo;
|
||||
};
|
||||
|
||||
/**
|
||||
* @Public
|
||||
* Exposed interfaces
|
||||
*/
|
||||
return {
|
||||
// methods get called on Storybook event lifecycle
|
||||
getContextNodes: getContextNodesWithSideEffects,
|
||||
getSelectionState: () => selectionState,
|
||||
getPropsMap,
|
||||
|
||||
// methods for processing framework specific bindings
|
||||
getRendererFrom,
|
||||
updateReactiveSystem,
|
||||
};
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { FORCE_RE_RENDER, SET_CURRENT_STORY } from '@storybook/core-events';
|
||||
import { REBOOT_MANAGER, UPDATE_PREVIEW, UPDATE_MANAGER } from '../constants';
|
||||
import { getContextNodes, getPropsMap, getRendererFrom, singleton } from './libs';
|
||||
import { ContextNode, PropsMap } from '../types';
|
||||
|
||||
/**
|
||||
* @Public
|
||||
* A singleton for handling wrapper-manager side-effects
|
||||
*/
|
||||
export const addonContextsAPI = singleton(() => {
|
||||
const channel = addons.getChannel();
|
||||
let memorizedNodes: null | ContextNode[] = null;
|
||||
let selectionState = {};
|
||||
|
||||
// from manager
|
||||
channel.on(SET_CURRENT_STORY, () => (memorizedNodes = null));
|
||||
channel.on(REBOOT_MANAGER, () => channel.emit(UPDATE_MANAGER, memorizedNodes, selectionState));
|
||||
channel.on(UPDATE_PREVIEW, state => (selectionState = Object.freeze(state)));
|
||||
channel.on(UPDATE_PREVIEW, () => channel.emit(FORCE_RE_RENDER));
|
||||
|
||||
// to manager
|
||||
const getContextNodesWithSideEffects: typeof getContextNodes = (...arg) => {
|
||||
// we want to notify the manager only when the story changed since `parameter` can be changed
|
||||
if (memorizedNodes === null) {
|
||||
memorizedNodes = getContextNodes(...arg);
|
||||
channel.emit(UPDATE_MANAGER, memorizedNodes);
|
||||
}
|
||||
return memorizedNodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* (Vue specific)
|
||||
* Vue will inject getter/setters on the first rendering of the addon,
|
||||
* which is the reason why we have to keep an internal reference and use `Object.assign` to update it.
|
||||
*/
|
||||
let reactivePropsMap = {};
|
||||
const updateReactiveSystem = (propsMap: PropsMap) =>
|
||||
/* tslint:disable:prefer-object-spread */
|
||||
Object.assign(reactivePropsMap, propsMap);
|
||||
|
||||
return {
|
||||
// methods get called on Storybook event lifecycle
|
||||
getContextNodes: getContextNodesWithSideEffects,
|
||||
getSelectionState: () => selectionState,
|
||||
getPropsMap,
|
||||
|
||||
// methods for processing framework specific bindings
|
||||
getRendererFrom,
|
||||
updateReactiveSystem,
|
||||
};
|
||||
});
|
14
addons/contexts/src/preview/frameworks/preact.ts
Normal file
14
addons/contexts/src/preview/frameworks/preact.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import Preact from 'preact';
|
||||
import { createAddonDecorator, Render } from '../../index';
|
||||
import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
|
||||
|
||||
/**
|
||||
* This is the framework specific bindings for Preact.
|
||||
* '@storybook/preact' expects the returning object from a decorator to be a 'Preact vNode'.
|
||||
*/
|
||||
export const renderPreact: Render<Preact.VNode> = (contextNodes, propsMap, getStoryVNode) => {
|
||||
const { getRendererFrom } = ContextsPreviewAPI();
|
||||
return getRendererFrom(Preact.h)(contextNodes, propsMap, getStoryVNode);
|
||||
};
|
||||
|
||||
export const withContexts = createAddonDecorator(renderPreact);
|
@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { createAddonDecorator, Render } from '../index';
|
||||
import { addonContextsAPI } from '../api';
|
||||
import { createAddonDecorator, Render } from '../../index';
|
||||
import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
|
||||
|
||||
/**
|
||||
* This is the framework specific bindings for React.
|
||||
* '@storybook/react' expects the returning object from a decorator to be a 'React Element' (vNode).
|
||||
*/
|
||||
export const renderReact: Render<React.ReactElement> = (contextNodes, propsMap, getStoryVNode) => {
|
||||
const { getRendererFrom } = addonContextsAPI();
|
||||
const { getRendererFrom } = ContextsPreviewAPI();
|
||||
return getRendererFrom(React.createElement)(contextNodes, propsMap, getStoryVNode);
|
||||
};
|
||||
|
||||
|
@ -1,22 +1,27 @@
|
||||
import Vue from 'vue';
|
||||
import { createAddonDecorator, Render } from '../index';
|
||||
import { addonContextsAPI } from '../api';
|
||||
import { ID } from '../../constants';
|
||||
import { createAddonDecorator, Render } from '../../index';
|
||||
import { ContextsPreviewAPI } from '../ContextsPreviewAPI';
|
||||
import { ID } from '../../shared/constants';
|
||||
|
||||
/**
|
||||
* This is the framework specific bindings for Vue.
|
||||
* '@storybook/vue' expects the returning object from a decorator to be a 'VueComponent'.
|
||||
*/
|
||||
export const renderVue: Render<Vue.Component> = (contextNodes, propsMap, getStoryVNode) => {
|
||||
const { getRendererFrom, updateReactiveSystem } = addonContextsAPI();
|
||||
export const renderVue: Render<Vue.Component> = (contextNodes, propsMap, getStoryComponent) => {
|
||||
const { getRendererFrom, updateReactiveSystem } = ContextsPreviewAPI();
|
||||
const reactiveProps = updateReactiveSystem(propsMap);
|
||||
return Vue.extend({
|
||||
name: ID,
|
||||
data: () => reactiveProps,
|
||||
render: createElement =>
|
||||
getRendererFrom((component, props, children) =>
|
||||
createElement(component, { props }, [children])
|
||||
)(contextNodes, reactiveProps, () => createElement(getStoryVNode())),
|
||||
getRendererFrom((Component, props, children) => {
|
||||
const { key, ref, style, classNames, ...rest } = props || Object();
|
||||
const contextData =
|
||||
Component instanceof Object
|
||||
? { key, ref, style, class: classNames, props: rest } // component as a Vue object
|
||||
: { key, ref, style, class: classNames, attrs: rest }; // component as a HTML tag string
|
||||
return createElement(Component, contextData, [children]);
|
||||
})(contextNodes, reactiveProps, () => createElement(getStoryComponent())),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { makeDecorator, StoryWrapper } from '@storybook/addons';
|
||||
import { addonContextsAPI } from './api';
|
||||
import { ID, PARAM } from '../constants';
|
||||
import { AddonSetting, AnyFunctionReturns, ContextNode, PropsMap } from '../types';
|
||||
|
||||
/**
|
||||
* This file serves a idiomatic facade of a Storybook decorator.
|
||||
*
|
||||
* Wrapper function get called whenever the Storybook rerender the view. This reflow logic is
|
||||
* framework agnostic; on the other hand, the framework specific bindings are the implementation
|
||||
* details hidden behind the passed `render` function.
|
||||
*
|
||||
* Here, we need a dedicated singleton as a state manager for preview (the addon API, in vanilla)
|
||||
* who is also knowing how to communicate with the Storybook manager (in React) via the Storybook
|
||||
* event system.
|
||||
*
|
||||
* @param {Render} render - framework specific bindings
|
||||
*/
|
||||
export type Render<T> = (...args: [ContextNode[], PropsMap, AnyFunctionReturns<T>]) => T;
|
||||
type CreateAddonDecorator = <T>(render: Render<T>) => (contexts: AddonSetting[]) => T;
|
||||
|
||||
export const createAddonDecorator: CreateAddonDecorator = render => {
|
||||
const wrapper: StoryWrapper = (getStory, context, settings: any) => {
|
||||
const { getContextNodes, getSelectionState, getPropsMap } = addonContextsAPI();
|
||||
const nodes = getContextNodes(settings);
|
||||
const state = getSelectionState();
|
||||
const props = getPropsMap(nodes, state);
|
||||
return render(nodes, props, () => getStory(context));
|
||||
};
|
||||
|
||||
return makeDecorator({
|
||||
name: ID,
|
||||
parameterName: PARAM,
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: false,
|
||||
wrapper,
|
||||
});
|
||||
};
|
@ -12,7 +12,7 @@ describe('Test on functional helpers: memorize', () => {
|
||||
const resultC = someFnMemo(1);
|
||||
|
||||
// assertion
|
||||
expect(someFn).toBeCalledTimes(2);
|
||||
expect(someFn).toHaveBeenCalledTimes(2);
|
||||
expect(resultA).toEqual(someFn(1));
|
||||
expect(resultA).not.toEqual(resultB);
|
||||
expect(resultA).toBe(resultC);
|
||||
@ -30,7 +30,7 @@ describe('Test on functional helpers: memorize', () => {
|
||||
const resultC = someFnMemo(1, 3);
|
||||
|
||||
// assertion
|
||||
expect(someFn).toBeCalledTimes(2);
|
||||
expect(someFn).toHaveBeenCalledTimes(2);
|
||||
expect(resultA).toEqual(someFn(1, 2));
|
||||
expect(resultA).toBe(resultB);
|
||||
expect(resultA).not.toEqual(resultC);
|
||||
@ -49,7 +49,7 @@ describe('Test on functional helpers: singleton', () => {
|
||||
const resultC = someFnSingleton(7, 8, 9);
|
||||
|
||||
// assertion
|
||||
expect(someFn).toBeCalledTimes(1);
|
||||
expect(someFn).toHaveBeenCalledTimes(1);
|
||||
expect(resultA).toEqual(someFn(1, 2, 3));
|
||||
expect(resultA).toBe(resultB);
|
||||
expect(resultA).toBe(resultC);
|
||||
|
@ -3,12 +3,12 @@
|
||||
* the default is to memorize its the first argument;
|
||||
* @return the memorized version of a function.
|
||||
*/
|
||||
type Memorize = <T, U extends any[]>(
|
||||
type memorize = <T, U extends any[]>(
|
||||
fn: (...args: U) => T,
|
||||
resolver?: (...args: U) => unknown
|
||||
) => (...args: U) => T;
|
||||
|
||||
export const memorize: Memorize = (fn, resolver) => {
|
||||
export const memorize: memorize = (fn, resolver) => {
|
||||
const memo = new Map();
|
||||
return (...arg) => {
|
||||
const key = resolver ? resolver(...arg) : arg[0];
|
||||
@ -21,6 +21,6 @@ export const memorize: Memorize = (fn, resolver) => {
|
||||
* the returned value is cached for resolving the subsequent calls.
|
||||
* @return the singleton version of a function.
|
||||
*/
|
||||
type Singleton = <T, U extends any[]>(fn: (...args: U) => T) => (...args: U) => T;
|
||||
type singleton = <T, U extends any[]>(fn: (...args: U) => T) => (...args: U) => T;
|
||||
|
||||
export const singleton: Singleton = fn => memorize(fn, () => 'singleton');
|
||||
export const singleton: singleton = fn => memorize(fn, () => 'singleton');
|
||||
|
@ -1,25 +1,25 @@
|
||||
import { AddonSetting, ContextNode, WrapperSettings } from '../../types';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { AddonSetting, ContextNode, WrapperSettings } from '../../shared/types.d';
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Merge the top-level (global options) and the story-level (parameters) from a pair of setting;
|
||||
* @return the normalized definition for a contextual environment (i.e. a contextNode).
|
||||
*/
|
||||
type GetMergedSettings = (
|
||||
type _getMergedSettings = (
|
||||
topLevel: Partial<AddonSetting>,
|
||||
storyLevel: Partial<AddonSetting>
|
||||
) => ContextNode;
|
||||
|
||||
export const _getMergedSettings: GetMergedSettings = (topLevel, storyLevel) => ({
|
||||
nodeId: topLevel.title || storyLevel.title || '',
|
||||
export const _getMergedSettings: _getMergedSettings = (topLevel, storyLevel) => ({
|
||||
// strip out special characters reserved for serializing
|
||||
nodeId: (topLevel.title || storyLevel.title || '').replace(/[,+]/g, ''),
|
||||
icon: topLevel.icon || storyLevel.icon || '',
|
||||
title: topLevel.title || storyLevel.title || '',
|
||||
components: topLevel.components || storyLevel.components || [],
|
||||
params:
|
||||
topLevel.params || storyLevel.params
|
||||
? Array()
|
||||
.concat(topLevel.params, storyLevel.params)
|
||||
.filter(Boolean)
|
||||
? [...(topLevel.params || []), ...(storyLevel.params || [])].filter(Boolean)
|
||||
: [{ name: '', props: {} }],
|
||||
options: {
|
||||
deep: false,
|
||||
@ -35,11 +35,10 @@ export const _getMergedSettings: GetMergedSettings = (topLevel, storyLevel) => (
|
||||
* Pair up settings for merging normalizations to produce the contextual definitions (i.e. contextNodes);
|
||||
* it guarantee the adding order can be respected but not duplicated.
|
||||
*/
|
||||
type GetContextNodes = (settings: WrapperSettings) => ContextNode[];
|
||||
type getContextNodes = (settings: WrapperSettings) => ContextNode[];
|
||||
|
||||
export const getContextNodes: GetContextNodes = ({ options, parameters }) => {
|
||||
const titles = Array()
|
||||
.concat(options, parameters)
|
||||
export const getContextNodes: getContextNodes = ({ options, parameters }) => {
|
||||
const titles = [...(options || []), ...(parameters || [])]
|
||||
.filter(Boolean)
|
||||
.map(({ title }) => title);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { _getPropsByParamName, getPropsMap } from './getPropsMap';
|
||||
import { OPT_OUT } from '../../constants';
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
|
||||
describe('Test on behaviors from collecting the propsMap', () => {
|
||||
const someParams = [{ name: 'A', props: {} }, { name: 'B', props: {} }];
|
||||
@ -10,7 +10,7 @@ describe('Test on behaviors from collecting the propsMap', () => {
|
||||
});
|
||||
|
||||
it('should return "OPT_OUT" token when the context being opted out', () => {
|
||||
const result = _getPropsByParamName(someParams, OPT_OUT);
|
||||
const result = _getPropsByParamName(someParams, OPT_OUT, { cancelable: true });
|
||||
expect(result).toBe(OPT_OUT);
|
||||
});
|
||||
|
||||
@ -66,7 +66,7 @@ describe('Test on the integrity of the method to get the propMaps', () => {
|
||||
];
|
||||
const someSelectionState = {
|
||||
'Some Context': 'A1',
|
||||
'Another Context': OPT_OUT,
|
||||
'Another Context': OPT_OUT, // an inconsistent but possible state being introduced via query param
|
||||
};
|
||||
|
||||
// exercise
|
||||
@ -75,7 +75,7 @@ describe('Test on the integrity of the method to get the propMaps', () => {
|
||||
// assertion
|
||||
expect(result).toEqual({
|
||||
'Some Context': { a: 1 },
|
||||
'Another Context': OPT_OUT,
|
||||
'Another Context': { b: 1 }, // not equal to `OPT_OUT` due to the context is not cancelable
|
||||
'Other Contexts': { c: 1 },
|
||||
});
|
||||
});
|
||||
|
@ -1,19 +1,21 @@
|
||||
import { OPT_OUT } from '../../constants';
|
||||
import { ContextNode, GenericProp, PropsMap, SelectionState } from '../../types';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
import { ContextNode, GenericProp, PropsMap, SelectionState } from '../../shared/types.d';
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Extract the activated props by name from a given contextual params.
|
||||
*/
|
||||
type GetPropsByParamName = (
|
||||
type _getPropsByParamName = (
|
||||
params: ContextNode['params'],
|
||||
name?: string
|
||||
name?: string,
|
||||
options?: Partial<ContextNode['options']>
|
||||
) => GenericProp | typeof OPT_OUT;
|
||||
|
||||
export const _getPropsByParamName: GetPropsByParamName = (params, name) => {
|
||||
export const _getPropsByParamName: _getPropsByParamName = (params, name = '', options = {}) => {
|
||||
const { props = null } =
|
||||
// when opt-out context
|
||||
(name === OPT_OUT && { props: OPT_OUT }) ||
|
||||
(options.cancelable && name === OPT_OUT && { props: OPT_OUT }) ||
|
||||
// when menu option get selected
|
||||
(name && params.find(param => param.name === name)) ||
|
||||
// when being initialized
|
||||
@ -29,10 +31,11 @@ export const _getPropsByParamName: GetPropsByParamName = (params, name) => {
|
||||
* @nosideeffects
|
||||
* Collect the propsMap from Nodes based on a controlled state tracker.
|
||||
*/
|
||||
type GetPropsMap = (contextNodes: ContextNode[], selectionState: SelectionState) => PropsMap;
|
||||
type getPropsMap = (contextNodes: ContextNode[], selectionState: SelectionState) => PropsMap;
|
||||
|
||||
export const getPropsMap: GetPropsMap = (contextNodes, selectionState) =>
|
||||
contextNodes.reduce((agg, { nodeId, params }) => {
|
||||
agg[nodeId] = _getPropsByParamName(params, selectionState[nodeId]);
|
||||
export const getPropsMap: getPropsMap = (contextNodes, selectionState) =>
|
||||
contextNodes.reduce((agg, { nodeId, params, options }) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
agg[nodeId] = _getPropsByParamName(params, selectionState[nodeId], options);
|
||||
return agg;
|
||||
}, Object());
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { _getAggregatedWrap, getRendererFrom } from './getRendererFrom';
|
||||
import { OPT_OUT } from '../../constants';
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
|
||||
// mocks
|
||||
const h = jest.fn();
|
||||
@ -18,21 +18,21 @@ describe('Test on aggregation of a single context', () => {
|
||||
const testedProps = {};
|
||||
const testedOption = { disable: true };
|
||||
spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)();
|
||||
expect(h).toBeCalledTimes(0);
|
||||
expect(h).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should skip wrapping when props is marked as "OPT_OUT"', () => {
|
||||
const testedProps = OPT_OUT;
|
||||
const testedOption = {};
|
||||
const testedOption = { cancelable: true };
|
||||
spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)();
|
||||
expect(h).toBeCalledTimes(0);
|
||||
expect(h).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should wrap components in the stacking order', () => {
|
||||
const testedProps = {};
|
||||
const testedOption = {};
|
||||
spiedAggregator([fakeTag, fakeComponent], testedProps, testedOption)();
|
||||
expect(h).toBeCalledTimes(2);
|
||||
expect(h).toHaveBeenCalledTimes(2);
|
||||
expect(h.mock.calls[0][0]).toBe(fakeComponent);
|
||||
expect(h.mock.calls[1][0]).toBe(fakeTag);
|
||||
});
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { OPT_OUT } from '../../constants';
|
||||
import { AddonOptions, AnyFunctionReturns, ContextNode, GenericProp, PropsMap } from '../../types';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { OPT_OUT } from '../../shared/constants';
|
||||
import {
|
||||
AddonOptions,
|
||||
AnyFunctionReturns,
|
||||
ContextNode,
|
||||
GenericProp,
|
||||
PropsMap,
|
||||
} from '../../shared/types.d';
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -8,7 +15,7 @@ import { AddonOptions, AnyFunctionReturns, ContextNode, GenericProp, PropsMap }
|
||||
*
|
||||
* @param {function} h - the associated `createElement` vNode creator from the framework
|
||||
*/
|
||||
type GetAggregatedWrap = <T>(
|
||||
type _getAggregatedWrap = <T>(
|
||||
h: AnyFunctionReturns<T>
|
||||
) => (
|
||||
components: ContextNode['components'],
|
||||
@ -16,13 +23,17 @@ type GetAggregatedWrap = <T>(
|
||||
options: AddonOptions
|
||||
) => AnyFunctionReturns<T>;
|
||||
|
||||
export const _getAggregatedWrap: GetAggregatedWrap = h => (components, props, options) => vNode => {
|
||||
export const _getAggregatedWrap: _getAggregatedWrap = h => (
|
||||
components,
|
||||
props,
|
||||
options
|
||||
) => vNode => {
|
||||
const last = components.length - 1;
|
||||
const isSkipped =
|
||||
// when set to disable
|
||||
options.disable ||
|
||||
// when opt-out context
|
||||
props === OPT_OUT;
|
||||
(options.cancelable && props === OPT_OUT);
|
||||
|
||||
return isSkipped
|
||||
? vNode
|
||||
@ -41,11 +52,11 @@ export const _getAggregatedWrap: GetAggregatedWrap = h => (components, props, op
|
||||
*
|
||||
* @param {function} h - the associated `createElement` vNode creator from the framework
|
||||
*/
|
||||
type GetRendererFrom = <T>(
|
||||
type getRendererFrom = <T>(
|
||||
h: AnyFunctionReturns<T>
|
||||
) => (contextNodes: ContextNode[], propsMap: PropsMap, getStoryVNode: AnyFunctionReturns<T>) => T;
|
||||
|
||||
export const getRendererFrom: GetRendererFrom = h => (contextNodes, propsMap, getStoryVNode) =>
|
||||
export const getRendererFrom: getRendererFrom = h => (contextNodes, propsMap, getStoryVNode) =>
|
||||
contextNodes
|
||||
// map over contextual nodes to get the wrapping function
|
||||
.map(({ nodeId, components, options }) =>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { createElement } from 'react';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
import { AddonManager } from './manager/AddonManager';
|
||||
import { ID } from './constants';
|
||||
import { ContextsManager } from './manager/ContextsManager';
|
||||
import { ID } from './shared/constants';
|
||||
|
||||
addons.register(ID, api =>
|
||||
addons.add(ID, {
|
||||
title: ID,
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode }) => viewMode === 'story',
|
||||
render: () => createElement(AddonManager, { channel: api.getChannel() }),
|
||||
render: () => createElement(ContextsManager, { api }),
|
||||
})
|
||||
);
|
||||
|
1
addons/contexts/src/shared/@mock-types/_global.d.ts
vendored
Normal file
1
addons/contexts/src/shared/@mock-types/_global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'global';
|
7
addons/contexts/src/shared/@mock-types/_preact.d.ts
vendored
Normal file
7
addons/contexts/src/shared/@mock-types/_preact.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Preact v8.4.2 shipped with global polluted JSX typing, which breaks the React components typing under Manager
|
||||
*/
|
||||
declare module 'preact' {
|
||||
declare type VNode = any;
|
||||
declare const h: any = () => {};
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
export { FORCE_RE_RENDER, SET_CURRENT_STORY } from '@storybook/core-events';
|
||||
|
||||
// configs
|
||||
export const ID = 'addon-contexts' as const;
|
||||
export const PARAM = 'contexts' as const;
|
20
addons/contexts/src/shared/serializers.test.ts
Normal file
20
addons/contexts/src/shared/serializers.test.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { deserialize, serialize } from './serializers';
|
||||
|
||||
describe('Test on serializers', () => {
|
||||
const someContextsQueryParam = 'CSS Themes=Forests,Languages=Fr';
|
||||
const someSelectionState = {
|
||||
'CSS Themes': 'Forests',
|
||||
Languages: 'Fr',
|
||||
};
|
||||
|
||||
it('Should serialize selection state into its string representation', () => {
|
||||
expect(serialize(null)).toEqual(null);
|
||||
expect(serialize(someSelectionState)).toEqual(someContextsQueryParam);
|
||||
});
|
||||
|
||||
it('Should deserialize a string representation into the represented selection state', () => {
|
||||
expect(deserialize('')).toEqual(null);
|
||||
expect(deserialize('An invalid string=')).toEqual(null);
|
||||
expect(deserialize(someContextsQueryParam)).toEqual(someSelectionState);
|
||||
});
|
||||
});
|
29
addons/contexts/src/shared/serializers.ts
Normal file
29
addons/contexts/src/shared/serializers.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { SelectionState } from './types.d';
|
||||
|
||||
/**
|
||||
* Serialize the selection state in its string representation.
|
||||
*/
|
||||
type serialize = (state: ReturnType<deserialize>) => string | null;
|
||||
|
||||
export const serialize: serialize = state =>
|
||||
!state
|
||||
? null
|
||||
: Object.entries(state)
|
||||
.map(tuple => tuple.join('='))
|
||||
.join(',');
|
||||
|
||||
/**
|
||||
* Deserialize URL query param into the specified selection state.
|
||||
*/
|
||||
type deserialize = (param?: string) => SelectionState | null;
|
||||
|
||||
export const deserialize: deserialize = param =>
|
||||
!param
|
||||
? null
|
||||
: param
|
||||
.split(/,+/g)
|
||||
.map(str => str.split(/=+/g))
|
||||
.reduce<SelectionState | null>(
|
||||
(acc, [nodeId, name]) => (nodeId && name ? { ...acc, [nodeId]: name } : acc),
|
||||
null
|
||||
);
|
@ -1,6 +1,8 @@
|
||||
import { ComponentProps, FunctionComponent } from 'react';
|
||||
import { Icons } from '@storybook/components';
|
||||
|
||||
export { API as ManagerAPI } from '@storybook/api';
|
||||
|
||||
// helpers
|
||||
export declare type AnyFunctionReturns<T> = (...arg: any[]) => T;
|
||||
export declare type FCNoChildren<P> = FunctionComponent<{ children?: never } & P>;
|
||||
@ -20,11 +22,11 @@ export declare interface AddonSetting {
|
||||
icon?: ComponentProps<typeof Icons>['icon'] | '';
|
||||
title: string;
|
||||
components?: unknown[];
|
||||
params?: Array<{
|
||||
params?: {
|
||||
name: string;
|
||||
props: GenericProp;
|
||||
default?: boolean;
|
||||
}>;
|
||||
}[];
|
||||
options?: AddonOptions;
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"preact": ["src/shared/@mock-types/_preact.d.ts"]
|
||||
},
|
||||
"strictNullChecks": true,
|
||||
"removeComments": true,
|
||||
"rootDir": "./src",
|
||||
"types": ["webpack-env", "jest"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/register.ts", "src/__tests__/**/*", "src/**/*.test.ts"]
|
||||
"exclude": ["src/register.ts", "src/**/*.test.ts"]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-cssresources",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "A storybook addon to switch between css resources at runtime for your story",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -25,10 +25,10 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.4"
|
||||
|
@ -4,7 +4,7 @@ import Adapter from 'enzyme-adapter-react-16';
|
||||
import { SyntaxHighlighter } from '@storybook/components';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import { EVENTS, PARAM_KEY } from './constants';
|
||||
import { CssResourcePanel } from './css-resource-panel.tsx';
|
||||
import { CssResourcePanel } from './css-resource-panel';
|
||||
|
||||
configure({
|
||||
adapter: new Adapter(),
|
||||
|
@ -47,6 +47,7 @@ export class CssResourcePanel extends Component<Props, State> {
|
||||
|
||||
if (list && currentStoryId !== id) {
|
||||
const existingIds = currentList.reduce((lookup: CssResourceLookup, res) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
lookup[res.id] = res;
|
||||
return lookup;
|
||||
}, {}) as CssResourceLookup;
|
||||
|
@ -45,7 +45,7 @@ const updateElement = (id: string, code: string, value: boolean) => {
|
||||
|
||||
const list: any[] = [];
|
||||
|
||||
const setResources = (resources: Array<{ code: string; id: string }>) => {
|
||||
const setResources = (resources: { code: string; id: string }[]) => {
|
||||
const added = resources.filter(i => !list.find(r => r.code === i.code));
|
||||
const removed = list.filter(i => !resources.find(r => r.code === i.code));
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-events",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Add events to your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -24,9 +24,9 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"format-json": "^1.0.3",
|
||||
"prop-types": "^15.7.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-google-analytics",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Storybook addon for google analytics",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -20,8 +20,8 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"react-ga": "^2.5.7"
|
||||
|
@ -4,7 +4,7 @@ Storybook GraphQL Addon can be used to display the GraphiQL IDE with example que
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-graphql",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Storybook addon to display the GraphiQL IDE",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -22,10 +22,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.28",
|
||||
"@storybook/api": "5.1.0-alpha.28",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"graphiql": "^0.13.0",
|
||||
"graphql": "^14.1.1",
|
||||
"graphql": "^14.2.1",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
1
addons/graphql/register.js
Normal file
1
addons/graphql/register.js
Normal file
@ -0,0 +1 @@
|
||||
require('./dist/register').register();
|
@ -1 +1,4 @@
|
||||
export { setupGraphiQL } from './preview';
|
||||
|
||||
export const ADDON_ID = 'graphiql';
|
||||
export const PARAM_KEY = ADDON_ID;
|
||||
|
54
addons/graphql/src/manager.js
Normal file
54
addons/graphql/src/manager.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { fetch } from 'global';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import GraphiQL from 'graphiql';
|
||||
import 'graphiql/graphiql.css';
|
||||
|
||||
import { Consumer } from '@storybook/api';
|
||||
|
||||
import { PARAM_KEY } from '.';
|
||||
|
||||
const FETCH_OPTIONS = {
|
||||
method: 'post',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
function getDefaultFetcher(url) {
|
||||
return params => {
|
||||
const body = JSON.stringify(params);
|
||||
const options = Object.assign({ body }, FETCH_OPTIONS);
|
||||
return fetch(url, options).then(res => res.json());
|
||||
};
|
||||
}
|
||||
|
||||
function reIndentQuery(query) {
|
||||
const lines = query.split('\n');
|
||||
const spaces = lines[lines.length - 1].length - 1;
|
||||
return lines.map((l, i) => (i === 0 ? l : l.slice(spaces))).join('\n');
|
||||
}
|
||||
|
||||
const GQL = ({ active }) => {
|
||||
return active ? (
|
||||
<Consumer>
|
||||
{({ api, state }) => {
|
||||
const story = state.storiesHash[state.storyId];
|
||||
const parameters = story ? api.getParameters(story.id, PARAM_KEY) : null;
|
||||
|
||||
if (parameters) {
|
||||
const query = reIndentQuery(parameters.query);
|
||||
const variables = parameters.variables || '{}';
|
||||
const url = parameters.url || '';
|
||||
const fetcher = parameters.fetcher || getDefaultFetcher(url);
|
||||
|
||||
return <GraphiQL query={query} variables={variables} fetcher={fetcher} />;
|
||||
}
|
||||
return <div>You have not configured GraphiQL yet</div>;
|
||||
}}
|
||||
</Consumer>
|
||||
) : null;
|
||||
};
|
||||
GQL.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default GQL;
|
16
addons/graphql/src/register.js
Normal file
16
addons/graphql/src/register.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { addons, types } from '@storybook/addons';
|
||||
|
||||
import GQL from './manager';
|
||||
import { ADDON_ID } from '.';
|
||||
|
||||
export function register() {
|
||||
addons.register(ADDON_ID, () => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: 'GraphiQL',
|
||||
type: types.TAB,
|
||||
route: ({ storyId }) => `/graphql/${storyId}`,
|
||||
match: ({ viewMode }) => viewMode === 'graphql',
|
||||
render: GQL,
|
||||
});
|
||||
});
|
||||
}
|
@ -5,7 +5,7 @@ Useful when you want to display usage or other types of documentation alongside
|
||||
|
||||
[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-info",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "A Storybook addon to show additional information for your stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -22,17 +22,18 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/client-logger": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/client-logger": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"marksy": "^6.1.0",
|
||||
"marksy": "^7.0.0",
|
||||
"nested-object-assign": "^1.0.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.4",
|
||||
"react-addons-create-fragment": "^15.6.2",
|
||||
"react-element-to-jsx-string": "^14.0.2",
|
||||
"react-is": "^16.8.4",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
@ -2654,45 +2654,47 @@ exports[`addon Info should render <Info /> for memoized component 1`] = `
|
||||
}
|
||||
string="seven"
|
||||
>
|
||||
<h1>
|
||||
function func(x) {
|
||||
<div>
|
||||
<h1>
|
||||
function func(x) {
|
||||
return x + 1;
|
||||
}
|
||||
</h1>
|
||||
<h2>
|
||||
[object Object]
|
||||
</h2>
|
||||
<h3>
|
||||
1,2,3
|
||||
</h3>
|
||||
<h4>
|
||||
7
|
||||
</h4>
|
||||
<h5>
|
||||
seven
|
||||
</h5>
|
||||
<h6>
|
||||
true
|
||||
</h6>
|
||||
<p>
|
||||
undefined
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
>
|
||||
test
|
||||
</a>
|
||||
<code>
|
||||
storiesOf
|
||||
</code>
|
||||
<ul>
|
||||
<li>
|
||||
1
|
||||
</li>
|
||||
<li>
|
||||
2
|
||||
</li>
|
||||
</ul>
|
||||
</h1>
|
||||
<h2>
|
||||
[object Object]
|
||||
</h2>
|
||||
<h3>
|
||||
1,2,3
|
||||
</h3>
|
||||
<h4>
|
||||
7
|
||||
</h4>
|
||||
<h5>
|
||||
seven
|
||||
</h5>
|
||||
<h6>
|
||||
true
|
||||
</h6>
|
||||
<p>
|
||||
undefined
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
>
|
||||
test
|
||||
</a>
|
||||
<code>
|
||||
storiesOf
|
||||
</code>
|
||||
<ul>
|
||||
<li>
|
||||
1
|
||||
</li>
|
||||
<li>
|
||||
2
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</TestComponent>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,11 +2,14 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SyntaxHighlighter } from '@storybook/components';
|
||||
|
||||
// XXX: is this a bug? should it be (props) => ?
|
||||
const Code = ({ props }) => <SyntaxHighlighter bordered copyable {...props} />;
|
||||
|
||||
const Code = ({ language, code }) => (
|
||||
<SyntaxHighlighter bordered copyable language={language}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
Code.propTypes = {
|
||||
props: PropTypes.shape({}).isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export { Code };
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import React from 'react';
|
||||
|
||||
import PrettyPropType from './PrettyPropType';
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-jest",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "React storybook addon that show component jest report",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -28,11 +28,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.4",
|
||||
|
@ -1,3 +1,6 @@
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-control-regex */
|
||||
/* tslint:disable:object-literal-sort-keys */
|
||||
|
||||
import React from 'react';
|
||||
|
@ -42,6 +42,7 @@ export const FailedResult = styled(({ fullName, title, status, failureMessages,
|
||||
</Indicator>
|
||||
</Head>
|
||||
{failureMessages.map((msg: string, i: number) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<Message msg={msg} key={i} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { STORY_CHANGED } from '@storybook/core-events';
|
||||
import { ADD_TESTS } from '../shared';
|
||||
import { API } from '@storybook/api';
|
||||
import { ADD_TESTS } from '../shared';
|
||||
|
||||
// TODO: import type from @types/jest
|
||||
interface AssertionResult {
|
||||
@ -40,9 +40,6 @@ const provideTests = (Component: React.ComponentType<InjectedProps>) =>
|
||||
active: false,
|
||||
};
|
||||
|
||||
mounted: boolean;
|
||||
stopListeningOnStory: () => void;
|
||||
|
||||
state: HocState = {};
|
||||
|
||||
componentDidMount() {
|
||||
@ -71,6 +68,10 @@ const provideTests = (Component: React.ComponentType<InjectedProps>) =>
|
||||
this.setState({ kind, storyName, tests });
|
||||
};
|
||||
|
||||
stopListeningOnStory: () => void;
|
||||
|
||||
mounted: boolean;
|
||||
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
const { tests } = this.state;
|
||||
|
@ -9,7 +9,7 @@ interface AddonParameters {
|
||||
|
||||
const findTestResults = (
|
||||
testFiles: string[],
|
||||
jestTestResults: { testResults: Array<{ name: string }> },
|
||||
jestTestResults: { testResults: { name: string }[] },
|
||||
jestTestFilesExt: string
|
||||
) =>
|
||||
Object.values(testFiles).map(name => {
|
||||
@ -35,7 +35,7 @@ interface EmitAddTestsArg {
|
||||
story: () => void;
|
||||
testFiles: string[];
|
||||
options: {
|
||||
results: { testResults: Array<{ name: string }> };
|
||||
results: { testResults: { name: string }[] };
|
||||
filesExt: string;
|
||||
};
|
||||
}
|
||||
@ -56,7 +56,6 @@ export const withTests = (userOptions: { results: any; filesExt?: string }) => {
|
||||
|
||||
return (...args: [(string | (() => void)), { kind: string; parameters: AddonParameters }]) => {
|
||||
if (typeof args[0] === 'string') {
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
return deprecate((storyFn: () => void, { kind }: { kind: string }) => {
|
||||
emitAddTests({ kind, story: storyFn, testFiles: args as string[], options });
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-knobs",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Storybook Addon Prop Editor Component",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -22,11 +22,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/client-api": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/client-api": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"core-js": "^2.6.5",
|
||||
"escape-html": "^1.0.3",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { shallow } from 'enzyme'; // eslint-disable-line
|
||||
import { shallow } from 'enzyme';
|
||||
import KnobManager from './KnobManager';
|
||||
|
||||
jest.mock('global', () => ({
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-links",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Story Links addon for storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -22,9 +22,9 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/router": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/router": "5.1.0-alpha.37",
|
||||
"common-tags": "^1.8.0",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
|
@ -4,7 +4,7 @@ import addons from '@storybook/addons';
|
||||
|
||||
import { SELECT_STORY } from '@storybook/core-events';
|
||||
import { mockChannel } from '../../preview.test';
|
||||
import LinkTo from './link.tsx';
|
||||
import LinkTo from './link';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
import LinkTo from './components/link';
|
||||
|
||||
export default LinkTo;
|
||||
|
@ -46,13 +46,16 @@ import { storiesOf } from '@storybook/vue';
|
||||
|
||||
import MyButton from './MyButton.vue';
|
||||
|
||||
storiesOf('MyButton', module)
|
||||
.add('with some emoji', () => ({
|
||||
storiesOf('MyButton', module).add(
|
||||
'with some emoji',
|
||||
() => ({
|
||||
components: { MyButton },
|
||||
template: '<my-button>😀 😎 👍 💯</my-button>'
|
||||
}), {
|
||||
template: '<my-button>😀 😎 👍 💯</my-button>',
|
||||
}),
|
||||
{
|
||||
notes: 'A very simple example of addon notes',
|
||||
});
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Using Markdown
|
||||
@ -62,9 +65,11 @@ Using Markdown in your notes is supported, Storybook will load Markdown as raw b
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import Component from './Component';
|
||||
import notes from './someMarkdownText.md';
|
||||
import markdownNotes from './someMarkdownText.md';
|
||||
|
||||
storiesOf('Component', module).add('With Markdown', () => <Component />, { notes });
|
||||
storiesOf('Component', module).add('With Markdown', () => <Component />, {
|
||||
notes: { markdown: markdownNotes },
|
||||
});
|
||||
```
|
||||
|
||||
## Giphy
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-notes",
|
||||
"version": "5.1.0-alpha.33",
|
||||
"version": "5.1.0-alpha.37",
|
||||
"description": "Write notes for your Storybook stories.",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -23,13 +23,15 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.1.0-alpha.33",
|
||||
"@storybook/api": "5.1.0-alpha.33",
|
||||
"@storybook/client-logger": "5.1.0-alpha.33",
|
||||
"@storybook/components": "5.1.0-alpha.33",
|
||||
"@storybook/core-events": "5.1.0-alpha.33",
|
||||
"@storybook/theming": "5.1.0-alpha.33",
|
||||
"@storybook/addons": "5.1.0-alpha.37",
|
||||
"@storybook/api": "5.1.0-alpha.37",
|
||||
"@storybook/client-logger": "5.1.0-alpha.37",
|
||||
"@storybook/components": "5.1.0-alpha.37",
|
||||
"@storybook/core-events": "5.1.0-alpha.37",
|
||||
"@storybook/router": "5.1.0-alpha.37",
|
||||
"@storybook/theming": "5.1.0-alpha.37",
|
||||
"core-js": "^2.6.5",
|
||||
"global": "^4.3.2",
|
||||
"markdown-to-jsx": "^6.9.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { Link } from '@reach/router';
|
||||
import { SyntaxHighlighter as SyntaxHighlighterBase } from '@storybook/components';
|
||||
import { SyntaxHighlighter } from './Panel.tsx';
|
||||
import { SyntaxHighlighter, NotesLink } from './Panel';
|
||||
|
||||
describe('NotesPanel', () => {
|
||||
describe('SyntaxHighlighter component', () => {
|
||||
@ -20,4 +21,25 @@ describe('NotesPanel', () => {
|
||||
expect(syntaxHighlighterBase.prop('language')).toBe('jsx');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NotesLink component', () => {
|
||||
it('should render storybook links with @storybook/router Link', () => {
|
||||
const component = mount(
|
||||
<NotesLink href="/story/addon-notes" title="title">
|
||||
Storybook Link
|
||||
</NotesLink>
|
||||
);
|
||||
expect(component.find(Link).prop('to')).toBe('/?path=/story/addon-notes');
|
||||
expect(component.find(Link).prop('title')).toBe('title');
|
||||
});
|
||||
it('should render absolute links as <a>', () => {
|
||||
const component = mount(
|
||||
<NotesLink href="https://example.com" title="title">
|
||||
Storybook Link
|
||||
</NotesLink>
|
||||
);
|
||||
expect(component.find('a').prop('href')).toBe('https://example.com');
|
||||
expect(component.find('a').prop('title')).toBe('title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
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