mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 17:01:07 +08:00
Merge branch 'next' into patch-3
This commit is contained in:
commit
83d5414267
@ -92,6 +92,10 @@ module.exports = {
|
||||
plugins: [
|
||||
'emotion',
|
||||
'babel-plugin-macros',
|
||||
'@babel/plugin-transform-arrow-functions',
|
||||
'@babel/plugin-transform-shorthand-properties',
|
||||
'@babel/plugin-transform-block-scoping',
|
||||
'@babel/plugin-transform-destructuring',
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
|
@ -4,7 +4,7 @@ aliases:
|
||||
- &defaults
|
||||
working_directory: /tmp/storybook
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
- image: circleci/node:10-browsers
|
||||
|
||||
jobs:
|
||||
install:
|
||||
@ -14,7 +14,7 @@ jobs:
|
||||
- restore_cache:
|
||||
name: Restore core dependencies cache
|
||||
keys:
|
||||
- core-dependencies-v4-{{ checksum "yarn.lock" }}
|
||||
- core-dependencies-v5-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn install
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
command: yarn repo-dirty-check
|
||||
- save_cache:
|
||||
name: Cache core dependencies
|
||||
key: core-dependencies-v4-{{ checksum "yarn.lock" }}
|
||||
key: core-dependencies-v5-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache
|
||||
- node_modules
|
||||
@ -83,15 +83,6 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Install Headless Chrome dependencies
|
||||
command: |
|
||||
sudo apt-get install -yq \
|
||||
gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
|
||||
libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
|
||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
- run:
|
||||
name: examples
|
||||
command: |
|
||||
@ -115,7 +106,7 @@ jobs:
|
||||
command: yarn cypress install
|
||||
- save_cache:
|
||||
name: Cache core dependencies
|
||||
key: core-dependencies-v4-{{ checksum "yarn.lock" }}
|
||||
key: core-dependencies-v5-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache
|
||||
- node_modules
|
||||
@ -204,7 +195,7 @@ jobs:
|
||||
- restore_cache:
|
||||
name: Restore core dependencies cache
|
||||
keys:
|
||||
- core-dependencies-v4-{{ checksum "yarn.lock" }}
|
||||
- core-dependencies-v5-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: yarn bootstrap --install
|
||||
@ -218,7 +209,7 @@ jobs:
|
||||
- restore_cache:
|
||||
name: Restore docs dependencies cache
|
||||
keys:
|
||||
- docs-dependencies-v2-{{ checksum "docs/yarn.lock" }}
|
||||
- docs-dependencies-v3-{{ checksum "docs/yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
@ -231,7 +222,7 @@ jobs:
|
||||
yarn build
|
||||
- save_cache:
|
||||
name: Cache docs dependencies
|
||||
key: docs-dependencies-v2-{{ checksum "docs/yarn.lock" }}
|
||||
key: docs-dependencies-v3-{{ checksum "docs/yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache
|
||||
lint:
|
||||
|
@ -17,6 +17,14 @@ module.exports = {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/__testfixtures__/**'],
|
||||
rules: {
|
||||
'react/forbid-prop-types': 'off',
|
||||
'react/no-unused-prop-types': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
},
|
||||
},
|
||||
{ files: '**/.storybook/config.js', rules: { 'global-require': 'off' } },
|
||||
{
|
||||
files: ['**/*.stories.*'],
|
||||
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -4,7 +4,6 @@
|
||||
/addons/a11y/ @jbovenschen @codebyalex
|
||||
/addons/actions/ @rhalff
|
||||
/addons/backgrounds/ @ndelangen
|
||||
/addons/centered/ @kazupon
|
||||
/addons/events/ @z4o4z @ndelangen
|
||||
/addons/graphql/ @mnmtanish
|
||||
/addons/info/ @theinterned @z4o4z @UsulPro @dangreenisrael
|
||||
|
1
.github/autolabeler.yml
vendored
1
.github/autolabeler.yml
vendored
@ -1,7 +1,6 @@
|
||||
'addon: a11y': ["addons/a11y/**"]
|
||||
'addon: actions': ["addons/actions/**"]
|
||||
'addon: backgrounds': ["addons/backgrounds/**"]
|
||||
'addon: centered': ["addons/centered/**"]
|
||||
'addon: events ': ["addons/events/**"]
|
||||
'addon: graphql ': ["addons/graphql/**"]
|
||||
'addon: info': ["addons/info/**"]
|
||||
|
14
.github/workflows/tests-cli.yml
vendored
14
.github/workflows/tests-cli.yml
vendored
@ -22,11 +22,10 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
|
||||
key: build-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
build-v2-${{ env.cache-name }}-
|
||||
build-v2-
|
||||
- name: install, bootstrap
|
||||
run: |
|
||||
yarn bootstrap --core
|
||||
@ -45,11 +44,10 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
|
||||
key: build-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
build-v2-${{ env.cache-name }}-
|
||||
build-v2-
|
||||
- name: install, bootstrap
|
||||
run: |
|
||||
yarn bootstrap --core
|
||||
|
7
.github/workflows/tests-unit.yml
vendored
7
.github/workflows/tests-unit.yml
vendored
@ -15,11 +15,10 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }}
|
||||
key: build-v2-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
${{ runner.OS }}-build-
|
||||
${{ runner.OS }}-
|
||||
build-v2-${{ env.cache-name }}-
|
||||
build-v2-
|
||||
- name: install, bootstrap
|
||||
run: |
|
||||
yarn bootstrap --core
|
||||
|
4
.teamcity/settings.kts
vendored
4
.teamcity/settings.kts
vendored
@ -177,14 +177,14 @@ object ExamplesTemplate : Template({
|
||||
scriptContent = """
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
|
||||
|
||||
yarn install
|
||||
rm -rf built-storybooks
|
||||
mkdir -p built-storybooks
|
||||
|
||||
yarn build-storybooks
|
||||
""".trimIndent()
|
||||
dockerImage = "node:10"
|
||||
dockerImage = "buildkite/puppeteer"
|
||||
dockerImagePlatform = ScriptBuildStep.ImagePlatform.Linux
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
| [a11y](addons/a11y) | + | | + | + | + | + | + | + | + | + | + | + |
|
||||
| [actions](addons/actions) | + | +\* | + | + | + | + | + | + | + | + | + | + |
|
||||
| [backgrounds](addons/backgrounds) | + | \* | + | + | + | + | + | + | + | + | + | + |
|
||||
| [centered](addons/centered) | + | | + | + | + | + | | + | | + | + | + |
|
||||
| [contexts](addons/contexts) | + | | + | | | | | | | | + | + |
|
||||
| [cssresources](addons/cssresources) | + | | + | + | + | + | + | + | + | + | + | + |
|
||||
| [design assets](addons/design-assets) | + | | + | + | + | + | + | + | + | + | + | + |
|
||||
|
85
CHANGELOG.md
85
CHANGELOG.md
@ -1,3 +1,86 @@
|
||||
## 6.0.0-alpha.33 (April 14, 2020)
|
||||
|
||||
### Breaking prerelease
|
||||
|
||||
- Core: Rename ParameterEnhancer to ArgsEnhancer ([#10398](https://github.com/storybookjs/storybook/pull/10398))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix `webpackFinal` being called twice ([#10402](https://github.com/storybookjs/storybook/pull/10402))
|
||||
- Core: Fix legacy redirect ([#10404](https://github.com/storybookjs/storybook/pull/10404))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CLI: Update fixtures used for CLI tests ([#10396](https://github.com/storybookjs/storybook/pull/10396))
|
||||
- Build: Update bootstrap to install optional deps on CI ([#10408](https://github.com/storybookjs/storybook/pull/10408))
|
||||
- Addon-docs: Format source at render time ([#10383](https://github.com/storybookjs/storybook/pull/10383))
|
||||
|
||||
## 6.0.0-alpha.32 (April 11, 2020)
|
||||
|
||||
### Features
|
||||
|
||||
- CSF: Warn when there are no exported stories ([#10357](https://github.com/storybookjs/storybook/pull/10357))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Marko: Always destroy old component when switching stories ([#10345](https://github.com/storybookjs/storybook/pull/10345))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Dev: Build script for package development ([#10343](https://github.com/storybookjs/storybook/pull/10343))
|
||||
|
||||
## 6.0.0-alpha.31 (April 7, 2020)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix ie11 compatibility ([#10281](https://github.com/storybookjs/storybook/pull/10281))
|
||||
- Core: Add .cjs & .mjs to interpret-files ([#10288](https://github.com/storybookjs/storybook/pull/10288))
|
||||
- Core: Fix source-map strategy for production ([#10290](https://github.com/storybookjs/storybook/pull/10290))
|
||||
- Addon-knobs: Allow `text` and `number` to take undefined values ([#10101](https://github.com/storybookjs/storybook/pull/10101))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Core: Warn about deprecated config files ([#10097](https://github.com/storybookjs/storybook/pull/10097))
|
||||
- Yarn 2: rework imports in webpack preview virtual module to fix compatibility ([#10305](https://github.com/storybookjs/storybook/pull/10305))
|
||||
- Addon-centered: Move to deprecated-addons ([#10300](https://github.com/storybookjs/storybook/pull/10300))
|
||||
|
||||
## 5.3.18 (March 31, 2020)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix manager assets to be routed in express ([#9646](https://github.com/storybookjs/storybook/pull/9646))
|
||||
- Storyshots: Fix MDX transform ([#10223](https://github.com/storybookjs/storybook/pull/10223))
|
||||
- Addon-docs: Restore IE11 compat on Windows by transpiling acorn-jsx ([#9790](https://github.com/storybookjs/storybook/pull/9790))
|
||||
- Addon-docs: Ensure visibility of links within prop descriptions ([#10210](https://github.com/storybookjs/storybook/pull/10210))
|
||||
|
||||
## 6.0.0-alpha.30 (March 31, 2020)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Misc: remove deprecations for 6.0.0 ([#10216](https://github.com/storybookjs/storybook/pull/10216))
|
||||
- DocsPage: Remove slots for 6.0 ([#10259](https://github.com/storybookjs/storybook/pull/10259))
|
||||
- Addon-actions: Add preset and configure with parameters ([#9933](https://github.com/storybookjs/storybook/pull/9933))
|
||||
|
||||
### Features
|
||||
|
||||
- MDX: Add args/argTypes/component/subcomponents support ([#10258](https://github.com/storybookjs/storybook/pull/10258))
|
||||
- Addon-docs: Add linear gradient support to ColorPalette block ([#10237](https://github.com/storybookjs/storybook/pull/10237))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-a11y: Performance fix ([#10219](https://github.com/storybookjs/storybook/pull/10219))
|
||||
- API: Fix local addon handling ([#10254](https://github.com/storybookjs/storybook/pull/10254))
|
||||
- Core: Fix URL load failure due to missing base ([#10228](https://github.com/storybookjs/storybook/pull/10228))
|
||||
- Storyshots: Fix MDX transform ([#10223](https://github.com/storybookjs/storybook/pull/10223))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Build: Add puppeteer libs so teamcity can build examples ([#10235](https://github.com/storybookjs/storybook/pull/10235))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Misc upgrades ([#10236](https://github.com/storybookjs/storybook/pull/10236))
|
||||
|
||||
## 6.0.0-alpha.29 (March 26, 2020)
|
||||
|
||||
### Features
|
||||
@ -1916,7 +1999,7 @@ Publish failed
|
||||
CSF users: This is potentially a breaking change. If you want to opt-out of the new default display name calculation (`lodash.startCase`) you can add the following to your SB config:
|
||||
|
||||
```js
|
||||
addParameters({ options: { makeDisplayName: key => key } });
|
||||
addParameters({ options: { makeDisplayName: (key) => key } });
|
||||
```
|
||||
|
||||
### Features
|
||||
|
@ -295,6 +295,17 @@ _This method is slow_
|
||||
2. Take a break 🍵
|
||||
3. `yarn test` (to verify everything worked)
|
||||
|
||||
#### Building specific packages
|
||||
|
||||
If you're working on one or a few packages, for every change that you make, you have to rebuild those packages. To make the process easier, there is a CLI command for that:
|
||||
|
||||
- Run `yarn build` to bring you a list of packages to select from. There will be also an option to run in watch mode.
|
||||
- Run `yarn build <package-name>` to build that package specifically. \
|
||||
For the package name, use its short version. Example: for `@storybook/addon-docs`, run `yarn build addon-docs`.
|
||||
- Run `yarn build --all` to build everything.
|
||||
- Add `--watch` to run automatically in watch more if you are either building a selection of packages by name or building all.
|
||||
Example: `yarn build core addon-docs --watch` or `yarn build --all --watch`.
|
||||
|
||||
### Working with the kitchen sink apps
|
||||
|
||||
Within the `examples` folder of the Storybook repo, you will find kitchen sink examples of storybook implementations for the various platforms that storybook supports.
|
||||
|
116
MIGRATION.md
116
MIGRATION.md
@ -1,6 +1,7 @@
|
||||
<h1>Migration</h1>
|
||||
|
||||
- [From version 5.3.x to 6.0.x](#from-version-53x-to-60x)
|
||||
- [DocsPage slots removed](#docspage-slots-removed)
|
||||
- [React prop tables with Typescript](#react-prop-tables-with-typescript)
|
||||
- [React.FC interfaces](#reactfc-interfaces)
|
||||
- [Imported types](#imported-types)
|
||||
@ -14,6 +15,11 @@
|
||||
- [Simplified Render Context](#simplified-render-context)
|
||||
- [Story Store immutable outside of configuration](#story-store-immutable-outside-of-configuration)
|
||||
- [Improved story source handling](#improved-story-source-handling)
|
||||
- [Actions Addon API changes](#actions-addon-api-changes)
|
||||
- [Actions Addon uses parameters](#actions-addon-uses-parameters)
|
||||
- [Removed action decorator APIs](#removed-action-decorator-apis)
|
||||
- [Removed addon centered](#removed-addon-centered)
|
||||
- [Removed withA11y decorator](#removed-witha11y-decorator)
|
||||
- [From version 5.2.x to 5.3.x](#from-version-52x-to-53x)
|
||||
- [To main.js configuration](#to-mainjs-configuration)
|
||||
- [Using main.js](#using-mainjs)
|
||||
@ -95,6 +101,25 @@
|
||||
|
||||
## From version 5.3.x to 6.0.x
|
||||
|
||||
### DocsPage slots removed
|
||||
|
||||
In SB5.2, we introduced the concept of [DocsPage slots](https://github.com/storybookjs/storybook/blob/0de8575eab73bfd5c5c7ba5fe33e53a49b92db3a/addons/docs/docs/docspage.md#docspage-slots) for customizing the DocsPage.
|
||||
|
||||
In 5.3, we introduced `docs.x` story parameters like `docs.prepareForInline` which get filled in by frameworks and can also be overwritten by users, which is a more natural/convenient way to make global customizations.
|
||||
|
||||
We also introduced introduced [Custom DocsPage](https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/docspage.md#replacing-docspage), which makes it possible to add/remove/update DocBlocks on the page.
|
||||
|
||||
These mechanisms are superior to slots, so we've removed slots in 6.0. For each slot, we provide a migration path here:
|
||||
|
||||
| Slot | Slot function | Replacement |
|
||||
| ----------- | ----------------- | -------------------------------------------- |
|
||||
| Title | `titleSlot` | Custom DocsPage |
|
||||
| Subtitle | `subtitleSlot` | Custom DocsPage |
|
||||
| Description | `descriptionSlot` | `docs.extractComponentDescription` parameter |
|
||||
| Primary | `primarySlot` | Custom DocsPage |
|
||||
| Props | `propsSlot` | `docs.extractProps` parameter |
|
||||
| Stories | `storiesSlot` | Custom DocsPage |
|
||||
|
||||
### React prop tables with Typescript
|
||||
|
||||
Starting in 6.0 we are changing our recommended setup for extracting prop tables in `addon-docs` for React projects using TypeScript.
|
||||
@ -104,8 +129,9 @@ In earlier versions, we recommended `react-docgen-typescript-loader` (`RDTL`) an
|
||||
As a consequence we've removed `RDTL` from the presets, which is a breaking change. We made this change because `react-docgen` now supports TypeScript natively, and fewer dependencies simplifies things for everybody.
|
||||
|
||||
The Babel-based `react-docgen` version is the default in:
|
||||
- `@storybook/preset-create-react-app` @ `^2.0.0`
|
||||
- `@storybook/preset-typescript` @ `^3.0.0-alpha.1`
|
||||
|
||||
- `@storybook/preset-create-react-app` @ `^2.1.0`
|
||||
- `@storybook/preset-typescript` @ `^3.0.0`
|
||||
|
||||
> NOTE: If you're using `preset-create-react-app` you don't need `preset-typescript`!
|
||||
|
||||
@ -141,7 +167,8 @@ type NewType = SomeType & { foo: string };
|
||||
const MyComponent: FC<NewType> = ...
|
||||
```
|
||||
|
||||
This was also an issue in `RDTL` so it doesn't get worse with `react-docgen`. There's an open PR for this https://github.com/reactjs/react-docgen/pull/352 which you can upvote if it affects you.
|
||||
This isn't an issue with `RDTL` so unfortunately it gets worse with `react-docgen`.
|
||||
There's an open PR for this https://github.com/reactjs/react-docgen/pull/352 which you can upvote if it affects you.
|
||||
|
||||
#### Rolling back
|
||||
|
||||
@ -174,43 +201,43 @@ module.exports = {
|
||||
|
||||
In Storybook 5.3 we introduced a declarative [main.js configuration](#to-mainjs-configuration), which is now the recommended way to configure Storybook. Part of the change is a simplified syntax for registering addons, which in 6.0 automatically registers many addons _using a preset_, which is a slightly different behavior than in earlier versions.
|
||||
|
||||
This breaking change currently applies to: `addon-a11y`, `addon-knobs`, `addon-links`, `addon-queryparams`.
|
||||
This breaking change currently applies to: `addon-a11y`, `addon-actions`, `addon-knobs`, `addon-links`, `addon-queryparams`.
|
||||
|
||||
Consider the following `main.js` config for the accessibility addon, `addon-a11y`:
|
||||
Consider the following `main.js` config for the accessibility addon, `addon-knobs`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
stories: ['../**/*.stories.js'],
|
||||
addons: ['@storybook/addon-a11y'],
|
||||
addons: ['@storybook/addon-knobs'],
|
||||
};
|
||||
```
|
||||
|
||||
In earlier versions of Storybook, this would automatically call `@storybook/addon-a11y/register`, which adds the the a11y panel to the Storybook UI. As a user you would also add a decorator:
|
||||
In earlier versions of Storybook, this would automatically call `@storybook/addon-knobs/register`, which adds the the knobs panel to the Storybook UI. As a user you would also add a decorator:
|
||||
|
||||
```js
|
||||
import { withA11y } from '../index';
|
||||
import { withKnobs } from '../index';
|
||||
|
||||
addDecorator(withA11y);
|
||||
addDecorator(withKnobs);
|
||||
```
|
||||
|
||||
Now in 6.0, `addon-a11y` comes with a preset, `@storybook/addon-a11y/preset`, that does this automatically for you. This change simplifies configuration, since now you don't need to add that decorator.
|
||||
Now in 6.0, `addon-knobs` comes with a preset, `@storybook/addon-knobs/preset`, that does this automatically for you. This change simplifies configuration, since now you don't need to add that decorator.
|
||||
|
||||
If you wish to disable this new behavior, you can modify your `main.js` to force it to use the `register` logic rather than the `preset`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
stories: ['../**/*.stories.js'],
|
||||
addons: ['@storybook/addon-a11y/register'],
|
||||
addons: ['@storybook/addon-knobs/register'],
|
||||
};
|
||||
```
|
||||
|
||||
If you wish to selectively disable `a11y` checks for a subset of stories, you can control this with story parameters:
|
||||
If you wish to selectively disable `knobs` checks for a subset of stories, you can control this with story parameters:
|
||||
|
||||
```js
|
||||
export const MyNonCheckedStory = () => <SomeComponent />;
|
||||
MyNonCheckedStory.story = {
|
||||
parameters: {
|
||||
a11y: { disable: true },
|
||||
knobs: { disable: true },
|
||||
},
|
||||
};
|
||||
```
|
||||
@ -320,6 +347,69 @@ The MDX analog:
|
||||
</Story>
|
||||
```
|
||||
|
||||
### Actions Addon API changes
|
||||
|
||||
#### Actions Addon uses parameters
|
||||
|
||||
Leveraging the new preset `@storybook/addon-actions` uses parameters to pass action options. If you previously had:
|
||||
|
||||
```js
|
||||
import { withactions } from `@storybook/addon-actions`;
|
||||
|
||||
export StoryOne = ...;
|
||||
StoryOne.story = {
|
||||
decorators: [withActions('mouseover', 'click .btn')],
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You should replace it with:
|
||||
|
||||
```js
|
||||
export StoryOne = ...;
|
||||
StoryOne.story = {
|
||||
parameters: { actions: ['mouseover', 'click .btn'] },
|
||||
}
|
||||
```
|
||||
|
||||
#### Removed action decorator APIs
|
||||
|
||||
In 6.0 we removed the actions addon decorate API. Actions handles can be configured globaly, for a collection of stories or per story via parameters. The ability to manipulate the data arguments of an event is only relevant in a few frameworks and is not a common enough usecase to be worth the complexity of supporting.
|
||||
|
||||
#### Removed addon centered
|
||||
|
||||
In 6.0 we removed the centered addon. Centering is now core feature of storybook, that no longer needs an addon.
|
||||
|
||||
Remove the addon-centered decorator and instead add a `layout` parameter:
|
||||
|
||||
```js
|
||||
export const MyStory = () => <div>my story</div>;
|
||||
|
||||
MyStory.story = {
|
||||
parameters: { layout: 'centered' },
|
||||
};
|
||||
```
|
||||
|
||||
Other possible values are: `padded` (default) and `fullscreen`.
|
||||
|
||||
#### Removed withA11y decorator
|
||||
|
||||
In 6.0 we removed the `withA11y` decorator. The code that runs accessibility checks is now directly injected in the preview.
|
||||
|
||||
Remove the addon-a11y decorator.
|
||||
To configure a11y now, you have to specify configuration using `addParameters`.
|
||||
|
||||
```js
|
||||
addParameters({
|
||||
a11y: {
|
||||
element: "#root",
|
||||
config: {},
|
||||
options: {},
|
||||
manual: true,
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## From version 5.2.x to 5.3.x
|
||||
|
||||
### To main.js configuration
|
||||
|
@ -137,7 +137,6 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl
|
||||
| [a11y](addons/a11y/) | Test components for user accessibility in Storybook |
|
||||
| [actions](addons/actions/) | Log actions as users interact with components in the Storybook UI |
|
||||
| [backgrounds](addons/backgrounds/) | Let users choose backgrounds in the Storybook UI |
|
||||
| [centered](addons/centered/) | Center the alignment of your components within the Storybook UI |
|
||||
| [contexts](addons/contexts/) | Interactively inject component contexts for stories in the Storybook UI |
|
||||
| [cssresources](addons/cssresources/) | Dynamically add/remove css resources to the component iframe |
|
||||
| [design assets](addons/design-assets/) | View images, videos, weblinks alongside your story |
|
||||
|
@ -13,8 +13,8 @@ function __setMockFiles(newMockFiles) {
|
||||
// A custom version of `readdirSync` that reads from the special mocked out
|
||||
// file list set via __setMockFiles
|
||||
const readFileSync = (filePath = '') => mockFiles[filePath];
|
||||
const existsSync = filePath => !!mockFiles[filePath];
|
||||
const lstatSync = filePath => ({
|
||||
const existsSync = (filePath) => !!mockFiles[filePath];
|
||||
const lstatSync = (filePath) => ({
|
||||
isFile: () => !!mockFiles[filePath],
|
||||
});
|
||||
|
||||
|
@ -56,11 +56,8 @@ You can override these options [at story level too](https://storybook.js.org/doc
|
||||
import React from 'react';
|
||||
import { storiesOf, addDecorator, addParameters } from '@storybook/react';
|
||||
|
||||
import { withA11y } from '@storybook/addon-a11y';
|
||||
|
||||
export default {
|
||||
title: 'button',
|
||||
decorators: [withA11y],
|
||||
parameters: {
|
||||
a11y: {
|
||||
// optional selector which element to inspect
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"version": "6.0.0-alpha.33",
|
||||
"description": "a11y addon for storybook",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -33,16 +33,18 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"@storybook/api": "6.0.0-alpha.29",
|
||||
"@storybook/client-api": "6.0.0-alpha.29",
|
||||
"@storybook/client-logger": "6.0.0-alpha.29",
|
||||
"@storybook/components": "6.0.0-alpha.29",
|
||||
"@storybook/core-events": "6.0.0-alpha.29",
|
||||
"@storybook/theming": "6.0.0-alpha.29",
|
||||
"axe-core": "^3.3.2",
|
||||
"@storybook/addons": "6.0.0-alpha.33",
|
||||
"@storybook/api": "6.0.0-alpha.33",
|
||||
"@storybook/channels": "6.0.0-alpha.33",
|
||||
"@storybook/client-api": "6.0.0-alpha.33",
|
||||
"@storybook/client-logger": "6.0.0-alpha.33",
|
||||
"@storybook/components": "6.0.0-alpha.33",
|
||||
"@storybook/core-events": "6.0.0-alpha.33",
|
||||
"@storybook/theming": "6.0.0-alpha.33",
|
||||
"axe-core": "^3.5.2",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"lodash": "^4.17.15",
|
||||
"memoizerific": "^1.11.3",
|
||||
"react-redux": "^7.0.2",
|
||||
"react-sizeme": "^2.5.2",
|
||||
|
60
addons/a11y/src/a11yRunner.ts
Normal file
60
addons/a11y/src/a11yRunner.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { document, window } from 'global';
|
||||
import { STORY_RENDERED } from '@storybook/core-events';
|
||||
import axe, { ElementContext, RunOptions, Spec } from 'axe-core';
|
||||
import addons from '@storybook/addons';
|
||||
import { EVENTS } from './constants';
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
||||
|
||||
interface Setup {
|
||||
element?: ElementContext;
|
||||
config: Spec;
|
||||
options: RunOptions;
|
||||
}
|
||||
|
||||
const channel = addons.getChannel();
|
||||
let active = false;
|
||||
|
||||
const getElement = () => {
|
||||
const storyRoot = document.getElementById('story-root');
|
||||
return storyRoot ? storyRoot.children : document.getElementById('root');
|
||||
};
|
||||
|
||||
const run = async (storyId: string) => {
|
||||
try {
|
||||
const input = getParams(storyId);
|
||||
|
||||
if (!active) {
|
||||
active = true;
|
||||
const {
|
||||
element = getElement(),
|
||||
config,
|
||||
options = {
|
||||
restoreScroll: true,
|
||||
},
|
||||
} = input;
|
||||
axe.reset();
|
||||
if (config) {
|
||||
axe.configure(config);
|
||||
}
|
||||
|
||||
const result = await axe.run(element, options);
|
||||
channel.emit(EVENTS.RESULT, result);
|
||||
}
|
||||
} catch (error) {
|
||||
channel.emit(EVENTS.ERROR, error);
|
||||
} finally {
|
||||
active = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getParams = (storyId: string): Setup => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const { parameters } = window.__STORYBOOK_STORY_STORE__._stories[storyId] || {};
|
||||
return parameters.a11y;
|
||||
};
|
||||
|
||||
channel.on(STORY_RENDERED, run);
|
||||
channel.on(EVENTS.REQUEST, run);
|
@ -12,6 +12,8 @@ function createApi() {
|
||||
jest.spyOn(emitter, 'emit');
|
||||
jest.spyOn(emitter, 'on');
|
||||
jest.spyOn(emitter, 'off');
|
||||
|
||||
emitter.getCurrentStoryData = () => ({ id: '1' });
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@ -130,7 +132,7 @@ describe('A11YPanel', () => {
|
||||
|
||||
// then
|
||||
expect(wrapper.text()).toMatch(/Please wait while the accessibility scan is running/);
|
||||
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST);
|
||||
expect(api.emit).toHaveBeenCalledWith(EVENTS.REQUEST, '1');
|
||||
});
|
||||
|
||||
it('should handle "ran" status', () => {
|
||||
@ -143,13 +145,7 @@ describe('A11YPanel', () => {
|
||||
wrapper.update();
|
||||
|
||||
// then
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.last()
|
||||
.text()
|
||||
.trim()
|
||||
).toBe('Tests completed');
|
||||
expect(wrapper.find('button').last().text().trim()).toBe('Tests completed');
|
||||
expect(wrapper.find('Tabs').prop('tabs').length).toBe(3);
|
||||
expect(wrapper.find('Tabs').prop('tabs')[0].label.props.children).toEqual([1, ' Violations']);
|
||||
expect(wrapper.find('Tabs').prop('tabs')[1].label.props.children).toEqual([1, ' Passes']);
|
||||
|
@ -169,7 +169,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
|
||||
status: 'running',
|
||||
},
|
||||
() => {
|
||||
api.emit(EVENTS.REQUEST);
|
||||
api.emit(EVENTS.REQUEST, api.getCurrentStoryData().id);
|
||||
// removes all elements from the redux map in store from the previous panel
|
||||
store.dispatch(clearElements());
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ const getColorList = (active: string | null, set: (i: string | null) => void): L
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...baseList.map(i => ({
|
||||
...baseList.map((i) => ({
|
||||
id: i,
|
||||
title: i.charAt(0).toUpperCase() + i.slice(1),
|
||||
onClick: () => {
|
||||
@ -98,7 +98,7 @@ export const ColorBlindness: FunctionComponent = () => {
|
||||
placement="top"
|
||||
trigger="click"
|
||||
tooltip={({ onHide }) => {
|
||||
const colorList = getColorList(active, i => {
|
||||
const colorList = getColorList(active, (i) => {
|
||||
setActive(i);
|
||||
onHide();
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ function areAllRequiredElementsHighlighted(
|
||||
elementsToHighlight: NodeResult[],
|
||||
highlightedElementsMap: Map<HTMLElement, HighlightedElementData>
|
||||
): CheckBoxStates {
|
||||
const highlightedCount = elementsToHighlight.filter(item => {
|
||||
const highlightedCount = elementsToHighlight.filter((item) => {
|
||||
const targetElement = getElementBySelectorPath(item.target[0]);
|
||||
return (
|
||||
highlightedElementsMap.has(targetElement) &&
|
||||
@ -104,7 +104,7 @@ class HighlightToggle extends Component<ToggleProps> {
|
||||
|
||||
componentDidMount() {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
elementsToHighlight.forEach((element) => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (targetElement && !highlightedElementsMap.has(targetElement)) {
|
||||
this.saveElementDataToMap(targetElement, false, targetElement.style.outline);
|
||||
@ -121,7 +121,7 @@ class HighlightToggle extends Component<ToggleProps> {
|
||||
|
||||
onToggle = (): void => {
|
||||
const { elementsToHighlight, highlightedElementsMap } = this.props;
|
||||
elementsToHighlight.forEach(element => {
|
||||
elementsToHighlight.forEach((element) => {
|
||||
const targetElement = getElementBySelectorPath(element.target[0]);
|
||||
if (!highlightedElementsMap.has(targetElement)) {
|
||||
return;
|
||||
|
@ -48,10 +48,7 @@ interface RuleProps {
|
||||
}
|
||||
|
||||
const formatSeverityText = (severity: string) => {
|
||||
return severity
|
||||
.charAt(0)
|
||||
.toUpperCase()
|
||||
.concat(severity.slice(1));
|
||||
return severity.charAt(0).toUpperCase().concat(severity.slice(1));
|
||||
};
|
||||
|
||||
const Rule: FunctionComponent<RuleProps> = ({ rule }) => {
|
||||
|
@ -23,7 +23,7 @@ interface TagsProps {
|
||||
export const Tags: FunctionComponent<TagsProps> = ({ tags }) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
{tags.map(tag => (
|
||||
{tags.map((tag) => (
|
||||
<Item key={tag}>{tag}</Item>
|
||||
))}
|
||||
</Wrapper>
|
||||
|
@ -13,7 +13,7 @@ export interface ReportProps {
|
||||
export const Report: FunctionComponent<ReportProps> = ({ items, empty, type }) => (
|
||||
<Fragment>
|
||||
{items && items.length ? (
|
||||
items.map(item => <Item 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,74 +1,3 @@
|
||||
import { document } from 'global';
|
||||
import axe, { AxeResults, ElementContext, RunOptions, Spec } from 'axe-core';
|
||||
|
||||
import addons, { makeDecorator } from '@storybook/addons';
|
||||
import { EVENTS, PARAM_KEY } from './constants';
|
||||
|
||||
let progress = Promise.resolve();
|
||||
interface Setup {
|
||||
element?: ElementContext;
|
||||
config: Spec;
|
||||
options: RunOptions;
|
||||
manual: boolean;
|
||||
}
|
||||
|
||||
const setup: Setup = { element: undefined, config: {}, options: {}, manual: false };
|
||||
|
||||
const getElement = () => {
|
||||
const storyRoot = document.getElementById('story-root');
|
||||
|
||||
if (storyRoot) {
|
||||
return storyRoot.children;
|
||||
}
|
||||
return document.getElementById('root');
|
||||
};
|
||||
|
||||
const report = (input: AxeResults) => addons.getChannel().emit(EVENTS.RESULT, input);
|
||||
|
||||
const run = (element: ElementContext, config: Spec, options: RunOptions) => {
|
||||
progress = progress.then(() => {
|
||||
axe.reset();
|
||||
if (config) {
|
||||
axe.configure(config);
|
||||
}
|
||||
return axe
|
||||
.run(
|
||||
element || getElement(),
|
||||
options ||
|
||||
({
|
||||
restoreScroll: true,
|
||||
} as RunOptions) // cast to RunOptions is necessary because axe types are not up to date
|
||||
)
|
||||
.then(report)
|
||||
.catch(error => addons.getChannel().emit(EVENTS.ERROR, String(error)));
|
||||
});
|
||||
};
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
||||
|
||||
let storedDefaultSetup: Setup | null = null;
|
||||
|
||||
export const withA11y = makeDecorator({
|
||||
name: 'withA11Y',
|
||||
parameterName: PARAM_KEY,
|
||||
wrapper: (getStory, context, { parameters }) => {
|
||||
if (parameters) {
|
||||
if (storedDefaultSetup === null) {
|
||||
storedDefaultSetup = { ...setup };
|
||||
}
|
||||
Object.assign(setup, parameters as Partial<Setup>);
|
||||
} else if (storedDefaultSetup !== null) {
|
||||
Object.assign(setup, storedDefaultSetup);
|
||||
storedDefaultSetup = null;
|
||||
}
|
||||
|
||||
addons
|
||||
.getChannel()
|
||||
.on(EVENTS.REQUEST, () => run(setup.element as ElementContext, setup.config, setup.options));
|
||||
addons.getChannel().emit(EVENTS.MANUAL, setup.manual);
|
||||
|
||||
return getStory(context);
|
||||
},
|
||||
});
|
||||
|
7
addons/a11y/src/preset.ts
Normal file
7
addons/a11y/src/preset.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function managerEntries(entry: any[] = []) {
|
||||
return [...entry, require.resolve('./register')];
|
||||
}
|
||||
|
||||
export function config(entry: any[] = []) {
|
||||
return [...entry, require.resolve('./a11yRunner')];
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import { withA11y } from '../index';
|
||||
|
||||
export const decorators = [withA11y];
|
@ -1,15 +0,0 @@
|
||||
type A11yOptions = {
|
||||
addDecorator?: boolean;
|
||||
};
|
||||
|
||||
export function managerEntries(entry: any[] = []) {
|
||||
return [...entry, require.resolve('../register')];
|
||||
}
|
||||
|
||||
export function config(entry: any[] = [], { addDecorator = true }: A11yOptions = {}) {
|
||||
const a11yConfig = [];
|
||||
if (addDecorator) {
|
||||
a11yConfig.push(require.resolve('./addDecorator'));
|
||||
}
|
||||
return [...entry, ...a11yConfig];
|
||||
}
|
@ -14,7 +14,7 @@ const Hidden = styled.div(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const PreviewWrapper: FunctionComponent<{}> = p => (
|
||||
const PreviewWrapper: FunctionComponent<{}> = (p) => (
|
||||
<Fragment>
|
||||
{p.children}
|
||||
<Hidden>
|
||||
@ -82,7 +82,7 @@ const PreviewWrapper: FunctionComponent<{}> = p => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.register(ADDON_ID, (api) => {
|
||||
addons.add(PANEL_ID, {
|
||||
title: '',
|
||||
type: types.TOOL,
|
||||
|
@ -97,26 +97,6 @@ export const first = () => <Button {...eventsFromNames}>Hello World!</Button>;
|
||||
export const second = () => <Button {...eventsFromObject}>Hello World!</Button>;
|
||||
```
|
||||
|
||||
## Action Decorators
|
||||
|
||||
If you wish to process action data before sending them over to the logger, you can do it with action decorators.
|
||||
|
||||
`decorate` takes an array of decorator functions. Each decorator function is passed an array of arguments, and should return a new arguments array to use. `decorate` returns a object with two functions: `action` and `actions`, that act like the above, except they log the modified arguments instead of the original arguments.
|
||||
|
||||
```js
|
||||
import { decorate } from '@storybook/addon-actions';
|
||||
import Button from './button';
|
||||
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
};
|
||||
|
||||
const firstArg = decorate([args => args.slice(0, 1)]);
|
||||
|
||||
export const first = () => <Button onClick={firstArg.action('button-click')}>Hello World!</Button>;
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Arguments which are passed to the action call will have to be serialized while be "transferred" over the channel.
|
||||
@ -153,17 +133,20 @@ action('my-action', {
|
||||
| `clearOnStoryChange` | Boolean | Flag whether to clear the action logger when switching away from the current story. | `true` |
|
||||
| `limit` | Number | Limits the number of items logged in the action logger | `50` |
|
||||
|
||||
## withActions decorator
|
||||
## Declarative Configuration via Parameters
|
||||
|
||||
You can define action handles in a declarative way using `withActions` decorators. It accepts the same arguments as [`actions`](#multiple-actions). Keys have `'<eventName> <selector>'` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`.
|
||||
You can define action handles in a declarative way using parameters. They accepts the same arguments as [`actions`](#multiple-actions)
|
||||
Keys have `'<eventName> <selector>'` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`.
|
||||
|
||||
```js
|
||||
import { withActions } from '@storybook/addon-actions';
|
||||
import Button from './button';
|
||||
|
||||
export default {
|
||||
title: 'Button',
|
||||
decorators: [withActions('mouseover', 'click .btn')],
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ['mouseover', 'click .btn']
|
||||
}
|
||||
};
|
||||
|
||||
export const first = () => <Button className="btn">Hello World!</Button>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"version": "6.0.0-alpha.33",
|
||||
"description": "Action Logger addon for storybook",
|
||||
"keywords": [
|
||||
"storybook"
|
||||
@ -28,20 +28,22 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"@storybook/api": "6.0.0-alpha.29",
|
||||
"@storybook/client-api": "6.0.0-alpha.29",
|
||||
"@storybook/components": "6.0.0-alpha.29",
|
||||
"@storybook/core-events": "6.0.0-alpha.29",
|
||||
"@storybook/theming": "6.0.0-alpha.29",
|
||||
"@storybook/addons": "6.0.0-alpha.33",
|
||||
"@storybook/api": "6.0.0-alpha.33",
|
||||
"@storybook/client-api": "6.0.0-alpha.33",
|
||||
"@storybook/components": "6.0.0-alpha.33",
|
||||
"@storybook/core-events": "6.0.0-alpha.33",
|
||||
"@storybook/theming": "6.0.0-alpha.33",
|
||||
"core-js": "^3.0.1",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"global": "^4.3.2",
|
||||
"polished": "^3.4.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.3",
|
||||
"react-inspector": "^4.0.0",
|
||||
"react-inspector": "^5.0.1",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"ts-dedent": "^1.1.1",
|
||||
"util-deprecate": "^1.0.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1 +1 @@
|
||||
require('./dist/manager').register();
|
||||
require('./dist/register');
|
||||
|
@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
import ActionLogger from './containers/ActionLogger';
|
||||
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
|
||||
|
||||
export function register() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title: 'Actions',
|
||||
render: ({ active, key }) => <ActionLogger key={key} api={api} active={active} />,
|
||||
paramKey: PARAM_KEY,
|
||||
});
|
||||
});
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { StoryContext } from '@storybook/addons';
|
||||
import { inferActionsFromArgTypesRegex, addActionsFromArgTypes } from './addArgs';
|
||||
|
||||
const withDefaultValue = (argTypes) =>
|
||||
Object.keys(argTypes).filter((key) => !!argTypes[key].defaultValue);
|
||||
|
||||
describe('actions parameter enhancers', () => {
|
||||
describe('actions.argTypesRegex parameter', () => {
|
||||
const baseParameters = {
|
||||
@ -10,18 +13,18 @@ describe('actions parameter enhancers', () => {
|
||||
|
||||
it('should add actions that match a pattern', () => {
|
||||
const parameters = baseParameters;
|
||||
const { args } = inferActionsFromArgTypesRegex({ parameters } as StoryContext);
|
||||
expect(Object.keys(args)).toEqual(['onClick', 'onFocus']);
|
||||
const argTypes = inferActionsFromArgTypesRegex({ parameters } as StoryContext);
|
||||
expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onFocus']);
|
||||
});
|
||||
|
||||
it('should prioritize pre-existing args', () => {
|
||||
it('should prioritize pre-existing argTypes', () => {
|
||||
const parameters = {
|
||||
...baseParameters,
|
||||
args: { onClick: 'pre-existing arg' },
|
||||
argTypes: { onClick: { defaultValue: 'pre-existing value' }, onFocus: {} },
|
||||
};
|
||||
const { args } = inferActionsFromArgTypesRegex({ parameters } as StoryContext);
|
||||
expect(Object.keys(args)).toEqual(['onClick', 'onFocus']);
|
||||
expect(args.onClick).toEqual('pre-existing arg');
|
||||
const argTypes = inferActionsFromArgTypesRegex({ parameters } as StoryContext);
|
||||
expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onFocus']);
|
||||
expect(argTypes.onClick.defaultValue).toEqual('pre-existing value');
|
||||
});
|
||||
|
||||
it('should do nothing if actions are disabled', () => {
|
||||
@ -30,7 +33,7 @@ describe('actions parameter enhancers', () => {
|
||||
actions: { ...baseParameters.actions, disable: true },
|
||||
};
|
||||
const result = inferActionsFromArgTypesRegex({ parameters } as StoryContext);
|
||||
expect(result).toBeFalsy();
|
||||
expect(result).toEqual(parameters.argTypes);
|
||||
});
|
||||
});
|
||||
|
||||
@ -41,27 +44,29 @@ describe('actions parameter enhancers', () => {
|
||||
onBlur: { action: 'blurred!' },
|
||||
},
|
||||
};
|
||||
|
||||
it('should add actions based on action.args', () => {
|
||||
const parameters = baseParameters;
|
||||
const { args } = addActionsFromArgTypes({ parameters } as StoryContext);
|
||||
expect(Object.keys(args)).toEqual(['onClick', 'onBlur']);
|
||||
const argTypes = addActionsFromArgTypes({ parameters } as StoryContext);
|
||||
expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onBlur']);
|
||||
});
|
||||
|
||||
it('should prioritize pre-existing args', () => {
|
||||
const parameters = {
|
||||
...baseParameters,
|
||||
args: { onClick: 'pre-existing arg' },
|
||||
argTypes: {
|
||||
onClick: { defaultValue: 'pre-existing value', action: 'onClick' },
|
||||
onBlur: { action: 'onBlur' },
|
||||
},
|
||||
};
|
||||
const { args } = addActionsFromArgTypes({ parameters } as StoryContext);
|
||||
expect(Object.keys(args)).toEqual(['onClick', 'onBlur']);
|
||||
expect(args.onClick).toEqual('pre-existing arg');
|
||||
const argTypes = addActionsFromArgTypes({ parameters } as StoryContext);
|
||||
expect(withDefaultValue(argTypes)).toEqual(['onClick', 'onBlur']);
|
||||
expect(argTypes.onClick.defaultValue).toEqual('pre-existing value');
|
||||
});
|
||||
|
||||
it('should do nothing if actions are disabled', () => {
|
||||
const parameters = { ...baseParameters, actions: { disable: true } };
|
||||
const result = addActionsFromArgTypes({ parameters } as StoryContext);
|
||||
expect(result).toBeFalsy();
|
||||
expect(result).toEqual(parameters.argTypes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ParameterEnhancer, combineParameters } from '@storybook/client-api';
|
||||
import { Args, ArgType } from '@storybook/addons';
|
||||
import { ArgTypesEnhancer, combineParameters } from '@storybook/client-api';
|
||||
import { ArgTypes, ArgType } from '@storybook/addons';
|
||||
|
||||
import { action } from '../index';
|
||||
|
||||
@ -12,46 +12,42 @@ import { action } from '../index';
|
||||
* Automatically add action args for argTypes whose name
|
||||
* matches a regex, such as `^on.*` for react-style `onClick` etc.
|
||||
*/
|
||||
export const inferActionsFromArgTypesRegex: ParameterEnhancer = context => {
|
||||
const { args, actions, argTypes } = context.parameters;
|
||||
export const inferActionsFromArgTypesRegex: ArgTypesEnhancer = (context) => {
|
||||
const { actions, argTypes } = context.parameters;
|
||||
if (!actions || actions.disable || !actions.argTypesRegex || !argTypes) {
|
||||
return null;
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
const argTypesRegex = new RegExp(actions.argTypesRegex);
|
||||
const actionArgs = Object.keys(argTypes).reduce((acc, name) => {
|
||||
const actionArgTypes = Object.keys(argTypes).reduce((acc, name) => {
|
||||
if (argTypesRegex.test(name)) {
|
||||
acc[name] = action(name);
|
||||
acc[name] = { defaultValue: action(name) };
|
||||
}
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
}, {} as ArgTypes);
|
||||
|
||||
return {
|
||||
args: combineParameters(actionArgs, args),
|
||||
};
|
||||
return combineParameters(actionArgTypes, argTypes) as ArgTypes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add action args for list of strings.
|
||||
*/
|
||||
export const addActionsFromArgTypes: ParameterEnhancer = context => {
|
||||
const { args, argTypes, actions } = context.parameters;
|
||||
export const addActionsFromArgTypes: ArgTypesEnhancer = (context) => {
|
||||
const { argTypes, actions } = context.parameters;
|
||||
if (actions?.disable || !argTypes) {
|
||||
return null;
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
const actionArgs = Object.keys(argTypes).reduce((acc, argName) => {
|
||||
const actionArgTypes = Object.keys(argTypes).reduce((acc, argName) => {
|
||||
const argType: ArgType = argTypes[argName];
|
||||
if (argType.action) {
|
||||
const message = typeof argType.action === 'string' ? argType.action : argName;
|
||||
acc[argName] = action(message);
|
||||
acc[argName] = { defaultValue: action(message) };
|
||||
}
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
}, {} as ArgTypes);
|
||||
|
||||
return {
|
||||
args: combineParameters(actionArgs, args),
|
||||
};
|
||||
return combineParameters(actionArgTypes, argTypes) as ArgTypes;
|
||||
};
|
||||
|
||||
export const parameterEnhancers = [addActionsFromArgTypes, inferActionsFromArgTypesRegex];
|
||||
export const argTypesEnhancers = [addActionsFromArgTypes, inferActionsFromArgTypesRegex];
|
||||
|
3
addons/actions/src/preset/addDecorator.ts
Normal file
3
addons/actions/src/preset/addDecorator.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { withActions } from '../index';
|
||||
|
||||
export const decorators = [withActions];
|
@ -1,7 +1,15 @@
|
||||
type ActionsOptions = {
|
||||
addDecorator?: boolean;
|
||||
};
|
||||
|
||||
export function managerEntries(entry: any[] = [], options: any) {
|
||||
return [...entry, require.resolve('../../register')];
|
||||
return [...entry, require.resolve('../register')];
|
||||
}
|
||||
|
||||
export function config(entry: any[] = []) {
|
||||
return [...entry, require.resolve('./addArgs')];
|
||||
export function config(entry: any[] = [], { addDecorator = true }: ActionsOptions = {}) {
|
||||
const actionConfig = [];
|
||||
if (addDecorator) {
|
||||
actionConfig.push(require.resolve('./addDecorator'));
|
||||
}
|
||||
return [...entry, ...actionConfig, require.resolve('./addArgs')];
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ const createChannel = () => {
|
||||
addons.getChannel.mockReturnValue(channel);
|
||||
return channel;
|
||||
};
|
||||
const getChannelData = channel => channel.emit.mock.calls[0][1].data.args;
|
||||
const getChannelData = (channel) => channel.emit.mock.calls[0][1].data.args;
|
||||
|
||||
describe('Action', () => {
|
||||
it('with one argument', () => {
|
||||
|
90
addons/actions/src/preview/__tests__/actions.test.js
Normal file
90
addons/actions/src/preview/__tests__/actions.test.js
Normal file
@ -0,0 +1,90 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { actions } from '../..';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
|
||||
const createChannel = () => {
|
||||
const channel = { emit: jest.fn() };
|
||||
addons.getChannel.mockReturnValue(channel);
|
||||
return channel;
|
||||
};
|
||||
const getChannelData = (channel, callIndex) => channel.emit.mock.calls[callIndex][1].data;
|
||||
const getChannelOptions = (channel, callIndex) => channel.emit.mock.calls[callIndex][1].options;
|
||||
|
||||
describe('Actions', () => {
|
||||
it('with one argument', () => {
|
||||
const channel = createChannel();
|
||||
|
||||
const actionsResult = actions('test-action');
|
||||
|
||||
expect(Object.keys(actionsResult)).toEqual(['test-action']);
|
||||
actionsResult['test-action']('one');
|
||||
|
||||
expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] });
|
||||
});
|
||||
|
||||
it('with multiple arguments', () => {
|
||||
const channel = createChannel();
|
||||
|
||||
const actionsResult = actions('test-action', 'test-action2');
|
||||
|
||||
expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']);
|
||||
|
||||
actionsResult['test-action']('one');
|
||||
actionsResult['test-action2']('two');
|
||||
|
||||
expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] });
|
||||
expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] });
|
||||
});
|
||||
|
||||
it('with multiple arguments + config', () => {
|
||||
const channel = createChannel();
|
||||
|
||||
const actionsResult = actions('test-action', 'test-action2', { some: 'config' });
|
||||
|
||||
expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']);
|
||||
|
||||
actionsResult['test-action']('one');
|
||||
actionsResult['test-action2']('two');
|
||||
|
||||
expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] });
|
||||
expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] });
|
||||
|
||||
expect(getChannelOptions(channel, 0).some).toEqual('config');
|
||||
expect(getChannelOptions(channel, 1).some).toEqual('config');
|
||||
});
|
||||
|
||||
it('with multiple arguments as object', () => {
|
||||
const channel = createChannel();
|
||||
|
||||
const actionsResult = actions({
|
||||
'test-action': 'test action',
|
||||
'test-action2': 'test action two',
|
||||
});
|
||||
|
||||
expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']);
|
||||
|
||||
actionsResult['test-action']('one');
|
||||
actionsResult['test-action2']('two');
|
||||
|
||||
expect(getChannelData(channel, 0)).toEqual({ name: 'test action', args: ['one'] });
|
||||
expect(getChannelData(channel, 1)).toEqual({ name: 'test action two', args: ['two'] });
|
||||
});
|
||||
|
||||
it('with first argument as array of arguments + config', () => {
|
||||
const channel = createChannel();
|
||||
|
||||
const actionsResult = actions(['test-action', 'test-action2', { some: 'config' }]);
|
||||
|
||||
expect(Object.keys(actionsResult)).toEqual(['test-action', 'test-action2']);
|
||||
|
||||
actionsResult['test-action']('one');
|
||||
actionsResult['test-action2']('two');
|
||||
|
||||
expect(getChannelData(channel, 0)).toEqual({ name: 'test-action', args: ['one'] });
|
||||
expect(getChannelData(channel, 1)).toEqual({ name: 'test-action2', args: ['two'] });
|
||||
|
||||
expect(getChannelOptions(channel, 0).some).toEqual('config');
|
||||
expect(getChannelOptions(channel, 1).some).toEqual('config');
|
||||
});
|
||||
});
|
@ -4,9 +4,13 @@ import { config } from './configureActions';
|
||||
|
||||
export const actions: ActionsFunction = (...args: any[]) => {
|
||||
let options: ActionOptions = config;
|
||||
const names = args;
|
||||
let names = args;
|
||||
// args argument can be a single argument as an array
|
||||
if (names.length === 1 && Array.isArray(names[0])) {
|
||||
[names] = names;
|
||||
}
|
||||
// last argument can be options
|
||||
if (names.length !== 1 && typeof args[args.length - 1] !== 'string') {
|
||||
if (names.length !== 1 && typeof names[names.length - 1] !== 'string') {
|
||||
options = {
|
||||
...config,
|
||||
...names.pop(),
|
||||
@ -16,13 +20,13 @@ export const actions: ActionsFunction = (...args: any[]) => {
|
||||
let namesObject = names[0];
|
||||
if (names.length !== 1 || typeof namesObject === 'string') {
|
||||
namesObject = {};
|
||||
names.forEach(name => {
|
||||
names.forEach((name) => {
|
||||
namesObject[name] = name;
|
||||
});
|
||||
}
|
||||
|
||||
const actionsObject: ActionsMap = {};
|
||||
Object.keys(namesObject).forEach(name => {
|
||||
Object.keys(namesObject).forEach((name) => {
|
||||
actionsObject[name] = action(namesObject[name], options);
|
||||
});
|
||||
return actionsObject;
|
||||
|
@ -1,37 +1,36 @@
|
||||
import { action } from './action';
|
||||
import { actions } from './actions';
|
||||
import { createDecorator } from './withActions';
|
||||
import { ActionOptions, DecoratorFunction, HandlerFunction } from '../models';
|
||||
import deprecate from 'util-deprecate';
|
||||
import dedent from 'ts-dedent';
|
||||
|
||||
const applyDecorators = (decorators: DecoratorFunction[], actionCallback: HandlerFunction) => {
|
||||
return (..._args: any[]) => {
|
||||
const decorated = decorators.reduce((args, storyFn) => storyFn(args), _args);
|
||||
actionCallback(...decorated);
|
||||
};
|
||||
import { DecoratorFunction } from '../models';
|
||||
|
||||
export const decorateAction = (_decorators: DecoratorFunction[]) => {
|
||||
return deprecate(
|
||||
() => {},
|
||||
dedent`
|
||||
decorateAction is no longer supported as of Storybook 6.0.
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
export const decorateAction = (
|
||||
decorators: DecoratorFunction[]
|
||||
): ((name: string, options?: ActionOptions) => HandlerFunction) => {
|
||||
return (name: string, options?: ActionOptions) => {
|
||||
const callAction = action(name, options);
|
||||
return applyDecorators(decorators, callAction);
|
||||
};
|
||||
};
|
||||
|
||||
export const decorate = (decorators: DecoratorFunction[]) => {
|
||||
const decorated = decorateAction(decorators);
|
||||
const decoratedActions = (...args: any[]) => {
|
||||
const rawActions = actions(...args);
|
||||
const actionsObject = {} as any;
|
||||
Object.keys(rawActions).forEach(name => {
|
||||
actionsObject[name] = applyDecorators(decorators, rawActions[name]);
|
||||
});
|
||||
return actionsObject;
|
||||
};
|
||||
return {
|
||||
action: decorated,
|
||||
actions: decoratedActions,
|
||||
withActions: createDecorator(decoratedActions),
|
||||
};
|
||||
export const decorate = (_decorators: DecoratorFunction[]) => {
|
||||
return deprecate(
|
||||
() => {
|
||||
return {
|
||||
action: deprecate(() => {}, 'decorate.action is no longer supported as of Storybook 6.0.'),
|
||||
actions: deprecate(() => {},
|
||||
'decorate.actions is no longer supported as of Storybook 6.0.'),
|
||||
withActions: deprecate(() => {},
|
||||
'decorate.withActions is no longer supported as of Storybook 6.0.'),
|
||||
};
|
||||
},
|
||||
dedent`
|
||||
decorate is deprecated, please configure addon-actions using the addParameter api:
|
||||
|
||||
addParameters({
|
||||
actions: {
|
||||
handles: options
|
||||
},
|
||||
});
|
||||
`
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,14 @@
|
||||
// Based on http://backbonejs.org/docs/backbone.html#section-164
|
||||
import { document, Element } from 'global';
|
||||
import { useEffect } from '@storybook/client-api';
|
||||
import deprecate from 'util-deprecate';
|
||||
import dedent from 'ts-dedent';
|
||||
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import { actions } from './actions';
|
||||
|
||||
import { PARAM_KEY } from '../constants';
|
||||
|
||||
const delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
||||
|
||||
const isIE = Element != null && !Element.prototype.matches;
|
||||
@ -22,8 +27,8 @@ const hasMatchInAncestry = (element: any, selector: any): boolean => {
|
||||
return hasMatchInAncestry(parent, selector);
|
||||
};
|
||||
|
||||
const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) => {
|
||||
const actionsObject = actionsFn(...args);
|
||||
const createHandlers = (actionsFn: (...arg: any[]) => object, ...handles: any[]) => {
|
||||
const actionsObject = actionsFn(...handles);
|
||||
return Object.entries(actionsObject).map(([key, action]) => {
|
||||
const [_, eventName, selector] = key.match(delegateEventSplitter);
|
||||
return {
|
||||
@ -37,18 +42,44 @@ const createHandlers = (actionsFn: (...arg: any[]) => object, ...args: any[]) =>
|
||||
});
|
||||
};
|
||||
|
||||
export const createDecorator = (actionsFn: any) => (...args: any[]) => (storyFn: () => any) => {
|
||||
const applyEventHandlers = (actionsFn: any, ...handles: any[]) => {
|
||||
useEffect(() => {
|
||||
if (root != null) {
|
||||
const handlers = createHandlers(actionsFn, ...args);
|
||||
const handlers = createHandlers(actionsFn, ...handles);
|
||||
handlers.forEach(({ eventName, handler }) => root.addEventListener(eventName, handler));
|
||||
return () =>
|
||||
handlers.forEach(({ eventName, handler }) => root.removeEventListener(eventName, handler));
|
||||
}
|
||||
return undefined;
|
||||
}, [root, actionsFn, args]);
|
||||
|
||||
return storyFn();
|
||||
}, [root, actionsFn, handles]);
|
||||
};
|
||||
|
||||
export const withActions = createDecorator(actions);
|
||||
const applyDeprecatedOptions = (actionsFn: any, options: any[]) => {
|
||||
if (options) {
|
||||
deprecate(
|
||||
() => applyEventHandlers(actionsFn, options),
|
||||
dedent`
|
||||
withActions(options) is deprecated, please configure addon-actions using the addParameter api:
|
||||
|
||||
addParameters({
|
||||
actions: {
|
||||
handles: options
|
||||
},
|
||||
});
|
||||
`
|
||||
)();
|
||||
}
|
||||
};
|
||||
|
||||
export const withActions = makeDecorator({
|
||||
name: 'withActions',
|
||||
parameterName: PARAM_KEY,
|
||||
skipIfNoParametersOrOptions: true,
|
||||
wrapper: (getStory, context, { parameters, options }) => {
|
||||
applyDeprecatedOptions(actions, options as any[]);
|
||||
|
||||
if (parameters && parameters.handles) applyEventHandlers(actions, ...parameters.handles);
|
||||
|
||||
return getStory(context);
|
||||
},
|
||||
});
|
||||
|
13
addons/actions/src/register.tsx
Normal file
13
addons/actions/src/register.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { addons, types } from '@storybook/addons';
|
||||
import ActionLogger from './containers/ActionLogger';
|
||||
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
|
||||
|
||||
addons.register(ADDON_ID, (api) => {
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title: 'Actions',
|
||||
type: types.PANEL,
|
||||
render: ({ active, key }) => <ActionLogger key={key} api={api} active={active} />,
|
||||
paramKey: PARAM_KEY,
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"version": "6.0.0-alpha.33",
|
||||
"description": "A storybook addon to show different backgrounds for your preview",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -32,12 +32,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"@storybook/api": "6.0.0-alpha.29",
|
||||
"@storybook/client-logger": "6.0.0-alpha.29",
|
||||
"@storybook/components": "6.0.0-alpha.29",
|
||||
"@storybook/core-events": "6.0.0-alpha.29",
|
||||
"@storybook/theming": "6.0.0-alpha.29",
|
||||
"@storybook/addons": "6.0.0-alpha.33",
|
||||
"@storybook/api": "6.0.0-alpha.33",
|
||||
"@storybook/client-logger": "6.0.0-alpha.33",
|
||||
"@storybook/components": "6.0.0-alpha.33",
|
||||
"@storybook/core-events": "6.0.0-alpha.33",
|
||||
"@storybook/theming": "6.0.0-alpha.33",
|
||||
"core-js": "^3.0.1",
|
||||
"memoizerific": "^1.11.3",
|
||||
"react": "^16.8.3",
|
||||
|
@ -52,12 +52,12 @@ const getSelectedBackgroundColor = (list: Input[], currentSelectedValue: string)
|
||||
return currentSelectedValue;
|
||||
}
|
||||
|
||||
if (list.find(i => i.value === currentSelectedValue)) {
|
||||
if (list.find((i) => i.value === currentSelectedValue)) {
|
||||
return currentSelectedValue;
|
||||
}
|
||||
|
||||
if (list.find(i => i.default)) {
|
||||
return list.find(i => i.default).value;
|
||||
if (list.find((i) => i.default)) {
|
||||
return list.find((i) => i.default).value;
|
||||
}
|
||||
|
||||
return 'transparent';
|
||||
@ -140,7 +140,7 @@ export class BackgroundSelector extends Component<Props> {
|
||||
trigger="click"
|
||||
tooltip={({ onHide }) => (
|
||||
<TooltipLinkList
|
||||
links={getDisplayedItems(items, selectedBackgroundColor, i => {
|
||||
links={getDisplayedItems(items, selectedBackgroundColor, (i) => {
|
||||
this.change(i);
|
||||
onHide();
|
||||
})}
|
||||
|
@ -5,7 +5,7 @@ import { ADDON_ID } from './constants';
|
||||
import { BackgroundSelector } from './containers/BackgroundSelector';
|
||||
import { GridSelector } from './containers/GridSelector';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.register(ADDON_ID, (api) => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: 'Backgrounds',
|
||||
type: types.TOOL,
|
||||
|
@ -1,211 +0,0 @@
|
||||
# Storybook Centered Decorator
|
||||
|
||||
Storybook Centered Decorator can be used to center components inside the preview in [Storybook](https://storybook.js.org).
|
||||
|
||||
[Framework Support](https://github.com/storybookjs/storybook/blob/master/ADDONS_SUPPORT.md)
|
||||
|
||||
⚠️ This addon applies styling to the view in order to center the component. This may impact the look and feel of story.
|
||||
|
||||
### Usage
|
||||
|
||||
```sh
|
||||
yarn add @storybook/addon-centered --dev
|
||||
```
|
||||
|
||||
You can set the decorator locally.
|
||||
|
||||
example for React:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import centered from '@storybook/addon-centered/react';
|
||||
|
||||
import MyComponent from '../Component';
|
||||
|
||||
storiesOf('MyComponent', module)
|
||||
.addDecorator(centered)
|
||||
.add('without props', () => (<MyComponent />))
|
||||
.add('with some props', () => (<MyComponent text="The Comp"/>));
|
||||
```
|
||||
|
||||
example for Vue:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/vue';
|
||||
import centered from '@storybook/addon-centered/vue';
|
||||
|
||||
import MyComponent from '../Component.vue';
|
||||
storiesOf('MyComponent', module)
|
||||
.addDecorator(centered)
|
||||
.add('without props', () => ({
|
||||
components: { MyComponent },
|
||||
template: '<my-component />'
|
||||
}))
|
||||
.add('with some props', () => ({
|
||||
components: { MyComponent },
|
||||
template: '<my-component text="The Comp"/>'
|
||||
}));
|
||||
```
|
||||
|
||||
example for Preact:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/preact';
|
||||
import centered from '@storybook/addon-centered/preact';
|
||||
|
||||
import MyComponent from '../Component';
|
||||
|
||||
storiesOf('MyComponent', module)
|
||||
.addDecorator(centered)
|
||||
.add('without props', () => (<MyComponent />))
|
||||
.add('with some props', () => (<MyComponent text="The Comp"/>));
|
||||
```
|
||||
|
||||
example for Svelte:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/svelte';
|
||||
import Centered from '@storybook/addon-centered/svelte';
|
||||
|
||||
import Component from '../Component.svelte';
|
||||
|
||||
storiesOf('Addon|Centered', module)
|
||||
.addDecorator(Centered)
|
||||
.add('rounded', () => ({
|
||||
Component,
|
||||
data: {
|
||||
rounded: true,
|
||||
text: "Look, I'm centered!",
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import centered from '@storybook/addon-centered/mithril';
|
||||
|
||||
import MyComponent from '../Component';
|
||||
|
||||
storiesOf('MyComponent', module)
|
||||
.addDecorator(centered)
|
||||
.add('without props', () => ({
|
||||
view: () => <MyComponent />
|
||||
}))
|
||||
.add('with some props', () => ({
|
||||
view: () => <MyComponent text="The Comp"/>
|
||||
}));
|
||||
```
|
||||
|
||||
example for Angular with component:
|
||||
|
||||
```ts
|
||||
import { storiesOf } from '@storybook/angular';
|
||||
import { centered } from '@storybook/addon-centered/angular';
|
||||
|
||||
import { AppComponent } from '../app/app.component';
|
||||
|
||||
storiesOf('Addon|Centered', module)
|
||||
.addDecorator(centered)
|
||||
.add('centered component', () => ({
|
||||
component: AppComponent,
|
||||
props: {},
|
||||
}));
|
||||
|
||||
```
|
||||
|
||||
example for Angular with template:
|
||||
|
||||
```ts
|
||||
import { moduleMetadata, storiesOf } from '@storybook/angular';
|
||||
import { centered } from '@storybook/addon-centered/angular';
|
||||
|
||||
import { AppComponent } from '../app/app.component';
|
||||
|
||||
storiesOf('Addon|Centered', module)
|
||||
.addDecorator(
|
||||
moduleMetadata({
|
||||
declarations: [Button],
|
||||
})
|
||||
)
|
||||
.addDecorator(centered)
|
||||
.add('centered template', () => ({
|
||||
template: `<storybook-button-component
|
||||
[text]="text" (onClick)="onClick($event)">
|
||||
</storybook-button-component>`,
|
||||
props: {
|
||||
text: 'Hello Button',
|
||||
onClick: event => {
|
||||
console.log('some bindings work');
|
||||
console.log(event);
|
||||
},
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
Also, you can also add this decorator globally
|
||||
|
||||
example for React:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/react';
|
||||
import centered from '@storybook/addon-centered/react';
|
||||
|
||||
addDecorator(centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Vue:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/vue';
|
||||
import centered from '@storybook/addon-centered/vue';
|
||||
|
||||
addDecorator(centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Svelte:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/svelte';
|
||||
import Centered from '@storybook/addon-centered/svelte';
|
||||
|
||||
addDecorator(Centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/mithril';
|
||||
import centered from '@storybook/addon-centered/mithril';
|
||||
|
||||
addDecorator(centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
If you don't want to use centered for a story, you can disable it by using `{ disable: true }` to skip the addon:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('example', () => <button>Click me</button>, {
|
||||
centered: { disable: true },
|
||||
});
|
||||
```
|
25
addons/centered/angular.d.ts
vendored
25
addons/centered/angular.d.ts
vendored
@ -1,25 +0,0 @@
|
||||
import { StoryFn } from "@storybook/addons";
|
||||
|
||||
export interface ICollection {
|
||||
[p: string]: any;
|
||||
}
|
||||
|
||||
export interface NgModuleMetadata {
|
||||
declarations?: any[];
|
||||
entryComponents?: any[];
|
||||
imports?: any[];
|
||||
schemas?: any[];
|
||||
providers?: any[];
|
||||
}
|
||||
|
||||
export interface IStory {
|
||||
component?: any;
|
||||
props?: ICollection;
|
||||
propsMeta?: ICollection;
|
||||
moduleMetadata?: NgModuleMetadata;
|
||||
template?: string;
|
||||
styles?: string[];
|
||||
}
|
||||
declare module '@storybook/addon-centered/angular' {
|
||||
export function centered(story: StoryFn<IStory>): IStory;
|
||||
}
|
3
addons/centered/angular.js
vendored
3
addons/centered/angular.js
vendored
@ -1,3 +0,0 @@
|
||||
import fromCentered from './dist/angular';
|
||||
|
||||
export const centered = fromCentered;
|
2
addons/centered/ember.d.ts
vendored
2
addons/centered/ember.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/ember';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/ember');
|
2
addons/centered/html.d.ts
vendored
2
addons/centered/html.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/html';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/html');
|
2
addons/centered/mithril.d.ts
vendored
2
addons/centered/mithril.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/mithril';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/mithril');
|
@ -1,73 +0,0 @@
|
||||
{
|
||||
"name": "@storybook/addon-centered",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"description": "Storybook decorator to center components",
|
||||
"keywords": [
|
||||
"addon",
|
||||
"storybook"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/centered",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "addons/centered"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"README.md",
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
"ts3.5/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mithril": "^1.1.16",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"mithril": "*",
|
||||
"preact": "*",
|
||||
"rax": "*",
|
||||
"rax-view": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mithril": "*",
|
||||
"preact": "*",
|
||||
"rax": "*",
|
||||
"rax-view": "*",
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mithril": {
|
||||
"optional": true
|
||||
},
|
||||
"preact": {
|
||||
"optional": true
|
||||
},
|
||||
"rax": {
|
||||
"optional": true
|
||||
},
|
||||
"rax-view": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"gitHead": "4b9d901add9452525135caae98ae5f78dd8da9ff"
|
||||
}
|
2
addons/centered/preact.d.ts
vendored
2
addons/centered/preact.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/preact';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/preact');
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/rax');
|
2
addons/centered/react.d.ts
vendored
2
addons/centered/react.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/react';
|
||||
export default centered;
|
1
addons/centered/react.js
vendored
1
addons/centered/react.js
vendored
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/react');
|
@ -1,70 +0,0 @@
|
||||
import { makeDecorator, StoryFn } from '@storybook/addons';
|
||||
import { IStory } from '../angular.d';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function getComponentSelector(component: any) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
return component.__annotations__[0].selector;
|
||||
}
|
||||
|
||||
function getTemplate(metadata: any) {
|
||||
let tpl = '';
|
||||
if (metadata.component) {
|
||||
const selector = getComponentSelector(metadata.component);
|
||||
tpl = `<${selector}></${selector}>`;
|
||||
}
|
||||
|
||||
if (metadata.template) {
|
||||
tpl = metadata.template;
|
||||
}
|
||||
|
||||
return `
|
||||
<div [ngStyle]="styles.style">
|
||||
<div [ngStyle]="styles.innerStyle">
|
||||
${tpl}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getModuleMetadata(metadata: any) {
|
||||
const { moduleMetadata, component } = metadata;
|
||||
|
||||
if (component && !moduleMetadata) {
|
||||
return {
|
||||
declarations: [metadata.component],
|
||||
};
|
||||
}
|
||||
|
||||
if (component && moduleMetadata) {
|
||||
return {
|
||||
...moduleMetadata,
|
||||
declarations: [...moduleMetadata.declarations, metadata.component],
|
||||
};
|
||||
}
|
||||
|
||||
return moduleMetadata;
|
||||
}
|
||||
|
||||
function centered(metadataFn: StoryFn<IStory>) {
|
||||
const metadata = metadataFn();
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
template: getTemplate(metadata),
|
||||
moduleMetadata: getModuleMetadata(metadata),
|
||||
props: {
|
||||
...metadata.props,
|
||||
styles,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as StoryFn),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<div class="svelte-centered-wrapper" style="{style}">
|
||||
<div class="svelte-centered-container" style="{innerStyle}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
export let style = '';
|
||||
export let innerStyle = '';
|
||||
</script>
|
@ -1,36 +0,0 @@
|
||||
import { document } from 'global';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn: () => { template: any; context: any }) {
|
||||
const { template, context } = storyFn();
|
||||
|
||||
const element = document.createElement('div');
|
||||
Object.assign(element.style, styles.style);
|
||||
|
||||
const innerElement = document.createElement('div');
|
||||
Object.assign(innerElement.style, styles.innerStyle);
|
||||
|
||||
element.appendChild(innerElement);
|
||||
|
||||
// the inner element should append the parent
|
||||
innerElement.appendTo = function appendTo(el: any) {
|
||||
el.appendChild(element);
|
||||
};
|
||||
|
||||
return {
|
||||
template,
|
||||
context,
|
||||
element: innerElement,
|
||||
};
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as any),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { document } from 'global';
|
||||
|
||||
/**
|
||||
* Not all frameworks support an object for the style attribute but we want all to
|
||||
* consume `styles.json`. Since `styles.json` uses standard style properties for keys,
|
||||
* we can just set them on an element and then get the string result of that element's
|
||||
* `style` attribute. This also means that invalid styles are filtered out.
|
||||
*
|
||||
* @param {Object} jsonStyles
|
||||
* @returns {string}
|
||||
* @see https://stackoverflow.com/questions/38533544/jsx-css-to-inline-styles
|
||||
*/
|
||||
export default function jsonToCss(jsonStyles: Partial<CSSStyleDeclaration>) {
|
||||
const frag = document.createElement('div') as HTMLDivElement;
|
||||
|
||||
Object.keys(jsonStyles).forEach(key => {
|
||||
(frag.style as any)[key] = (jsonStyles as any)[key];
|
||||
});
|
||||
|
||||
return frag.getAttribute('style');
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
import { document, Node } from 'global';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
const INNER_ID = 'sb-addon-centered-inner';
|
||||
const WRAPPER_ID = 'sb-addon-centered-wrapper';
|
||||
|
||||
function getOrCreate(id: string, style: Partial<CSSStyleDeclaration>): HTMLDivElement {
|
||||
const elementOnDom = document.getElementById(id);
|
||||
|
||||
if (elementOnDom) {
|
||||
return elementOnDom;
|
||||
}
|
||||
|
||||
const element = document.createElement('div') as HTMLDivElement;
|
||||
element.setAttribute('id', id);
|
||||
Object.assign(element.style, style);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function getInnerDiv() {
|
||||
return getOrCreate(INNER_ID, styles.innerStyle);
|
||||
}
|
||||
|
||||
function getWrapperDiv() {
|
||||
return getOrCreate(WRAPPER_ID, styles.style);
|
||||
}
|
||||
|
||||
function centered(storyFn: () => any) {
|
||||
const inner = getInnerDiv();
|
||||
const wrapper = getWrapperDiv();
|
||||
wrapper.appendChild(inner);
|
||||
|
||||
const element = storyFn();
|
||||
|
||||
if (typeof element === 'string') {
|
||||
inner.innerHTML = element;
|
||||
} else if (element instanceof Node) {
|
||||
inner.innerHTML = '';
|
||||
inner.appendChild(element);
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as any),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/** @jsx m */
|
||||
import m, { ComponentTypes } from 'mithril';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn: () => ComponentTypes) {
|
||||
return {
|
||||
view: () => (
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{m(storyFn())}</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as any),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
const parameters = {
|
||||
name: 'centered',
|
||||
parameterName: 'centered',
|
||||
} as const;
|
||||
|
||||
export default parameters;
|
@ -1,18 +0,0 @@
|
||||
/** @jsx h */
|
||||
import { Component, h } from 'preact';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn: () => Component) {
|
||||
return (
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{storyFn()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as any),
|
||||
});
|
@ -1,23 +0,0 @@
|
||||
/** @jsx createElement */
|
||||
import { createElement } from 'rax';
|
||||
import View from 'rax-view';
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn) {
|
||||
return (
|
||||
<View style={styles.style}>
|
||||
<View style={styles.innerStyle}>{storyFn()}</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: centered,
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { makeDecorator, StoryFn } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered(storyFn: () => ReactNode) {
|
||||
/* eslint-disable no-undef */
|
||||
if (window) {
|
||||
const params = new URL(window.location.href).search;
|
||||
const isInDocsView = params.includes('viewMode=docs');
|
||||
|
||||
if (isInDocsView) {
|
||||
return storyFn();
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-undef */
|
||||
|
||||
return (
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{storyFn()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as StoryFn),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
const styles = {
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
left: '0',
|
||||
bottom: '0',
|
||||
right: '0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
overflow: 'auto',
|
||||
},
|
||||
innerStyle: {
|
||||
margin: 'auto',
|
||||
maxHeight: '100%', // Hack for centering correctly in IE11
|
||||
},
|
||||
} as const;
|
||||
|
||||
export default styles;
|
@ -1,42 +0,0 @@
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import Centered from './components/Centered.svelte';
|
||||
import styles from './styles';
|
||||
import json2CSS from './helpers/json2CSS';
|
||||
import parameters from './parameters';
|
||||
|
||||
const centeredStyles = {
|
||||
/** @type {string} */
|
||||
style: json2CSS(styles.style),
|
||||
/** @type {string} */
|
||||
innerStyle: json2CSS(styles.innerStyle),
|
||||
};
|
||||
|
||||
/**
|
||||
* This functionality works by passing the svelte story component into another
|
||||
* svelte component that has the single purpose of centering the story component
|
||||
* using a wrapper and container.
|
||||
*
|
||||
* We use the special element <svelte:component /> to achieve this.
|
||||
*
|
||||
* @see https://svelte.technology/guide#svelte-component
|
||||
*/
|
||||
function centered(storyFn: () => any) {
|
||||
const { Component: OriginalComponent, props, on } = storyFn();
|
||||
|
||||
return {
|
||||
Component: OriginalComponent,
|
||||
props,
|
||||
on,
|
||||
Wrapper: Centered,
|
||||
WrapperData: centeredStyles,
|
||||
};
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: getStory => centered(getStory as any),
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
2
addons/centered/src/typings.d.ts
vendored
2
addons/centered/src/typings.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
declare module 'global';
|
||||
declare module '*.svelte';
|
@ -1,27 +0,0 @@
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import parameters from './parameters';
|
||||
import styles from './styles';
|
||||
|
||||
function centered() {
|
||||
return {
|
||||
template: `
|
||||
<div :style="style">
|
||||
<div :style="innerStyle">
|
||||
<story/>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return styles;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default makeDecorator({
|
||||
...parameters,
|
||||
wrapper: centered,
|
||||
});
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
2
addons/centered/svelte.d.ts
vendored
2
addons/centered/svelte.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/svelte';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/svelte');
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"types": ["webpack-env"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/__tests__/**/*"
|
||||
]
|
||||
}
|
2
addons/centered/vue.d.ts
vendored
2
addons/centered/vue.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
import centered from './dist/vue';
|
||||
export default centered;
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/vue');
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-contexts",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"version": "6.0.0-alpha.33",
|
||||
"description": "Storybook Addon Contexts",
|
||||
"keywords": [
|
||||
"preact",
|
||||
@ -28,10 +28,10 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"@storybook/api": "6.0.0-alpha.29",
|
||||
"@storybook/components": "6.0.0-alpha.29",
|
||||
"@storybook/core-events": "6.0.0-alpha.29",
|
||||
"@storybook/addons": "6.0.0-alpha.33",
|
||||
"@storybook/api": "6.0.0-alpha.33",
|
||||
"@storybook/components": "6.0.0-alpha.33",
|
||||
"@storybook/core-events": "6.0.0-alpha.33",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"qs": "^6.6.0",
|
||||
@ -42,7 +42,6 @@
|
||||
"enzyme": "^3.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"global": "*",
|
||||
"preact": "*",
|
||||
"qs": "*",
|
||||
"rax": "*",
|
||||
|
@ -19,7 +19,7 @@ import { AddonSetting, AnyFunctionReturns, ContextNode, PropsMap } from './share
|
||||
export type Render<T> = (...args: [ContextNode[], PropsMap, AnyFunctionReturns<T>]) => T;
|
||||
type CreateAddonDecorator = <T>(render: Render<T>) => (contexts: AddonSetting[]) => unknown;
|
||||
|
||||
export const createAddonDecorator: CreateAddonDecorator = render => {
|
||||
export const createAddonDecorator: CreateAddonDecorator = (render) => {
|
||||
const wrapper: StoryWrapper = (getStory, context, settings: any) => {
|
||||
const { getContextNodes, getSelectionState, getPropsMap } = ContextsPreviewAPI();
|
||||
const nodes = getContextNodes(settings);
|
||||
@ -32,7 +32,6 @@ export const createAddonDecorator: CreateAddonDecorator = render => {
|
||||
name: ID,
|
||||
parameterName: PARAM,
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: false,
|
||||
wrapper,
|
||||
});
|
||||
};
|
||||
|
@ -16,13 +16,13 @@ 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 })),
|
||||
(nodeId, name) => setState((obj) => ({ ...obj, [nodeId]: name })),
|
||||
[]
|
||||
);
|
||||
|
||||
// from preview
|
||||
const emit = useChannel({
|
||||
[UPDATE_MANAGER]: newNodes => setNodes(newNodes || []),
|
||||
[UPDATE_MANAGER]: (newNodes) => setNodes(newNodes || []),
|
||||
});
|
||||
|
||||
// to preview
|
||||
|
@ -28,7 +28,7 @@ export const ToolBarControl: ToolBarControl = ({
|
||||
// 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 ||
|
||||
(params.find((param) => !!param.default) || { name: null }).name ||
|
||||
// fallback to the first
|
||||
params[0].name;
|
||||
const list = options.cancelable ? [OPT_OUT, ...paramNames] : paramNames;
|
||||
|
@ -11,7 +11,7 @@ type ToolBarMenuOptions = FCNoChildren<{
|
||||
|
||||
export const ToolBarMenuOptions: ToolBarMenuOptions = ({ activeName, list, onSelectOption }) => (
|
||||
<TooltipLinkList
|
||||
links={list.map(name => ({
|
||||
links={list.map((name) => ({
|
||||
key: name,
|
||||
id: name,
|
||||
title: name !== OPT_OUT ? name : 'Off',
|
||||
|
@ -41,7 +41,7 @@ export const ContextsPreviewAPI = singleton(() => {
|
||||
* Preview-manager communications.
|
||||
*/
|
||||
// from manager
|
||||
channel.on(UPDATE_PREVIEW, state => {
|
||||
channel.on(UPDATE_PREVIEW, (state) => {
|
||||
if (state) {
|
||||
selectionState = state;
|
||||
channel.emit(FORCE_RE_RENDER);
|
||||
|
@ -13,7 +13,7 @@ export const renderVue: Render<Vue.Component> = (contextNodes, propsMap, getStor
|
||||
return Vue.extend({
|
||||
name: ID,
|
||||
data: () => reactiveProps, // deepscan-disable-line
|
||||
render: createElement =>
|
||||
render: (createElement) =>
|
||||
getRendererFrom((Component, props, children) => {
|
||||
const { key, ref, style, classNames, ...rest } = props || Object();
|
||||
const contextData =
|
||||
|
@ -3,7 +3,7 @@ import { memorize, singleton } from './decorators';
|
||||
describe('Test on functional helpers: memorize', () => {
|
||||
it('should memorize the calculated result', () => {
|
||||
// given
|
||||
const someFn = jest.fn(x => [x]);
|
||||
const someFn = jest.fn((x) => [x]);
|
||||
const someFnMemo = memorize(someFn);
|
||||
|
||||
// when
|
||||
|
@ -23,4 +23,4 @@ export const memorize: memorize = (fn, resolver) => {
|
||||
*/
|
||||
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');
|
||||
|
@ -44,10 +44,10 @@ export const getContextNodes: getContextNodes = ({ options, parameters }) => {
|
||||
|
||||
return Array.from(new Set(titles))
|
||||
.filter(Boolean)
|
||||
.map(title =>
|
||||
.map((title) =>
|
||||
_getMergedSettings(
|
||||
(options && options.find(option => option.title === title)) || {},
|
||||
(parameters && parameters.find(param => param.title === title)) || {}
|
||||
(options && options.find((option) => option.title === title)) || {},
|
||||
(parameters && parameters.find((param) => param.title === title)) || {}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -17,9 +17,9 @@ export const _getPropsByParamName: _getPropsByParamName = (params, name = '', op
|
||||
// when opt-out context
|
||||
(options.cancelable && name === OPT_OUT && { props: OPT_OUT }) ||
|
||||
// when menu option get selected
|
||||
(name && params.find(param => param.name === name)) ||
|
||||
(name && params.find((param) => param.name === name)) ||
|
||||
// when being initialized
|
||||
params.find(param => !!param.default) ||
|
||||
params.find((param) => !!param.default) ||
|
||||
// fallback to the first
|
||||
params[0] ||
|
||||
// fallback for destructuring
|
||||
|
@ -23,11 +23,9 @@ 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
|
||||
@ -56,7 +54,7 @@ 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 }) =>
|
||||
|
@ -3,7 +3,7 @@ import addons, { types } from '@storybook/addons';
|
||||
import { ContextsManager } from './manager/ContextsManager';
|
||||
import { ID } from './shared/constants';
|
||||
|
||||
addons.register(ID, api =>
|
||||
addons.register(ID, (api) =>
|
||||
addons.add(ID, {
|
||||
title: ID,
|
||||
type: types.TOOL,
|
||||
|
@ -5,12 +5,12 @@ import { SelectionState } from './types.d';
|
||||
*/
|
||||
type deserialize = (param?: string) => SelectionState | null;
|
||||
|
||||
export const deserialize: deserialize = param =>
|
||||
export const deserialize: deserialize = (param) =>
|
||||
!param
|
||||
? null
|
||||
: param
|
||||
.split(/,+/g)
|
||||
.map(str => str.split(/=+/g))
|
||||
.map((str) => str.split(/=+/g))
|
||||
.reduce<SelectionState | null>(
|
||||
(acc, [nodeId, name]) => (nodeId && name ? { ...acc, [nodeId]: name } : acc),
|
||||
null
|
||||
@ -21,9 +21,9 @@ export const deserialize: deserialize = param =>
|
||||
*/
|
||||
type serialize = (state: ReturnType<deserialize>) => string | null;
|
||||
|
||||
export const serialize: serialize = state =>
|
||||
export const serialize: serialize = (state) =>
|
||||
!state
|
||||
? null
|
||||
: Object.entries(state)
|
||||
.map(tuple => tuple.join('='))
|
||||
.map((tuple) => tuple.join('='))
|
||||
.join(',');
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-cssresources",
|
||||
"version": "6.0.0-alpha.29",
|
||||
"version": "6.0.0-alpha.33",
|
||||
"description": "A storybook addon to switch between css resources at runtime for your story",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -32,11 +32,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.0.0-alpha.29",
|
||||
"@storybook/api": "6.0.0-alpha.29",
|
||||
"@storybook/components": "6.0.0-alpha.29",
|
||||
"@storybook/core-events": "6.0.0-alpha.29",
|
||||
"@storybook/theming": "6.0.0-alpha.29",
|
||||
"@storybook/addons": "6.0.0-alpha.33",
|
||||
"@storybook/api": "6.0.0-alpha.33",
|
||||
"@storybook/components": "6.0.0-alpha.33",
|
||||
"@storybook/core-events": "6.0.0-alpha.33",
|
||||
"@storybook/theming": "6.0.0-alpha.33",
|
||||
"core-js": "^3.0.1",
|
||||
"global": "^4.3.2",
|
||||
"react": "^16.8.3",
|
||||
|
@ -262,7 +262,7 @@ describe('CSSResourcePanel', () => {
|
||||
const node = shallowNode();
|
||||
node.instance().onStoryChange('fake-story-id');
|
||||
|
||||
defaultParameters.forEach(param => {
|
||||
defaultParameters.forEach((param) => {
|
||||
it(`should render list item with id '${param.id}'`, () => {
|
||||
expect(node.find(`#${param.id}`).length).toEqual(1);
|
||||
});
|
||||
@ -270,12 +270,7 @@ describe('CSSResourcePanel', () => {
|
||||
it(`should render list item with id '${param.id}' as ${
|
||||
param.picked ? 'checked' : 'unchecked'
|
||||
}`, () => {
|
||||
expect(
|
||||
node
|
||||
.find(`#${param.id}`)
|
||||
.first()
|
||||
.prop('checked')
|
||||
).toBe(param.picked);
|
||||
expect(node.find(`#${param.id}`).first().prop('checked')).toBe(param.picked);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ export class CssResourcePanel extends Component<Props, State> {
|
||||
lookup[res.id] = res;
|
||||
return lookup;
|
||||
}, {}) as CssResourceLookup;
|
||||
const mergedList = list.map(res => {
|
||||
const mergedList = list.map((res) => {
|
||||
const existingItem = existingIds[res.id];
|
||||
return existingItem
|
||||
? {
|
||||
@ -82,14 +82,14 @@ export class CssResourcePanel extends Component<Props, State> {
|
||||
}
|
||||
: res;
|
||||
});
|
||||
const picked = mergedList.filter(res => res.picked);
|
||||
const picked = mergedList.filter((res) => res.picked);
|
||||
this.setState({ list: mergedList, currentStoryId: id }, () => this.emit(picked));
|
||||
}
|
||||
};
|
||||
|
||||
onChange = (event: any) => {
|
||||
const { list: oldList } = this.state;
|
||||
const list = oldList.map(i => ({
|
||||
const list = oldList.map((i) => ({
|
||||
...i,
|
||||
picked: i.id === event.target.id ? event.target.checked : i.picked,
|
||||
}));
|
||||
|
@ -35,8 +35,8 @@ const getElement = (id: string, code: string) => {
|
||||
const updateElement = (id: string, code: string, value: boolean) => {
|
||||
const { element, created } = getElement(id, code);
|
||||
|
||||
element.querySelectorAll('link').forEach(child => changeMediaAttribute(child, value));
|
||||
element.querySelectorAll('style').forEach(child => changeMediaAttribute(child, value));
|
||||
element.querySelectorAll('link').forEach((child) => changeMediaAttribute(child, value));
|
||||
element.querySelectorAll('style').forEach((child) => changeMediaAttribute(child, value));
|
||||
|
||||
if (created) {
|
||||
document.body.appendChild(element);
|
||||
@ -46,16 +46,16 @@ const updateElement = (id: string, code: string, value: boolean) => {
|
||||
const list: any[] = [];
|
||||
|
||||
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));
|
||||
const added = resources.filter((i) => !list.find((r) => r.code === i.code));
|
||||
const removed = list.filter((i) => !resources.find((r) => r.code === i.code));
|
||||
|
||||
added.forEach(r => list.push(r));
|
||||
added.forEach((r) => list.push(r));
|
||||
|
||||
resources.forEach(r => {
|
||||
resources.forEach((r) => {
|
||||
const { id, code } = r;
|
||||
updateElement(id, code, true);
|
||||
});
|
||||
removed.forEach(r => {
|
||||
removed.forEach((r) => {
|
||||
const { id, code } = r;
|
||||
updateElement(id, code, false);
|
||||
});
|
||||
@ -65,7 +65,6 @@ export const withCssResources = makeDecorator({
|
||||
name: 'withCssResources',
|
||||
parameterName: PARAM_KEY,
|
||||
skipIfNoParametersOrOptions: true,
|
||||
allowDeprecatedUsage: false,
|
||||
|
||||
wrapper: (getStory, context, { options, parameters }) => {
|
||||
const storyOptions = parameters || options;
|
||||
|
@ -4,7 +4,7 @@ import { addons, types } from '@storybook/addons';
|
||||
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
|
||||
import { CssResourcePanel } from './css-resource-panel';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.register(ADDON_ID, (api) => {
|
||||
// Need to cast as any as it's not matching Addon type, to investigate
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
|
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