Merge branch 'next' into pr/15492
@ -198,11 +198,11 @@ jobs:
|
||||
command: yarn test:e2e-framework --clean --all --skip angular11 --skip angular --skip vue3 --skip web_components_typescript --skip cra
|
||||
no_output_timeout: 5m
|
||||
- store_artifacts:
|
||||
path: /tmp/storybook/cypress
|
||||
path: /tmp/cypress-record
|
||||
destination: cypress
|
||||
e2e-tests-core:
|
||||
executor:
|
||||
class: medium
|
||||
class: large
|
||||
name: sb_cypress_6_node_12
|
||||
parallelism: 2
|
||||
steps:
|
||||
@ -224,7 +224,7 @@ jobs:
|
||||
command: yarn test:e2e-framework vue3 angular angular11 web_components_typescript web_components_lit2
|
||||
no_output_timeout: 5m
|
||||
- store_artifacts:
|
||||
path: /tmp/storybook/cypress
|
||||
path: /tmp/cypress-record
|
||||
destination: cypress
|
||||
cra-bench:
|
||||
executor:
|
||||
@ -271,7 +271,7 @@ jobs:
|
||||
name: run e2e tests
|
||||
command: yarn test:e2e-framework --pnp sfcVue cra
|
||||
- store_artifacts:
|
||||
path: /tmp/storybook/cypress
|
||||
path: /tmp/cypress-record
|
||||
destination: cypress
|
||||
e2e-tests-examples:
|
||||
executor:
|
||||
@ -293,7 +293,7 @@ jobs:
|
||||
name: cypress run
|
||||
command: yarn test:e2e-examples
|
||||
- store_artifacts:
|
||||
path: /tmp/storybook/cypress
|
||||
path: /tmp/cypress-record
|
||||
destination: cypress
|
||||
smoke-tests:
|
||||
executor:
|
||||
|
@ -13,6 +13,7 @@ lib/core-server/prebuilt
|
||||
lib/codemod/src/transforms/__testfixtures__
|
||||
lib/components/src/controls/react-editable-json-tree
|
||||
scripts/storage
|
||||
scripts/repros-generator
|
||||
*.bundle.js
|
||||
*.js.map
|
||||
*.d.ts
|
||||
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -4,9 +4,9 @@ Issue:
|
||||
|
||||
## How to test
|
||||
|
||||
- Is this testable with Jest or Chromatic screenshots?
|
||||
- Does this need a new example in the kitchen sink apps?
|
||||
- Does this need an update to the documentation?
|
||||
- [ ] Is this testable with Jest or Chromatic screenshots?
|
||||
- [ ] Does this need a new example in the kitchen sink apps?
|
||||
- [ ] Does this need an update to the documentation?
|
||||
|
||||
If your answer is yes to any of these, please make sure to include it in your PR.
|
||||
|
||||
|
28
.github/workflows/generate-repros.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: Generate And Push Repros
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '2 2 */1 * *'
|
||||
workflow_dispatch:
|
||||
# To remove when the branch will be merged
|
||||
push:
|
||||
branches:
|
||||
- generate-repros
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
YARN_ENABLE_IMMUTABLE_INSTALLS: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup git user
|
||||
run: |
|
||||
git config --global user.name "Storybook Bot"
|
||||
git config --global user.email "bot@storybook.js.org"
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Generate repros with Latest Storybook CLI
|
||||
run: yarn generate-repros --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/repro-templates.git --push --force-push
|
||||
- name: Generate repros with Next Storybook CLI
|
||||
run: yarn generate-repros --next --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/repro-templates.git --push --force-push
|
28
.github/workflows/tests-unit.yml
vendored
@ -7,19 +7,15 @@ jobs:
|
||||
name: Core Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: node_modules
|
||||
key: yarn-2-cache-v1-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: install, bootstrap
|
||||
run: |
|
||||
yarn install --immutable
|
||||
yarn bootstrap --core
|
||||
- name: test
|
||||
run: |
|
||||
yarn test --runInBand --ci
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "12.x"
|
||||
cache: yarn
|
||||
- name: install, bootstrap
|
||||
run: |
|
||||
yarn install --immutable
|
||||
yarn bootstrap --core
|
||||
- name: test
|
||||
run: |
|
||||
yarn test --runInBand --ci
|
||||
|
5
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
generated
vendored
695
.yarn/releases/yarn-sources.cjs
generated
vendored
543
CHANGELOG.md
@ -1,3 +1,546 @@
|
||||
## 6.3.9 (October 1, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CLI: Add webpack5 builder to CRA5 `sb init` ([#16194](https://github.com/storybookjs/storybook/pull/16194))
|
||||
|
||||
## 6.4.0-beta.3 (October 1, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CLI: Add webpack5 builder to CRA5 `sb init` ([#16194](https://github.com/storybookjs/storybook/pull/16194))
|
||||
|
||||
## 6.4.0-beta.2 (October 1, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Controls: Hide color control format toggle when no value ([#16186](https://github.com/storybookjs/storybook/pull/16186))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Upgrade boxen to 5.x ([#16190](https://github.com/storybookjs/storybook/pull/16190))
|
||||
- Upgrade react-dev-utils to 11.0.4 ([#16196](https://github.com/storybookjs/storybook/pull/16196))
|
||||
- Dependencies: Fix ansi-html vulnerability ([#16155](https://github.com/storybookjs/storybook/pull/16155))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CLI: Better scope sample page component styles ([#16185](https://github.com/storybookjs/storybook/pull/16185))
|
||||
|
||||
## 6.4.0-beta.1 (September 26, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- MDX: Support CSF3 play/render functions ([#16159](https://github.com/storybookjs/storybook/pull/16159))
|
||||
- Addon-a11y: Export parameter types ([#16128](https://github.com/storybookjs/storybook/pull/16128))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-docs: Fix loading behavior for Canvas doc block ([#16161](https://github.com/storybookjs/storybook/pull/16161))
|
||||
- Added index.d.ts for addon-docs/angular ([#16123](https://github.com/storybookjs/storybook/pull/16123))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Angular: Remove dead code in client ([#16137](https://github.com/storybookjs/storybook/pull/16137))
|
||||
|
||||
## 6.4.0-beta.0 (September 22, 2021)
|
||||
|
||||
Storybook 6.4 is in beta! 🎊
|
||||
|
||||
SB6.4 adds interaction testing and performance re-architecture in preparation for a huge 7.0 release.
|
||||
|
||||
Track the release in the Github: [Storybook 6.4 Release 🛠](https://github.com/storybookjs/storybook/issues/15355)
|
||||
|
||||
## 6.4.0-alpha.41 (September 22, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix `./stories.json` requests in manager for relative paths ([#16114](https://github.com/storybookjs/storybook/pull/16114))
|
||||
- Core: Fix dotenv handling ([#16105](https://github.com/storybookjs/storybook/pull/16105))
|
||||
- Addon-docs: Fix embedding selected story in canvas block ([#15915](https://github.com/storybookjs/storybook/pull/15915))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Story index server: Add story sorting ([#16102](https://github.com/storybookjs/storybook/pull/16102))
|
||||
- Refactor `stories-json` to use a caching class ([#16106](https://github.com/storybookjs/storybook/pull/16106))
|
||||
|
||||
## 6.4.0-alpha.40 (September 20, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Webpack5: Fix output paths ([#16074](https://github.com/storybookjs/storybook/pull/16074))
|
||||
- Core: Infer docs only stories ([#16101](https://github.com/storybookjs/storybook/pull/16101))
|
||||
- CSF3: Fix story type back-compat ([#16107](https://github.com/storybookjs/storybook/pull/16107))
|
||||
|
||||
## 6.4.0-alpha.39 (September 18, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CSF3: Add auto-title support to on-demand V7/V6 refactor ([#16098](https://github.com/storybookjs/storybook/pull/16098))
|
||||
|
||||
## 6.4.0-alpha.38 (September 16, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Angular: Fix ng selector issue and dynamically show templates in stories ([#15976](https://github.com/storybookjs/storybook/pull/15976))
|
||||
- Core: Fix issue with more complex `stories` paths. ([#16078](https://github.com/storybookjs/storybook/pull/16078))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Main.js config: Fix Builder type ([#16013](https://github.com/storybookjs/storybook/pull/16013))
|
||||
|
||||
## 6.4.0-alpha.37 (September 16, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Angular: Fix error handling for angular builder standalone builds ([#15978](https://github.com/storybookjs/storybook/pull/15978))
|
||||
- Addon-docs: Fix `useStories` to correctly respond to change in `storyId` ([#16046](https://github.com/storybookjs/storybook/pull/16046))
|
||||
|
||||
## 6.4.0-alpha.36 (September 15, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-docs: Ensure we don't clobber multiple source container state updates ([#16039](https://github.com/storybookjs/storybook/pull/16039))
|
||||
- Core: Restore deprecation warning for configure ([#16041](https://github.com/storybookjs/storybook/pull/16041))
|
||||
- Core: Be careful in `FEATURES` check ([#16044](https://github.com/storybookjs/storybook/pull/16044))
|
||||
|
||||
## 6.4.0-alpha.35 (September 14, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Core: On demand store ([#15871](https://github.com/storybookjs/storybook/pull/15871))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- UI: Fix ActionButton out of position in Safari ([#15981](https://github.com/storybookjs/storybook/pull/15981))
|
||||
|
||||
## 6.4.0-alpha.34 (September 7, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Angular: Support storybook configuration for projects with only angular Library ([#15744](https://github.com/storybookjs/storybook/pull/15744))
|
||||
- CLI: Show framework name in startup banner ([#15966](https://github.com/storybookjs/storybook/pull/15966))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CLI: Fix sb link to yarn3 repos ([#15989](https://github.com/storybookjs/storybook/pull/15989))
|
||||
- Core: Pass proper stack of an error ([#15864](https://github.com/storybookjs/storybook/pull/15864))
|
||||
- Addon-docs/Angular: Fix default values in ArgsTable ([#15881](https://github.com/storybookjs/storybook/pull/15881))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Core: Replaced `process.env` override in `DefinePlugin` config ([#15925](https://github.com/storybookjs/storybook/pull/15925))
|
||||
- CSF: Infer defaultValue of argtype based on arg ([#15798](https://github.com/storybookjs/storybook/pull/15798))
|
||||
|
||||
## 6.3.8 (September 3, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Core: Write JSON stats file in streaming fashion and omit `chunks` for brevity ([#15889](https://github.com/storybookjs/storybook/pull/15889))
|
||||
|
||||
## 6.4.0-alpha.33 (September 1, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- TypeScript: Fix glob pattern used in package `typesVersions` config ([#15918](https://github.com/storybookjs/storybook/pull/15918))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Core: Add Babel mode v7 ([#15928](https://github.com/storybookjs/storybook/pull/15928))
|
||||
- Core: Write JSON stats file in streaming fashion and omit `chunks` for brevity ([#15889](https://github.com/storybookjs/storybook/pull/15889))
|
||||
- UI: Add playback icons ([#15909](https://github.com/storybookjs/storybook/pull/15909))
|
||||
- Misc: Generate and push repros to a GitHub repo every night ([#15877](https://github.com/storybookjs/storybook/pull/15877))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Bump cpy to 8.1.2 for security ([#15953](https://github.com/storybookjs/storybook/pull/15953))
|
||||
|
||||
## 6.4.0-alpha.32 (August 24, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CLI/Storyshots: Specify custom sb extract Chromium exe ([#15878](https://github.com/storybookjs/storybook/pull/15878))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Angular: Fix Cannot read property 'selector' of undefined ([#15874](https://github.com/storybookjs/storybook/pull/15874))
|
||||
- Addon-docs: Fix refs support in Docs pages ([#15890](https://github.com/storybookjs/storybook/pull/15890))
|
||||
|
||||
## 6.4.0-alpha.31 (August 23, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- UI: Add skip to canvas/sidebar links ([#15740](https://github.com/storybookjs/storybook/pull/15740))
|
||||
- Controls: Add id to setter button for undefined values ([#15729](https://github.com/storybookjs/storybook/pull/15729))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CSF3: Normalize windows paths in autoTitle ([#15770](https://github.com/storybookjs/storybook/pull/15770))
|
||||
- Addon-docs: Fix newline handling in ArgsTable code blocks ([#12882](https://github.com/storybookjs/storybook/pull/12882))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Build: Update `caniuse-lite` dependency ([#15863](https://github.com/storybookjs/storybook/pull/15863))
|
||||
|
||||
## 6.4.0-alpha.30 (August 14, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CLI: Improve typings of Angular components ([#15832](https://github.com/storybookjs/storybook/pull/15832))
|
||||
- Controls: Fix `esm is not defined` error with built Storybook ([#15812](https://github.com/storybookjs/storybook/pull/15812))
|
||||
|
||||
## 6.4.0-alpha.29 (August 10, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Addon-docs/Angular: Render user defined template as source if it exists ([#15743](https://github.com/storybookjs/storybook/pull/15743))
|
||||
- Core: Add MDX support to built-in stories.json generation ([#15808](https://github.com/storybookjs/storybook/pull/15808))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Controls: Add better icon for reset button ([#15737](https://github.com/storybookjs/storybook/pull/15737))
|
||||
- Add checkboxes to pull request template ([#15799](https://github.com/storybookjs/storybook/pull/15799))
|
||||
|
||||
## 6.4.0-alpha.28 (August 10, 2021)
|
||||
|
||||
Fix bad publish of `6.4.0-alpha.27` to the `latest` tag
|
||||
|
||||
## 6.3.7 (August 10, 2021)
|
||||
|
||||
Fix bad publish of `6.4.0-alpha.27` to the `latest` tag
|
||||
|
||||
## 6.4.0-alpha.26 (August 9, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Server: Update example to use options and labels for options controls ([#15789](https://github.com/storybookjs/storybook/pull/15789))
|
||||
- Controls: Remove ArrayControl ([#15788](https://github.com/storybookjs/storybook/pull/15788))
|
||||
|
||||
## 6.4.0-alpha.25 (August 8, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Angular: Add global CSF3 renderer ([#15742](https://github.com/storybookjs/storybook/pull/15742))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-docs/Angular: Use compodoc rawdescription where available ([#15774](https://github.com/storybookjs/storybook/pull/15774))
|
||||
- Core: Fix main.js glob resolution for direct paths in stories ([#15775](https://github.com/storybookjs/storybook/pull/15775))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CSF: Optionally pass Args generic type from BaseAnnotations to ArgTypes ([#14356](https://github.com/storybookjs/storybook/pull/14356))
|
||||
|
||||
## 6.4.0-alpha.24 (August 4, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- HTML: Dynamic source snippets ([#15748](https://github.com/storybookjs/storybook/pull/15748))
|
||||
|
||||
## 6.4.0-alpha.23 (August 3, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CLI: Add --no-open flag ([#15739](https://github.com/storybookjs/storybook/pull/15739))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Angular: Fix incomplete property metadata when using inheritance ([#15586](https://github.com/storybookjs/storybook/pull/15586))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Build: Upgrade to Yarn 3 ([#15682](https://github.com/storybookjs/storybook/pull/15682))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Lower babel-loader required version ([#14811](https://github.com/storybookjs/storybook/pull/14811))
|
||||
- Relax prettier version constraint ([#15298](https://github.com/storybookjs/storybook/pull/15298))
|
||||
|
||||
## 6.4.0-alpha.22 (July 28, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CSF3: Add auto-titles from standard glob patterns ([#15697](https://github.com/storybookjs/storybook/pull/15697))
|
||||
- CSF3: Add startCase to auto-generated titles ([#15618](https://github.com/storybookjs/storybook/pull/15618))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CLI: Fix Svelte CLI template markup ([#15689](https://github.com/storybookjs/storybook/pull/15689))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Server: Upgrade to CSF3 ([#15698](https://github.com/storybookjs/storybook/pull/15698))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Fix some transitive peer dependency warnings ([#15687](https://github.com/storybookjs/storybook/pull/15687))
|
||||
- Upgrade react-refresh plugin to fix fast refresh on Webpack5 ([#15616](https://github.com/storybookjs/storybook/pull/15616))
|
||||
|
||||
## 6.3.6 (July 26, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CLI: Fix debug webpack output in static build ([#15674](https://github.com/storybookjs/storybook/pull/15674))
|
||||
- CSF3: Fix custom render function ([#15668](https://github.com/storybookjs/storybook/pull/15668))
|
||||
|
||||
## 6.4.0-alpha.21 (July 26, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CLI: Fix debug webpack output in static build ([#15674](https://github.com/storybookjs/storybook/pull/15674))
|
||||
- Controls: Fix boolean toggle style to match underlying value ([#15676](https://github.com/storybookjs/storybook/pull/15676))
|
||||
- Components: Fix Button to accept href attribute ([#15671](https://github.com/storybookjs/storybook/pull/15671))
|
||||
|
||||
## 6.4.0-alpha.20 (July 24, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CSF3: Fix custom render function ([#15668](https://github.com/storybookjs/storybook/pull/15668))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Remove glob-base dependency ([#15399](https://github.com/storybookjs/storybook/pull/15399))
|
||||
|
||||
## 6.3.5 (July 22, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Controls: Don't set arg in validateOptions if it would be `undefined` ([#15654](https://github.com/storybookjs/storybook/pull/15654))
|
||||
- Trailing comma handling for "-s" command line paramenter ([#15615](https://github.com/storybookjs/storybook/pull/15615))
|
||||
- Controls: Fix color matching behavior for non-string types ([#15549](https://github.com/storybookjs/storybook/pull/15549))
|
||||
- Composition: Fix refs ordering ([#15527](https://github.com/storybookjs/storybook/pull/15527))
|
||||
|
||||
## 6.4.0-alpha.19 (July 22, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Controls: Don't set arg in validateOptions if it would be `undefined` ([#15654](https://github.com/storybookjs/storybook/pull/15654))
|
||||
- Vue: Add support for tsx ([#11936](https://github.com/storybookjs/storybook/pull/11936))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CLI: Fix trailing comma handling for "-s" command line paramenter ([#15615](https://github.com/storybookjs/storybook/pull/15615))
|
||||
- Components: Lazy-load syntax highlighter ([#15607](https://github.com/storybookjs/storybook/pull/15607))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Controls: Clean up arg unboxing and switch statements ([#14394](https://github.com/storybookjs/storybook/pull/14394))
|
||||
- Examples: Fix react-ts to be runnable standalone ([#15621](https://github.com/storybookjs/storybook/pull/15621))
|
||||
|
||||
## 6.4.0-alpha.18 (July 16, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- UI: Allow keyboard shortcut to copy code in preview blocks ([#15559](https://github.com/storybookjs/storybook/pull/15559))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Avoid slow regex.match call in renderJsx ([#15581](https://github.com/storybookjs/storybook/pull/15581))
|
||||
|
||||
## 6.4.0-alpha.17 (July 15, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Types: Export BaseStoryFn and BaseStoryObject ([#15592](https://github.com/storybookjs/storybook/pull/15592))
|
||||
- Addon-docs: Add transparency support to color swatch ([#14439](https://github.com/storybookjs/storybook/pull/14439))
|
||||
|
||||
## 6.4.0-alpha.16 (July 13, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Addon-backgrounds: Respect user's reduced motion settings ([#13711](https://github.com/storybookjs/storybook/pull/13711))
|
||||
- CSF: Add CSF3 typings ([#15558](https://github.com/storybookjs/storybook/pull/15558))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Angular: Fix actions argType auto generation ([#15563](https://github.com/storybookjs/storybook/pull/15563))
|
||||
|
||||
## 6.4.0-alpha.15 (July 13, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Controls: Fix color matching behavior for non-string types ([#15549](https://github.com/storybookjs/storybook/pull/15549))
|
||||
- UI: Fix toggle button for custom theming ([#15449](https://github.com/storybookjs/storybook/pull/15449))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Build: Fix `publish` step on CircleCI ([#15556](https://github.com/storybookjs/storybook/pull/15556))
|
||||
- Examples: Add no-manager-cache to all examples ([#15546](https://github.com/storybookjs/storybook/pull/15546))
|
||||
- Official-storybook: Add example of embedding story object in MDX ([#15533](https://github.com/storybookjs/storybook/pull/15533))
|
||||
|
||||
## 6.4.0-alpha.14 (July 11, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Web-components: Dynamic source snippets ([#15337](https://github.com/storybookjs/storybook/pull/15337))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Essentials: Add measure addon to monorepo ([#15545](https://github.com/storybookjs/storybook/pull/15545))
|
||||
|
||||
## 6.4.0-alpha.13 (July 9, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Addon-docs/Angular: Add unique id to Angular stories ([#15501](https://github.com/storybookjs/storybook/pull/15501))
|
||||
- Composition: Fix refs ordering ([#15527](https://github.com/storybookjs/storybook/pull/15527))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Essentials: Add outline addon to monorepo ([#15526](https://github.com/storybookjs/storybook/pull/15526))
|
||||
- Build: Fix cache setup in GitHub Actions workflow ([#15523](https://github.com/storybookjs/storybook/pull/15523))
|
||||
|
||||
## 6.3.4 (July 8, 2021)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Addon-docs: Cache DocsContext on window to prevent duplication ([#15428](https://github.com/storybookjs/storybook/pull/15428))
|
||||
|
||||
## 6.3.3 (July 7, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Webpack5: Quit process after finishing a static build ([#15483](https://github.com/storybookjs/storybook/pull/15483))
|
||||
- Addon-docs/Angular: Fix numeric args default value handling ([#15491](https://github.com/storybookjs/storybook/pull/15491))
|
||||
- Angular: Fix circular reference not being handled in moduleMetadata ([#15410](https://github.com/storybookjs/storybook/pull/15410))
|
||||
- Core: Fix double rebuilds by removing aggregateTimeout ([#15372](https://github.com/storybookjs/storybook/pull/15372))
|
||||
- CLI: Fix NPM typo ([#15461](https://github.com/storybookjs/storybook/pull/15461))
|
||||
|
||||
## 6.4.0-alpha.12 (July 7, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Webpack5: Quit process after finishing a static build ([#15483](https://github.com/storybookjs/storybook/pull/15483))
|
||||
- Addon-docs/Angular: Fix numeric args default value handling ([#15491](https://github.com/storybookjs/storybook/pull/15491))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Angular: Make Ivy work by default in the angular-cli example ([#15280](https://github.com/storybookjs/storybook/pull/15280))
|
||||
- Official-storybook: Fix shortcut for navigating to previous language ([#15489](https://github.com/storybookjs/storybook/pull/15489))
|
||||
- Addon-docs: Add docs to standalone example ([#7848](https://github.com/storybookjs/storybook/pull/7848))
|
||||
- Build: Update Yarn cache setup in GitHub Actions workflow ([#15480](https://github.com/storybookjs/storybook/pull/15480))
|
||||
|
||||
## 6.4.0-alpha.11 (July 3, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- UI: Fix sidebar toggle in fullscreen mode ([#15459](https://github.com/storybookjs/storybook/pull/15459))
|
||||
- Angular: Fix circular reference not being handled in moduleMetadata ([#15410](https://github.com/storybookjs/storybook/pull/15410))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Addon-a11y: Reverse help and description labels in accordion ([#15466](https://github.com/storybookjs/storybook/pull/15466))
|
||||
|
||||
## 6.4.0-alpha.10 (July 2, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- UI: Display menu icon on the toolbar when the sidebar is collapsed ([#15369](https://github.com/storybookjs/storybook/pull/15369))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix double rebuilds by removing aggregateTimeout ([#15372](https://github.com/storybookjs/storybook/pull/15372))
|
||||
- CLI: Fix NPM typo ([#15461](https://github.com/storybookjs/storybook/pull/15461))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Addon-docs: Cache DocsContext on window to prevent duplication ([#15428](https://github.com/storybookjs/storybook/pull/15428))
|
||||
|
||||
## 6.3.2 (June 30, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Essentials: Update measure and outline. Fix alt+tab issues on windows. ([#15402](https://github.com/storybookjs/storybook/pull/15402))
|
||||
- Core: Fix decorator context update ([#15408](https://github.com/storybookjs/storybook/pull/15408))
|
||||
- Revert "Vue3: Update args without re-mounting component" ([#15409](https://github.com/storybookjs/storybook/pull/15409))
|
||||
- Upgrade bad release of `react-docgen-typescript-plugin` ([#15432](https://github.com/storybookjs/storybook/pull/15432))
|
||||
|
||||
## 6.4.0-alpha.9 (June 30, 2021)
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Upgrade bad release of `react-docgen-typescript-plugin` ([#15432](https://github.com/storybookjs/storybook/pull/15432))
|
||||
|
||||
## 6.4.0-alpha.8 (June 30, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- Web-components: Custom Elements Manifest v1 support ([#15138](https://github.com/storybookjs/storybook/pull/15138))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CSF: Fix auto-title generation for standard config dir ([#15430](https://github.com/storybookjs/storybook/pull/15430))
|
||||
|
||||
### Dependency Upgrades
|
||||
|
||||
- Upgrade `react-docgen-typescript-plugin` for refresh perf regression ([#15431](https://github.com/storybookjs/storybook/pull/15431))
|
||||
|
||||
## 6.4.0-alpha.7 (June 29, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CSF: Generate default titles based on file path ([#15376](https://github.com/storybookjs/storybook/pull/15376))
|
||||
|
||||
## 6.4.0-alpha.6 (June 29, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Fix decorator context update ([#15408](https://github.com/storybookjs/storybook/pull/15408))
|
||||
- Revert "Vue3: Update args without re-mounting component" ([#15409](https://github.com/storybookjs/storybook/pull/15409))
|
||||
|
||||
## 6.4.0-alpha.5 (June 29, 2021)
|
||||
|
||||
### Features
|
||||
|
||||
- CSF: Add stories.json generation for CSF3 stories ([#15395](https://github.com/storybookjs/storybook/pull/15395))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Essentials: Update measure and outline. Fix alt+tab issues on windows. ([#15402](https://github.com/storybookjs/storybook/pull/15402))
|
||||
|
||||
## 6.3.1 (June 28, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Only use dotenv-webpack when a user has a dotenv file ([#15365](https://github.com/storybookjs/storybook/pull/15365))
|
||||
- Essentials: Update addon measure and outline ([#15354](https://github.com/storybookjs/storybook/pull/15354))
|
||||
- Actions: Don't override existing action args ([#15394](https://github.com/storybookjs/storybook/pull/15394))
|
||||
- Svelte: Fix argType.type.name extraction ([#15332](https://github.com/storybookjs/storybook/pull/15332))
|
||||
- CSF3: Genericize feature flagging and fix webpack5 ([#15375](https://github.com/storybookjs/storybook/pull/15375))
|
||||
- Webpack5: Fix warnings typo ([#15374](https://github.com/storybookjs/storybook/pull/15374))
|
||||
- UI: Fix navigation after no story error ([#15349](https://github.com/storybookjs/storybook/pull/15349))
|
||||
- CSF3: Rename setup to play ([#15358](https://github.com/storybookjs/storybook/pull/15358))
|
||||
- Upgrade dotenv-webpack to 7.0.x ([#15343](https://github.com/storybookjs/storybook/pull/15343))
|
||||
|
||||
## 6.4.0-alpha.4 (June 28, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Actions: Don't override existing action args ([#15394](https://github.com/storybookjs/storybook/pull/15394))
|
||||
|
||||
## 6.4.0-alpha.3 (June 26, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- CSF3: Genericize feature flagging and fix webpack5 ([#15375](https://github.com/storybookjs/storybook/pull/15375))
|
||||
- Webpack5: Fix warnings typo ([#15374](https://github.com/storybookjs/storybook/pull/15374))
|
||||
|
||||
## 6.4.0-alpha.2 (June 25, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Core: Only use dotenv-webpack when a user has a dotenv file ([#15365](https://github.com/storybookjs/storybook/pull/15365))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- CSF3: Rename setup to play ([#15358](https://github.com/storybookjs/storybook/pull/15358))
|
||||
|
||||
## 6.4.0-alpha.1 (June 25, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Essentials: Update addon measure and outline ([#15354](https://github.com/storybookjs/storybook/pull/15354))
|
||||
- UI: Fix navigation after no story error ([#15349](https://github.com/storybookjs/storybook/pull/15349))
|
||||
|
||||
## 6.4.0-alpha.0 (June 24, 2021)
|
||||
|
||||
### Bug Fixes
|
||||
|
211
MIGRATION.md
@ -1,13 +1,22 @@
|
||||
<h1>Migration</h1>
|
||||
|
||||
- [From version 6.3.x to 6.4.0](#from-version-63x-to-640)
|
||||
- [CSF3 enabled](#csf3-enabled)
|
||||
- [Story Store v7](#story-store-v7)
|
||||
- [Behavioral differences](#behavioral-differences)
|
||||
- [Using the v7 store](#using-the-v7-store)
|
||||
- [V7-style story sort](#v7-style-story-sort)
|
||||
- [Babel mode v7](#babel-mode-v7)
|
||||
- [Loader behavior with args changes](#loader-behavior-with-args-changes)
|
||||
- [Angular component parameter removed](#angular-component-parameter-removed)
|
||||
- [From version 6.2.x to 6.3.0](#from-version-62x-to-630)
|
||||
- [Webpack 5 manager build](#webpack-5-manager-build)
|
||||
- [Angular 12 upgrade](#angular-12-upgrade)
|
||||
- [Lit support](#lit-support)
|
||||
- [No longer inferring default values of args](#no-longer-inferring-default-values-of-args)
|
||||
- [6.3 deprecations](#63-deprecations)
|
||||
- [Deprecated addon-knobs](#deprecated-addon-knobs)
|
||||
- [Deprecated scoped blocks imports](#deprecated-scoped-blocks-imports)
|
||||
- [Deprecated `argType.defaultValue`](#deprecated-argtypedefaultvalue)
|
||||
- [Deprecated layout URL params](#deprecated-layout-url-params)
|
||||
- [From version 6.1.x to 6.2.0](#from-version-61x-to-620)
|
||||
- [MDX pattern tweaked](#mdx-pattern-tweaked)
|
||||
@ -162,6 +171,138 @@
|
||||
- [Packages renaming](#packages-renaming)
|
||||
- [Deprecated embedded addons](#deprecated-embedded-addons)
|
||||
|
||||
## From version 6.3.x to 6.4.0
|
||||
|
||||
### CSF3 enabled
|
||||
|
||||
SB6.3 introduced a feature flag, `features.previewCsfV3`, to opt-in to experimental [CSF3 syntax support](https://storybook.js.org/blog/component-story-format-3-0/). In SB6.4, CSF3 is supported regardless of `previewCsfV3`'s value. This should be a fully backwards-compatible change. The `previewCsfV3` flag has been deprecated and will be removed in SB7.0.
|
||||
|
||||
### Story Store v7
|
||||
|
||||
SB6.4 introduces an opt-in feature flag, `features.storyStoreV7`, which loads stories in an "on demand" way (that is when rendered), rather than up front when the Storybook is booted. This way of operating will become the default in 7.0 and will likely be switched to opt-out in that version.
|
||||
|
||||
The key benefit of the on demand store is that stories are code-split automatically (in `builder-webpack4` and `builder-webpack5`), which allows for much smaller bundle sizes, faster rendering, and improved general performance via various opt-in Webpack features.
|
||||
|
||||
The on-demand store relies on the "story index" data structure which is generated in the server (node) via static code analysis. As such, it has the following limitations:
|
||||
|
||||
- Does not work with `storiesOf()`
|
||||
- Does not work if you used dynamic story names or component titles.
|
||||
|
||||
However, the `autoTitle` feature is supported.
|
||||
|
||||
#### Behavioral differences
|
||||
|
||||
The key behavioral differences of the v7 store are:
|
||||
|
||||
- `SET_STORIES` is not emitted on boot up. Instead the manager loads the story index independenly.
|
||||
- A new event `STORY_PREPARED` is emitted when a story is rendered for the first time, which contains metadata about the story, such as `parameters`.
|
||||
- All "entire" store APIs such as `extract()` need to be proceeded by an async call to `loadAllCSFFiles()` which fetches all CSF files and processes them.
|
||||
|
||||
#### Using the v7 store
|
||||
|
||||
To activate the v7 mode set the feature flag in your `.storybook/main.js` config:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ... your existing config
|
||||
features: {
|
||||
storyStoreV7: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
NOTE: `features.storyStoreV7` implies `features.buildStoriesJson` and has the same limitations.
|
||||
|
||||
#### V7-style story sort
|
||||
|
||||
If you've written a custom `storySort` function, you'll need to rewrite it for V7.
|
||||
|
||||
SB6.x supports a global story function specified in `.storybook/preview.js`. It accepts two arrays which each contain:
|
||||
|
||||
- The story ID
|
||||
- A story object that contains the name, title, etc.
|
||||
- The component's parameters
|
||||
- The project-level parameters
|
||||
|
||||
SB 7.0 streamlines the story function. It now accepts a `StoryIndexEntry` which is
|
||||
an object that contains only the story's `id`, `title`, `name`, and `importPath`.
|
||||
|
||||
Consider the following example, before and after:
|
||||
|
||||
```js
|
||||
// v6-style sort
|
||||
function storySort(a, b) {
|
||||
return a[1].kind === b[1].kind
|
||||
? 0
|
||||
: a[1].id.localeCompare(b[1].id, undefined, { numeric: true });
|
||||
},
|
||||
```
|
||||
|
||||
And the after version using `title` instead of `kind` and not receiving the full parameters:
|
||||
|
||||
```js
|
||||
// v7-style sort
|
||||
function storySort(a, b) {
|
||||
return a.title === b.title
|
||||
? 0
|
||||
: a.id.localeCompare(b.id, undefined, { numeric: true });
|
||||
},
|
||||
```
|
||||
|
||||
### Babel mode v7
|
||||
|
||||
SB6.4 introduces an opt-in feature flag, `features.babelModeV7`, that reworks the way Babel is configured in Storybook to make it more consistent with the Babel is configured in your app. This breaking change will become the default in SB 7.0, but we encourage you to migrate today.
|
||||
|
||||
> NOTE: CRA apps using `@storybook/preset-create-react-app` use CRA's handling, so the new flag has no effect on CRA apps.
|
||||
|
||||
In SB6.x and earlier, Storybook provided its own default configuration and inconsistently handled configurations from the user's babelrc file. This resulted in a final configuration that differs from your application's configuration AND is difficult to debug.
|
||||
|
||||
In `babelModeV7`, Storybook no longer provides its own default configuration and is primarily configured via babelrc file, with small, incremental updates from Storybook addons.
|
||||
|
||||
In 6.x, Storybook supported a `.storybook/babelrc` configuration option. This is no longer supported and it's up to you to reconcile this with your project babelrc.
|
||||
|
||||
To activate the v7 mode set the feature flag in your `.storybook/main.js` config:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ... your existing config
|
||||
features: {
|
||||
babelModeV7: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
In the new mode, Storybook expects you to provide a configuration file. If you want a configuration file that's equivalent to the 6.x default, you can run the following command in your project directory:
|
||||
|
||||
```sh
|
||||
npx sb@next babelrc
|
||||
```
|
||||
|
||||
This will create a `.babelrc.json` file. This file includes a bunch of babel plugins, so you may need to add new package devDependencies accordingly.
|
||||
|
||||
### Loader behavior with args changes
|
||||
|
||||
In 6.4 the behavior of loaders when arg changes occurred was tweaked so loaders do not re-run. Instead the previous value of the loader in passed to the story, irrespective of the new args.
|
||||
|
||||
### Angular component parameter removed
|
||||
|
||||
In SB6.3 and earlier, the `default.component` metadata was implemented as a parameter, meaning that stories could set `parameters.component` to override the default export. This was an internal implementation that was never documented, but it was mistakenly used in some Angular examples.
|
||||
|
||||
If you have Angular stories of the form:
|
||||
|
||||
```js
|
||||
export const MyStory = () => ({ ... })
|
||||
SomeStory.parameters = { component: MyComponent };
|
||||
```
|
||||
|
||||
You should rewrite them as:
|
||||
|
||||
```js
|
||||
export const MyStory = () => ({ component: MyComponent, ... })
|
||||
```
|
||||
|
||||
[More discussion here.](https://github.com/storybookjs/storybook/pull/16010#issuecomment-917378595)
|
||||
|
||||
## From version 6.2.x to 6.3.0
|
||||
|
||||
### Webpack 5 manager build
|
||||
@ -176,6 +317,14 @@ yarn add @storybook/manager-webpack5 --dev
|
||||
npm install @storybook/manager-webpack5 --save-dev
|
||||
```
|
||||
|
||||
Because Storybook uses `webpack@4` as the default, it's possible for the wrong version of webpack to get hoisted by your package manager. If you receive an error that looks like you might be using the wrong version of webpack, install `webpack@5` explicitly as a dev dependency to force it to be hoisted:
|
||||
|
||||
```shell
|
||||
yarn add webpack@5 --dev
|
||||
# Or
|
||||
npm install webpack@5 --save-dev
|
||||
```
|
||||
|
||||
### Angular 12 upgrade
|
||||
|
||||
Storybook 6.3 supports Angular 12 out of the box when you install it fresh. However, if you're upgrading your project from a previous version, you'll need to do the following steps to force Storybook to use webpack 5 for building your project:
|
||||
@ -204,34 +353,13 @@ To do so, it relies on helpers added in the latest minor versions of `lit-html`/
|
||||
|
||||
According to the package manager you are using, it can be handled automatically when updating Storybook or can require to manually update the versions and regenerate the lockfile.
|
||||
|
||||
### 6.3 deprecations
|
||||
|
||||
#### Deprecated addon-knobs
|
||||
|
||||
We are replacing `@storybook/addon-knobs` with `@storybook/addon-controls`.
|
||||
|
||||
- [Rationale & discussion](https://github.com/storybookjs/storybook/discussions/15060)
|
||||
- [Migration notes](https://github.com/storybookjs/storybook/blob/next/addons/controls/README.md#how-do-i-migrate-from-addon-knobs)
|
||||
|
||||
#### Deprecated scoped blocks imports
|
||||
|
||||
In 6.3, we changed doc block imports from `@storybook/addon-docs/blocks` to `@storybook/addon-docs`. This makes it possible for bundlers to automatically choose the ESM or CJS version of the library depending on the context.
|
||||
|
||||
To update your code, you should be able to global replace `@storybook/addon-docs/blocks` with `@storybook/addon-docs`. Example:
|
||||
|
||||
```js
|
||||
// before
|
||||
import { Meta, Story } from '@storybook/addon-docs/blocks';
|
||||
|
||||
// after
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
```
|
||||
|
||||
#### Deprecated `argType.defaultValue`
|
||||
### No longer inferring default values of args
|
||||
|
||||
Previously, unset `args` were set to the `argType.defaultValue` if set or inferred from the component's prop types (etc.). In 6.3 we no longer infer default values and instead set arg values to `undefined` when unset, allowing the framework to supply the default value.
|
||||
|
||||
If you were using `argType.defaultValue` to fix issues with the above inference, it should no longer be necessary, you can remove that code. If you were using it to set a default value for an arg, there is a simpler way; simply set a value for the arg at the component level:
|
||||
If you were using `argType.defaultValue` to fix issues with the above inference, it should no longer be necessary, you can remove that code.
|
||||
|
||||
If you were using `argType.defaultValue` or relying on inference to set a default value for an arg, you should now set a value for the arg at the component level:
|
||||
|
||||
```js
|
||||
export default {
|
||||
@ -255,6 +383,29 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### 6.3 deprecations
|
||||
|
||||
#### Deprecated addon-knobs
|
||||
|
||||
We are replacing `@storybook/addon-knobs` with `@storybook/addon-controls`.
|
||||
|
||||
- [Rationale & discussion](https://github.com/storybookjs/storybook/discussions/15060)
|
||||
- [Migration notes](https://github.com/storybookjs/storybook/blob/next/addons/controls/README.md#how-do-i-migrate-from-addon-knobs)
|
||||
|
||||
#### Deprecated scoped blocks imports
|
||||
|
||||
In 6.3, we changed doc block imports from `@storybook/addon-docs/blocks` to `@storybook/addon-docs`. This makes it possible for bundlers to automatically choose the ESM or CJS version of the library depending on the context.
|
||||
|
||||
To update your code, you should be able to global replace `@storybook/addon-docs/blocks` with `@storybook/addon-docs`. Example:
|
||||
|
||||
```js
|
||||
// before
|
||||
import { Meta, Story } from '@storybook/addon-docs/blocks';
|
||||
|
||||
// after
|
||||
import { Meta, Story } from '@storybook/addon-docs';
|
||||
```
|
||||
|
||||
#### Deprecated layout URL params
|
||||
|
||||
Several URL params to control the manager layout have been deprecated and will be removed in 7.0:
|
||||
@ -263,7 +414,7 @@ Several URL params to control the manager layout have been deprecated and will b
|
||||
- `panelRight=1`: use `panel=right` instead
|
||||
- `stories=0`: use `nav=false` instead
|
||||
|
||||
Additionally, support for legacy URLs using `selectedKind` and `selectedStory` will be removed in 7.0. Use `path` instead.
|
||||
Additionally, support for legacy URLs using `selectedKind` and `selectedStory` will be removed in 7.0. Use `path` instead.
|
||||
|
||||
## From version 6.1.x to 6.2.0
|
||||
|
||||
@ -1336,13 +1487,13 @@ The description doc block on DocsPage has also been updated. To see how to confi
|
||||
|
||||
### React Native Async Storage
|
||||
|
||||
Starting from version React Native 0.59, Async Storage is deprecated in React Native itself. The new @react-native-community/async-storage module requires native installation, and we don't want to have it as a dependency for React Native Storybook.
|
||||
Starting from version React Native 0.59, Async Storage is deprecated in React Native itself. The new @react-native-async-storage/async-storage module requires native installation, and we don't want to have it as a dependency for React Native Storybook.
|
||||
|
||||
To avoid that now you have to manually pass asyncStorage to React Native Storybook with asyncStorage prop. To notify users we are displaying a warning about it.
|
||||
|
||||
Solution:
|
||||
|
||||
- Use `require('@react-native-community/async-storage').default` for React Native v0.59 and above.
|
||||
- Use `require('@react-native-async-storage/async-storage').default` for React Native v0.59 and above.
|
||||
- Use `require('react-native').AsyncStorage` for React Native v0.58 or below.
|
||||
- Use `null` to disable Async Storage completely.
|
||||
|
||||
@ -1369,7 +1520,7 @@ Addon-docs configuration gets simpler in 5.3. In 5.2, each framework had its own
|
||||
|
||||
We've deprecated the ability to specify the hierarchy separators (how you control the grouping of story kinds in the sidebar). From Storybook 6.0 we will have a single separator `/`, which cannot be configured.
|
||||
|
||||
If you are currently using using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, we provide a codemod, [`upgrade-hierarchy-separators`](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#upgrade-hierarchy-separators), that can be used to rename all your components.
|
||||
If you are currently using custom separators, we encourage you to migrate to using `/` as the sole separator. If you are using `|` or `.` as a separator currently, we provide a codemod, [`upgrade-hierarchy-separators`](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#upgrade-hierarchy-separators), that can be used to rename all your components.
|
||||
|
||||
```
|
||||
yarn sb migrate upgrade-hierarchy-separators --glob="*.stories.js"
|
||||
|
@ -134,6 +134,8 @@ For additional help, join us in the [Storybook Discord](https://discord.gg/story
|
||||
| [storyshots](addons/storyshots/) | Snapshot testing for components in Storybook |
|
||||
| [storysource](addons/storysource/) | View the code of your stories within the Storybook UI |
|
||||
| [viewport](addons/viewport/) | Change display sizes and layouts for responsive components using Storybook |
|
||||
| [outline](addons/outline/) | Visuallly debug the CSS layout and alignment within the Storybook UI |
|
||||
| [measure](addons/measure/) | Visually inspect the layout and box model within the Storybook UI |
|
||||
|
||||
See [Addon / Framework Support Table](https://storybook.js.org/docs/react/api/frameworks-feature-support)
|
||||
|
||||
|
@ -4,7 +4,7 @@ This Storybook addon can be helpful to make your UI components more accessible.
|
||||
|
||||
[Framework Support](https://github.com/storybookjs/storybook/blob/main/ADDONS_SUPPORT.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Getting started
|
||||
|
||||
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 175 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-a11y",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Test component compliance with web accessibility standards",
|
||||
"keywords": [
|
||||
"a11y",
|
||||
@ -11,13 +11,13 @@
|
||||
"verify",
|
||||
"test"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook#readme",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/a11y",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/storybookjs/storybook.git",
|
||||
"url": "https://github.com/storybookjs/storybook.git",
|
||||
"directory": "addons/a11y"
|
||||
},
|
||||
"funding": {
|
||||
@ -30,7 +30,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -45,14 +45,14 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/channels": "6.4.0-alpha.0",
|
||||
"@storybook/client-api": "6.4.0-alpha.0",
|
||||
"@storybook/client-logger": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/channels": "6.4.0-beta.3",
|
||||
"@storybook/client-logger": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"axe-core": "^4.2.0",
|
||||
"core-js": "^3.8.2",
|
||||
"global": "^4.4.0",
|
||||
@ -81,7 +81,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Accessibility",
|
||||
|
@ -7,7 +7,7 @@ const Wrapper = styled.div({
|
||||
padding: 12,
|
||||
marginBottom: 10,
|
||||
});
|
||||
const Help = styled.p({
|
||||
const Description = styled.p({
|
||||
margin: '0 0 12px',
|
||||
});
|
||||
const Link = styled.a({
|
||||
@ -24,7 +24,7 @@ interface InfoProps {
|
||||
export const Info: FunctionComponent<InfoProps> = ({ item }) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Help>{item.help}</Help>
|
||||
<Description>{item.description}</Description>
|
||||
<Link href={item.helpUrl} target="_blank">
|
||||
More info...
|
||||
</Link>
|
||||
|
@ -82,7 +82,7 @@ export const Item = (props: ItemProps) => {
|
||||
transform: `rotate(${open ? 0 : -90}deg)`,
|
||||
}}
|
||||
/>
|
||||
{item.description}
|
||||
{item.help}
|
||||
</HeaderBar>
|
||||
<HighlightToggleElement>
|
||||
<HighlightToggle toggleId={highlightToggleId} elementsToHighlight={item.nodes} />
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { DecoratorFunction } from '@storybook/addons';
|
||||
import { AnyFramework, DecoratorFunction } from '@storybook/csf';
|
||||
import deprecate from 'util-deprecate';
|
||||
import dedent from 'ts-dedent';
|
||||
|
||||
export { PARAM_KEY } from './constants';
|
||||
export * from './highlight';
|
||||
export * from './params';
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
||||
|
||||
export const withA11y: DecoratorFunction = deprecate(
|
||||
export const withA11y: DecoratorFunction<AnyFramework> = deprecate(
|
||||
(storyFn, storyContext) => {
|
||||
return storyFn(storyContext);
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ Storybook Addon Actions can be used to display data received by event handlers i
|
||||
|
||||
[Framework Support](https://storybook.js.org/docs/react/api/frameworks-feature-support)
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 80 KiB |
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@storybook/addon-actions",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Get UI feedback when an action is performed on an interactive element",
|
||||
"keywords": [
|
||||
"storybook",
|
||||
"essentials",
|
||||
"data-state"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/actions",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/actions",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -26,7 +26,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -41,12 +41,12 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/client-api": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"core-js": "^3.8.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"global": "^4.4.0",
|
||||
@ -78,7 +78,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Actions",
|
||||
|
@ -8,6 +8,7 @@ describe('actions parameter enhancers', () => {
|
||||
|
||||
it('should add actions that match a pattern', () => {
|
||||
const args = inferActionsFromArgTypesRegex(({
|
||||
initialArgs: {},
|
||||
argTypes,
|
||||
parameters,
|
||||
} as unknown) as StoryContext);
|
||||
@ -17,25 +18,41 @@ describe('actions parameter enhancers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should override pre-existing argTypes', () => {
|
||||
it('should NOT override pre-existing args', () => {
|
||||
const args = inferActionsFromArgTypesRegex(({
|
||||
initialArgs: { onClick: 'pre-existing value' },
|
||||
argTypes,
|
||||
parameters,
|
||||
argTypes: {
|
||||
onClick: { defaultValue: 'pre-existing value' },
|
||||
},
|
||||
} as unknown) as StoryContext);
|
||||
expect(args).toEqual({
|
||||
onClick: expect.any(Function),
|
||||
});
|
||||
expect(args).toEqual({ onFocus: expect.any(Function) });
|
||||
});
|
||||
|
||||
it('should NOT override pre-existing args, if null', () => {
|
||||
const args = inferActionsFromArgTypesRegex(({
|
||||
initialArgs: { onClick: null },
|
||||
argTypes,
|
||||
parameters,
|
||||
} as unknown) as StoryContext);
|
||||
expect(args).toEqual({ onFocus: expect.any(Function) });
|
||||
});
|
||||
|
||||
it('should override pre-existing args, if undefined', () => {
|
||||
const args = inferActionsFromArgTypesRegex(({
|
||||
initialArgs: { onClick: undefined },
|
||||
argTypes,
|
||||
parameters,
|
||||
} as unknown) as StoryContext);
|
||||
expect(args).toEqual({ onClick: expect.any(Function), onFocus: expect.any(Function) });
|
||||
});
|
||||
|
||||
it('should do nothing if actions are disabled', () => {
|
||||
const args = inferActionsFromArgTypesRegex(({
|
||||
initialArgs: {},
|
||||
argTypes,
|
||||
parameters: {
|
||||
...parameters,
|
||||
actions: { ...parameters.actions, disable: true },
|
||||
},
|
||||
argTypes,
|
||||
} as unknown) as StoryContext);
|
||||
expect(args).toEqual({});
|
||||
});
|
||||
@ -48,16 +65,51 @@ describe('actions parameter enhancers', () => {
|
||||
};
|
||||
it('should add actions based on action.args', () => {
|
||||
expect(
|
||||
addActionsFromArgTypes(({ argTypes, parameters: {} } as unknown) as StoryContext)
|
||||
addActionsFromArgTypes(({
|
||||
initialArgs: {},
|
||||
argTypes,
|
||||
parameters: {},
|
||||
} as unknown) as StoryContext)
|
||||
).toEqual({
|
||||
onClick: expect.any(Function),
|
||||
onBlur: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT override pre-existing args', () => {
|
||||
expect(
|
||||
addActionsFromArgTypes(({
|
||||
argTypes: { onClick: { action: 'clicked!' } },
|
||||
initialArgs: { onClick: 'pre-existing value' },
|
||||
parameters: {},
|
||||
} as unknown) as StoryContext)
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should NOT override pre-existing args, if null', () => {
|
||||
expect(
|
||||
addActionsFromArgTypes(({
|
||||
argTypes: { onClick: { action: 'clicked!' } },
|
||||
initialArgs: { onClick: null },
|
||||
parameters: {},
|
||||
} as unknown) as StoryContext)
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it('should override pre-existing args, if undefined', () => {
|
||||
expect(
|
||||
addActionsFromArgTypes(({
|
||||
argTypes: { onClick: { action: 'clicked!' } },
|
||||
initialArgs: { onClick: undefined },
|
||||
parameters: {},
|
||||
} as unknown) as StoryContext)
|
||||
).toEqual({ onClick: expect.any(Function) });
|
||||
});
|
||||
|
||||
it('should do nothing if actions are disabled', () => {
|
||||
expect(
|
||||
addActionsFromArgTypes(({
|
||||
initialArgs: {},
|
||||
argTypes,
|
||||
parameters: { actions: { disable: true } },
|
||||
} as unknown) as StoryContext)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Args } from '@storybook/addons';
|
||||
import { ArgsEnhancer } from '@storybook/client-api';
|
||||
import { AnyFramework, ArgsEnhancer } from '@storybook/csf';
|
||||
import { action } from '../index';
|
||||
|
||||
// interface ActionsParameter {
|
||||
@ -12,10 +12,11 @@ import { action } from '../index';
|
||||
* matches a regex, such as `^on.*` for react-style `onClick` etc.
|
||||
*/
|
||||
|
||||
export const inferActionsFromArgTypesRegex: ArgsEnhancer = (context) => {
|
||||
export const inferActionsFromArgTypesRegex: ArgsEnhancer<AnyFramework> = (context) => {
|
||||
const {
|
||||
parameters: { actions },
|
||||
initialArgs,
|
||||
argTypes,
|
||||
parameters: { actions },
|
||||
} = context;
|
||||
if (!actions || actions.disable || !actions.argTypesRegex || !argTypes) {
|
||||
return {};
|
||||
@ -27,7 +28,9 @@ export const inferActionsFromArgTypesRegex: ArgsEnhancer = (context) => {
|
||||
);
|
||||
|
||||
return argTypesMatchingRegex.reduce((acc, [name, argType]) => {
|
||||
acc[name] = action(name);
|
||||
if (typeof initialArgs[name] === 'undefined') {
|
||||
acc[name] = action(name);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
};
|
||||
@ -35,8 +38,9 @@ export const inferActionsFromArgTypesRegex: ArgsEnhancer = (context) => {
|
||||
/**
|
||||
* Add action args for list of strings.
|
||||
*/
|
||||
export const addActionsFromArgTypes: ArgsEnhancer = (context) => {
|
||||
export const addActionsFromArgTypes: ArgsEnhancer<AnyFramework> = (context) => {
|
||||
const {
|
||||
initialArgs,
|
||||
argTypes,
|
||||
parameters: { actions },
|
||||
} = context;
|
||||
@ -47,7 +51,9 @@ export const addActionsFromArgTypes: ArgsEnhancer = (context) => {
|
||||
const argTypesWithAction = Object.entries(argTypes).filter(([name, argType]) => !!argType.action);
|
||||
|
||||
return argTypesWithAction.reduce((acc, [name, argType]) => {
|
||||
acc[name] = action(typeof argType.action === 'string' ? argType.action : name);
|
||||
if (typeof initialArgs[name] === 'undefined') {
|
||||
acc[name] = action(typeof argType.action === 'string' ? argType.action : name);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Args);
|
||||
};
|
||||
|
@ -1,10 +1,9 @@
|
||||
// Based on http://backbonejs.org/docs/backbone.html#section-164
|
||||
import global from 'global';
|
||||
import { useEffect } from '@storybook/client-api';
|
||||
import { useEffect, makeDecorator } from '@storybook/addons';
|
||||
import deprecate from 'util-deprecate';
|
||||
import dedent from 'ts-dedent';
|
||||
|
||||
import { makeDecorator } from '@storybook/addons';
|
||||
import { actions } from './actions';
|
||||
|
||||
import { PARAM_KEY } from '../constants';
|
||||
|
@ -4,7 +4,7 @@ Storybook Addon Backgrounds can be used to change background colors inside the p
|
||||
|
||||
[Framework Support](https://storybook.js.org/docs/react/api/frameworks-feature-support)
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
|
BIN
addons/backgrounds/docs/addon-backgrounds.gif
Normal file
After Width: | Height: | Size: 465 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-backgrounds",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Switch backgrounds to view components in different settings",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -10,7 +10,7 @@
|
||||
"essentials",
|
||||
"design"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/backgrounds",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/backgrounds",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -30,7 +30,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -45,12 +45,13 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/client-logger": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/client-logger": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"core-js": "^3.8.2",
|
||||
"global": "^4.4.0",
|
||||
"memoizerific": "^1.11.3",
|
||||
@ -76,7 +77,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Backgrounds",
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { StoryFn as StoryFunction, StoryContext, useMemo, useEffect } from '@storybook/addons';
|
||||
import { useMemo, useEffect } from '@storybook/addons';
|
||||
import { AnyFramework, PartialStoryFn as StoryFunction, StoryContext } from '@storybook/csf';
|
||||
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
import { clearStyles, addBackgroundStyle, getBackgroundColorByName } from '../helpers';
|
||||
import {
|
||||
clearStyles,
|
||||
addBackgroundStyle,
|
||||
getBackgroundColorByName,
|
||||
isReduceMotionEnabled,
|
||||
} from '../helpers';
|
||||
|
||||
export const withBackground = (StoryFn: StoryFunction, context: StoryContext) => {
|
||||
export const withBackground = (
|
||||
StoryFn: StoryFunction<AnyFramework>,
|
||||
context: StoryContext<AnyFramework>
|
||||
) => {
|
||||
const { globals, parameters } = context;
|
||||
const globalsBackgroundColor = globals[BACKGROUNDS_PARAM_KEY]?.value;
|
||||
const backgroundsConfig = parameters[BACKGROUNDS_PARAM_KEY];
|
||||
@ -29,10 +38,11 @@ export const withBackground = (StoryFn: StoryFunction, context: StoryContext) =>
|
||||
context.viewMode === 'docs' ? `#anchor--${context.id} .docs-story` : '.sb-show-main';
|
||||
|
||||
const backgroundStyles = useMemo(() => {
|
||||
const transitionStyle = 'transition: background-color 0.3s;';
|
||||
return `
|
||||
${selector} {
|
||||
background: ${selectedBackgroundColor} !important;
|
||||
transition: background-color 0.3s;
|
||||
${isReduceMotionEnabled() ? '' : transitionStyle}
|
||||
}
|
||||
`;
|
||||
}, [selectedBackgroundColor, selector]);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import dedent from 'ts-dedent';
|
||||
import deprecate from 'util-deprecate';
|
||||
import { StoryFn as StoryFunction, StoryContext, useMemo, useEffect } from '@storybook/addons';
|
||||
import { useMemo, useEffect } from '@storybook/addons';
|
||||
import { AnyFramework, PartialStoryFn as StoryFunction, StoryContext } from '@storybook/csf';
|
||||
|
||||
import { clearStyles, addGridStyle } from '../helpers';
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
@ -15,7 +16,10 @@ const deprecatedCellSizeWarning = deprecate(
|
||||
`
|
||||
);
|
||||
|
||||
export const withGrid = (StoryFn: StoryFunction, context: StoryContext) => {
|
||||
export const withGrid = (
|
||||
StoryFn: StoryFunction<AnyFramework>,
|
||||
context: StoryContext<AnyFramework>
|
||||
) => {
|
||||
const { globals, parameters } = context;
|
||||
const gridParameters = parameters[BACKGROUNDS_PARAM_KEY].grid;
|
||||
const isActive = globals[BACKGROUNDS_PARAM_KEY]?.grid === true && gridParameters.disable !== true;
|
||||
|
@ -5,7 +5,12 @@ import { logger } from '@storybook/client-logger';
|
||||
|
||||
import { Background } from '../types';
|
||||
|
||||
const { document } = global;
|
||||
const { document, window } = global;
|
||||
|
||||
export const isReduceMotionEnabled = () => {
|
||||
const prefersReduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
return prefersReduceMotion.matches;
|
||||
};
|
||||
|
||||
export const getBackgroundColorByName = (
|
||||
currentSelectedValue: string,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-controls",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Interact with component inputs dynamically in the Storybook UI",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -30,7 +30,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -45,13 +45,16 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/client-api": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/node-logger": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/client-logger": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/node-logger": "6.4.0-beta.3",
|
||||
"@storybook/store": "6.4.0-beta.3",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"core-js": "^3.8.2",
|
||||
"lodash": "^4.17.20",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -69,7 +72,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/register.js",
|
||||
"storybook": {
|
||||
"displayName": "Controls",
|
||||
|
@ -37,6 +37,7 @@ export const ensureDocsBeforeControls = (configDir: string) => {
|
||||
if (!verifyDocsBeforeControls(main.addons)) {
|
||||
logger.warn(dedent`
|
||||
Expected '@storybook/addon-docs' to be listed before '@storybook/addon-controls' (or '@storybook/addon-essentials'). Check your main.js?
|
||||
If addon-docs or addon-essentials is included by another addon/preset you can safely ignore this warning.
|
||||
|
||||
https://github.com/storybookjs/storybook/issues/11442
|
||||
`);
|
||||
|
1
addons/docs/angular/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from '../dist/ts3.9/frameworks/angular/index.d';
|
@ -17,6 +17,7 @@
|
||||
- [Reordering Docs tab first](#reordering-docs-tab-first)
|
||||
- [Customizing source snippets](#customizing-source-snippets)
|
||||
- [Overwriting docs container](#overwriting-docs-container)
|
||||
- [Add description to individual stories](#add-description-to-individual-stories)
|
||||
- [More resources](#more-resources)
|
||||
|
||||
## Component Story Format (CSF) with DocsPage
|
||||
@ -31,7 +32,7 @@ If you want to intersperse longform documentation in your Storybook, for example
|
||||
|
||||
## Mixed CSF / MDX Stories
|
||||
|
||||
Can't decide between CSF and MDX? In transition? Or have did you find that each format has its own use? There's nothing stopping you from keeping some of your stories in CSF and some in MDX. And if you want to migrate one way or another, the [csf-to-mdx and mdx-to-csf codemod migrations](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md) can help.
|
||||
Can't decide between CSF and MDX? In transition? Or have you found that each format has its own use? There's nothing stopping you from keeping some of your stories in CSF and some in MDX. And if you want to migrate one way or another, the [csf-to-mdx and mdx-to-csf codemod migrations](https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md) can help.
|
||||
|
||||
The only limitation is that your exported titles (CSF: `default.title`, MDX `Meta.title`) should be unique across files. Loading will fail if there are duplicate titles.
|
||||
|
||||
@ -336,6 +337,24 @@ import { theme } from '../path/to/theme'
|
||||
Rest of your file...
|
||||
```
|
||||
|
||||
## Add description to individual stories
|
||||
|
||||
Add `story` to `docs.description` parameter
|
||||
|
||||
```js
|
||||
const Example = () => <Button />;
|
||||
|
||||
Example.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Individual story description, may contain `markdown` markup"
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
There is also an webpack loader package that extracts descriptions from jsdoc comments [story-description-loader](https://www.npmjs.com/package/story-description-loader)
|
||||
|
||||
## More resources
|
||||
|
||||
- References: [README](../README.md) / [DocsPage](docspage.md) / [MDX](mdx.md) / [FAQ](faq.md) / [Recipes](recipes.md) / [Theming](theming.md) / [Props](props-tables.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-docs",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Document component usage and properties in Markdown",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -10,7 +10,7 @@
|
||||
"essentials",
|
||||
"organize"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/docs",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/docs",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -29,7 +29,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -63,20 +63,22 @@
|
||||
"@mdx-js/loader": "^1.6.22",
|
||||
"@mdx-js/mdx": "^1.6.22",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/builder-webpack4": "6.4.0-alpha.0",
|
||||
"@storybook/client-api": "6.4.0-alpha.0",
|
||||
"@storybook/client-logger": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/core": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/csf": "0.0.1",
|
||||
"@storybook/csf-tools": "6.4.0-alpha.0",
|
||||
"@storybook/node-logger": "6.4.0-alpha.0",
|
||||
"@storybook/postinstall": "6.4.0-alpha.0",
|
||||
"@storybook/source-loader": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/builder-webpack4": "6.4.0-beta.3",
|
||||
"@storybook/client-api": "6.4.0-beta.3",
|
||||
"@storybook/client-logger": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/core": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/csf-tools": "6.4.0-beta.3",
|
||||
"@storybook/node-logger": "6.4.0-beta.3",
|
||||
"@storybook/postinstall": "6.4.0-beta.3",
|
||||
"@storybook/preview-web": "6.4.0-beta.3",
|
||||
"@storybook/source-loader": "6.4.0-beta.3",
|
||||
"@storybook/store": "6.4.0-beta.3",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"acorn": "^7.4.1",
|
||||
"acorn-jsx": "^5.3.1",
|
||||
"acorn-walk": "^7.2.0",
|
||||
@ -89,8 +91,9 @@
|
||||
"js-string-escape": "^1.0.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"lodash": "^4.17.20",
|
||||
"nanoid": "^3.1.23",
|
||||
"p-limit": "^3.1.0",
|
||||
"prettier": "~2.2.1",
|
||||
"prettier": "^2.2.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-element-to-jsx-string": "^14.3.2",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
@ -104,10 +107,11 @@
|
||||
"@babel/core": "^7.12.10",
|
||||
"@emotion/core": "^10.1.1",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@storybook/angular": "6.4.0-alpha.0",
|
||||
"@storybook/react": "6.4.0-alpha.0",
|
||||
"@storybook/vue": "6.4.0-alpha.0",
|
||||
"@storybook/web-components": "6.4.0-alpha.0",
|
||||
"@storybook/angular": "6.4.0-beta.3",
|
||||
"@storybook/html": "6.4.0-beta.3",
|
||||
"@storybook/react": "6.4.0-beta.3",
|
||||
"@storybook/vue": "6.4.0-beta.3",
|
||||
"@storybook/web-components": "6.4.0-beta.3",
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"@types/doctrine": "^0.0.3",
|
||||
"@types/enzyme": "^3.10.8",
|
||||
@ -117,7 +121,7 @@
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/tmp": "^0.2.0",
|
||||
"@types/util-deprecate": "^1.0.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-react-docgen": "^4.2.1",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"fs-extra": "^9.0.1",
|
||||
@ -137,10 +141,12 @@
|
||||
"zone.js": "^0.11.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/angular": "6.4.0-alpha.0",
|
||||
"@storybook/vue": "6.4.0-alpha.0",
|
||||
"@storybook/vue3": "6.4.0-alpha.0",
|
||||
"@storybook/web-components": "6.4.0-alpha.0",
|
||||
"@storybook/angular": "6.4.0-beta.3",
|
||||
"@storybook/html": "6.4.0-beta.3",
|
||||
"@storybook/react": "6.4.0-beta.3",
|
||||
"@storybook/vue": "6.4.0-beta.3",
|
||||
"@storybook/vue3": "6.4.0-beta.3",
|
||||
"@storybook/web-components": "6.4.0-beta.3",
|
||||
"lit": "^2.0.0-rc.1",
|
||||
"lit-html": "^1.4.1 || ^2.0.0-rc.3",
|
||||
"react": "^16.8.0 || ^17.0.0",
|
||||
@ -154,6 +160,12 @@
|
||||
"@storybook/angular": {
|
||||
"optional": true
|
||||
},
|
||||
"@storybook/html": {
|
||||
"optional": true
|
||||
},
|
||||
"@storybook/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@storybook/vue": {
|
||||
"optional": true
|
||||
},
|
||||
@ -191,7 +203,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Docs",
|
||||
|
@ -1,24 +1,23 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import React, { FC, useContext, useEffect, useState, useCallback } from 'react';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import {
|
||||
ArgsTable as PureArgsTable,
|
||||
ArgsTableProps as PureArgsTableProps,
|
||||
ArgsTableError,
|
||||
ArgTypes,
|
||||
SortType,
|
||||
TabbedArgsTable,
|
||||
} from '@storybook/components';
|
||||
import { Args } from '@storybook/addons';
|
||||
import { StoryStore, filterArgTypes } from '@storybook/client-api';
|
||||
import type { PropDescriptor } from '@storybook/client-api';
|
||||
import { addons } from '@storybook/addons';
|
||||
import { filterArgTypes, PropDescriptor } from '@storybook/store';
|
||||
import Events from '@storybook/core-events';
|
||||
import { StrictArgTypes, Args } from '@storybook/csf';
|
||||
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { Component, CURRENT_SELECTION, PRIMARY_STORY } from './types';
|
||||
import { getComponentName, getDocsStories } from './utils';
|
||||
import { getComponentName } from './utils';
|
||||
import { ArgTypesExtractor } from '../lib/docgen/types';
|
||||
import { lookupStoryId } from './Story';
|
||||
import { useStory } from './useStory';
|
||||
|
||||
interface BaseProps {
|
||||
include?: PropDescriptor;
|
||||
@ -45,29 +44,33 @@ type ArgsTableProps = BaseProps | OfProps | ComponentsProps | StoryProps;
|
||||
|
||||
const useArgs = (
|
||||
storyId: string,
|
||||
storyStore: StoryStore
|
||||
context: DocsContextProps
|
||||
): [Args, (args: Args) => void, (argNames?: string[]) => void] => {
|
||||
const story = storyStore.fromId(storyId);
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const story = context.storyById(storyId);
|
||||
if (!story) {
|
||||
throw new Error(`Unknown story: ${storyId}`);
|
||||
}
|
||||
|
||||
const { args: initialArgs } = story;
|
||||
const [args, setArgs] = useState(initialArgs);
|
||||
const storyContext = context.getStoryContext(story);
|
||||
|
||||
const [args, setArgs] = useState(storyContext.args);
|
||||
useEffect(() => {
|
||||
const cb = (changed: { storyId: string; args: Args }) => {
|
||||
if (changed.storyId === storyId) {
|
||||
setArgs(changed.args);
|
||||
}
|
||||
};
|
||||
storyStore._channel.on(Events.STORY_ARGS_UPDATED, cb);
|
||||
return () => storyStore._channel.off(Events.STORY_ARGS_UPDATED, cb);
|
||||
channel.on(Events.STORY_ARGS_UPDATED, cb);
|
||||
return () => channel.off(Events.STORY_ARGS_UPDATED, cb);
|
||||
}, [storyId]);
|
||||
const updateArgs = useCallback((newArgs) => storyStore.updateStoryArgs(storyId, newArgs), [
|
||||
storyId,
|
||||
]);
|
||||
const updateArgs = useCallback(
|
||||
(updatedArgs) => channel.emit(Events.UPDATE_STORY_ARGS, { storyId, updatedArgs }),
|
||||
[storyId]
|
||||
);
|
||||
const resetArgs = useCallback(
|
||||
(argNames?: string[]) => storyStore.resetStoryArgs(storyId, argNames),
|
||||
(argNames?: string[]) => channel.emit(Events.RESET_STORY_ARGS, { storyId, argNames }),
|
||||
[storyId]
|
||||
);
|
||||
return [args, updateArgs, resetArgs];
|
||||
@ -75,12 +78,12 @@ const useArgs = (
|
||||
|
||||
export const extractComponentArgTypes = (
|
||||
component: Component,
|
||||
{ parameters }: DocsContextProps,
|
||||
{ id, storyById }: DocsContextProps,
|
||||
include?: PropDescriptor,
|
||||
exclude?: PropDescriptor
|
||||
): ArgTypes => {
|
||||
const params = parameters || {};
|
||||
const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = params.docs || {};
|
||||
): StrictArgTypes => {
|
||||
const { parameters } = storyById(id);
|
||||
const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = parameters.docs || {};
|
||||
if (!extractArgTypes) {
|
||||
throw new Error(ArgsTableError.ARGS_UNSUPPORTED);
|
||||
}
|
||||
@ -94,11 +97,13 @@ const isShortcut = (value?: string) => {
|
||||
return value && [CURRENT_SELECTION, PRIMARY_STORY].includes(value);
|
||||
};
|
||||
|
||||
export const getComponent = (props: ArgsTableProps = {}, context: DocsContextProps): Component => {
|
||||
export const getComponent = (
|
||||
props: ArgsTableProps = {},
|
||||
{ id, storyById }: DocsContextProps
|
||||
): Component => {
|
||||
const { of } = props as OfProps;
|
||||
const { story } = props as StoryProps;
|
||||
const { parameters = {} } = context;
|
||||
const { component } = parameters;
|
||||
const { component } = storyById(id);
|
||||
if (isShortcut(of) || isShortcut(story)) {
|
||||
return component || null;
|
||||
}
|
||||
@ -127,47 +132,51 @@ export const StoryTable: FC<
|
||||
StoryProps & { component: Component; subcomponents: Record<string, Component> }
|
||||
> = (props) => {
|
||||
const context = useContext(DocsContext);
|
||||
const { id: currentId, componentStories } = context;
|
||||
const {
|
||||
id: currentId,
|
||||
parameters: { argTypes },
|
||||
storyStore,
|
||||
} = context;
|
||||
const { story, component, subcomponents, showComponent, include, exclude, sort } = props;
|
||||
let storyArgTypes;
|
||||
story: storyName,
|
||||
component,
|
||||
subcomponents,
|
||||
showComponent,
|
||||
include,
|
||||
exclude,
|
||||
sort,
|
||||
} = props;
|
||||
try {
|
||||
let storyId;
|
||||
switch (story) {
|
||||
switch (storyName) {
|
||||
case CURRENT_SELECTION: {
|
||||
storyId = currentId;
|
||||
storyArgTypes = argTypes;
|
||||
break;
|
||||
}
|
||||
case PRIMARY_STORY: {
|
||||
const primaryStory = getDocsStories(context)[0];
|
||||
const primaryStory = componentStories()[0];
|
||||
storyId = primaryStory.id;
|
||||
storyArgTypes = primaryStory.parameters.argTypes;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
storyId = lookupStoryId(story, context);
|
||||
const data = storyStore.fromId(storyId);
|
||||
storyArgTypes = data.parameters.argTypes;
|
||||
storyId = lookupStoryId(storyName, context);
|
||||
}
|
||||
}
|
||||
storyArgTypes = filterArgTypes(storyArgTypes, include, exclude);
|
||||
|
||||
const story = useStory(storyId, context);
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [args, updateArgs, resetArgs] = useArgs(storyId, context);
|
||||
if (!story) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const argTypes = filterArgTypes(story.argTypes, include, exclude);
|
||||
|
||||
const mainLabel = getComponentName(component) || 'Story';
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [args, updateArgs, resetArgs] = useArgs(storyId, storyStore);
|
||||
let tabs = { [mainLabel]: { rows: storyArgTypes, args, updateArgs, resetArgs } } as Record<
|
||||
let tabs = { [mainLabel]: { rows: argTypes, args, updateArgs, resetArgs } } as Record<
|
||||
string,
|
||||
PureArgsTableProps
|
||||
>;
|
||||
|
||||
// Use the dynamically generated component tabs if there are no controls
|
||||
const storyHasArgsWithControls =
|
||||
storyArgTypes && Object.values(storyArgTypes).find((v) => !!v?.control);
|
||||
const storyHasArgsWithControls = argTypes && Object.values(argTypes).find((v) => !!v?.control);
|
||||
|
||||
if (!storyHasArgsWithControls) {
|
||||
updateArgs = null;
|
||||
@ -203,15 +212,19 @@ export const ComponentsTable: FC<ComponentsProps> = (props) => {
|
||||
|
||||
export const ArgsTable: FC<ArgsTableProps> = (props) => {
|
||||
const context = useContext(DocsContext);
|
||||
const { parameters: { subcomponents, controls } = {} } = context;
|
||||
const { id, storyById } = context;
|
||||
const {
|
||||
parameters: { controls },
|
||||
subcomponents,
|
||||
} = storyById(id);
|
||||
|
||||
const { include, exclude, components, sort: sortProp } = props as ComponentsProps;
|
||||
const { story } = props as StoryProps;
|
||||
const { story: storyName } = props as StoryProps;
|
||||
|
||||
const sort = sortProp || controls?.sort;
|
||||
|
||||
const main = getComponent(props, context);
|
||||
if (story) {
|
||||
if (storyName) {
|
||||
return <StoryTable {...(props as StoryProps)} component={main} {...{ subcomponents, sort }} />;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { FC, ReactElement, ReactNode, ReactNodeArray, useContext } from 'react';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { toId, storyNameFromExport } from '@storybook/csf';
|
||||
import { toId, storyNameFromExport, AnyFramework } from '@storybook/csf';
|
||||
import {
|
||||
resetComponents,
|
||||
Preview as PurePreview,
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { SourceContext, SourceContextProps } from './SourceContainer';
|
||||
import { getSourceProps, SourceState } from './Source';
|
||||
import { useStories } from './useStory';
|
||||
|
||||
export { SourceState };
|
||||
|
||||
@ -19,48 +20,57 @@ type CanvasProps = PurePreviewProps & {
|
||||
|
||||
const getPreviewProps = (
|
||||
{ withSource, mdxSource, children, ...props }: CanvasProps & { children?: ReactNode },
|
||||
docsContext: DocsContextProps,
|
||||
docsContext: DocsContextProps<AnyFramework>,
|
||||
sourceContext: SourceContextProps
|
||||
): PurePreviewProps => {
|
||||
const { mdxComponentMeta, mdxStoryNameToKey } = docsContext;
|
||||
) => {
|
||||
const { mdxComponentAnnotations, mdxStoryNameToKey } = docsContext;
|
||||
let sourceState = withSource;
|
||||
let isLoading = false;
|
||||
if (sourceState === SourceState.NONE) {
|
||||
return props;
|
||||
return { isLoading, previewProps: props };
|
||||
}
|
||||
if (mdxSource) {
|
||||
return {
|
||||
...props,
|
||||
withSource: getSourceProps({ code: decodeURI(mdxSource) }, docsContext, sourceContext),
|
||||
isLoading,
|
||||
previewProps: {
|
||||
...props,
|
||||
withSource: getSourceProps({ code: decodeURI(mdxSource) }, docsContext, sourceContext),
|
||||
},
|
||||
};
|
||||
}
|
||||
const childArray: ReactNodeArray = Array.isArray(children) ? children : [children];
|
||||
const stories = childArray.filter(
|
||||
const storyChildren = childArray.filter(
|
||||
(c: ReactElement) => c.props && (c.props.id || c.props.name)
|
||||
) as ReactElement[];
|
||||
const targetIds = stories.map(
|
||||
const targetIds = storyChildren.map(
|
||||
(s) =>
|
||||
s.props.id ||
|
||||
toId(
|
||||
mdxComponentMeta.id || mdxComponentMeta.title,
|
||||
mdxComponentAnnotations.id || mdxComponentAnnotations.title,
|
||||
storyNameFromExport(mdxStoryNameToKey[s.props.name])
|
||||
)
|
||||
);
|
||||
const sourceProps = getSourceProps({ ids: targetIds }, docsContext, sourceContext);
|
||||
if (!sourceState) sourceState = sourceProps.state;
|
||||
const stories = useStories(targetIds, docsContext);
|
||||
isLoading = stories.some((s) => !s);
|
||||
|
||||
return {
|
||||
...props, // pass through columns etc.
|
||||
withSource: sourceProps,
|
||||
isExpanded: sourceState === SourceState.OPEN,
|
||||
isLoading,
|
||||
previewProps: {
|
||||
...props, // pass through columns etc.
|
||||
withSource: sourceProps,
|
||||
isExpanded: sourceState === SourceState.OPEN,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const Canvas: FC<CanvasProps> = (props) => {
|
||||
const docsContext = useContext(DocsContext);
|
||||
const sourceContext = useContext(SourceContext);
|
||||
const previewProps = getPreviewProps(props, docsContext, sourceContext);
|
||||
const { isLoading, previewProps } = getPreviewProps(props, docsContext, sourceContext);
|
||||
const { children } = props;
|
||||
return (
|
||||
return isLoading ? null : (
|
||||
<MDXProvider components={resetComponents}>
|
||||
<PurePreview {...previewProps}>{children}</PurePreview>
|
||||
</MDXProvider>
|
||||
|
@ -31,12 +31,13 @@ const noDescription = (component?: Component): string | null => null;
|
||||
|
||||
export const getDescriptionProps = (
|
||||
{ of, type, markdown, children }: DescriptionProps,
|
||||
{ parameters }: DocsContextProps
|
||||
{ id, storyById }: DocsContextProps<any>
|
||||
): PureDescriptionProps => {
|
||||
const { component, parameters } = storyById(id);
|
||||
if (children || markdown) {
|
||||
return { markdown: children || markdown };
|
||||
}
|
||||
const { component, notes, info, docs } = parameters;
|
||||
const { notes, info, docs } = parameters;
|
||||
const { extractComponentDescription = noDescription, description } = docs || {};
|
||||
const target = of === CURRENT_SELECTION ? component : of;
|
||||
|
||||
@ -63,7 +64,7 @@ ${extractComponentDescription(target) || ''}
|
||||
case DescriptionType.DOCGEN:
|
||||
case DescriptionType.AUTO:
|
||||
default:
|
||||
return { markdown: extractComponentDescription(target, parameters) };
|
||||
return { markdown: extractComponentDescription(target, { component, ...parameters }) };
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,7 @@ import dedent from 'ts-dedent';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { ThemeProvider, ensure as ensureTheme } from '@storybook/theming';
|
||||
import { DocsWrapper, DocsContent, components as htmlComponents } from '@storybook/components';
|
||||
import { AnyFramework } from '@storybook/csf';
|
||||
import { DocsContextProps, DocsContext } from './DocsContext';
|
||||
import { anchorBlockIdFromId } from './Anchor';
|
||||
import { storyBlockIdFromId } from './Story';
|
||||
@ -14,8 +15,8 @@ import { scrollToElement } from './utils';
|
||||
|
||||
const { document, window: globalWindow } = global;
|
||||
|
||||
export interface DocsContainerProps {
|
||||
context: DocsContextProps;
|
||||
export interface DocsContainerProps<TFramework extends AnyFramework = AnyFramework> {
|
||||
context: DocsContextProps<TFramework>;
|
||||
}
|
||||
|
||||
const defaultComponents = {
|
||||
@ -35,8 +36,10 @@ const warnOptionsTheme = deprecate(
|
||||
);
|
||||
|
||||
export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context, children }) => {
|
||||
const { id: storyId = null, parameters = {} } = context || {};
|
||||
const { options = {}, docs = {} } = parameters;
|
||||
const { id: storyId, storyById } = context;
|
||||
const {
|
||||
parameters: { options = {}, docs = {} },
|
||||
} = storyById(storyId);
|
||||
let themeVars = docs.theme;
|
||||
if (!themeVars && options.theme) {
|
||||
warnOptionsTheme();
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { Context, createContext } from 'react';
|
||||
import { window as globalWindow } from 'global';
|
||||
|
||||
export interface DocsContextProps {
|
||||
id?: string;
|
||||
kind?: string;
|
||||
name?: string;
|
||||
import { DocsContextProps } from '@storybook/preview-web';
|
||||
import { AnyFramework } from '@storybook/csf';
|
||||
|
||||
/**
|
||||
* mdxStoryNameToKey is an MDX-compiler-generated mapping of an MDX story's
|
||||
* display name to its story key for ID generation. It's used internally by the `<Story>`
|
||||
* and `Preview` doc blocks.
|
||||
*/
|
||||
mdxStoryNameToKey?: Record<string, string>;
|
||||
mdxComponentMeta?: any;
|
||||
parameters?: any;
|
||||
storyStore?: any;
|
||||
forceRender?: () => void;
|
||||
export type { DocsContextProps };
|
||||
|
||||
// We add DocsContext to window. The reason is that in case DocsContext.ts is
|
||||
// imported multiple times (maybe once directly, and another time from a minified bundle)
|
||||
// we will have multiple DocsContext definitions - leading to lost context in
|
||||
// the React component tree.
|
||||
// This was specifically a problem with the Vite builder.
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
if (globalWindow.__DOCS_CONTEXT__ === undefined) {
|
||||
globalWindow.__DOCS_CONTEXT__ = createContext({});
|
||||
globalWindow.__DOCS_CONTEXT__.displayName = 'DocsContext';
|
||||
}
|
||||
|
||||
export const DocsContext: Context<DocsContextProps> = createContext({});
|
||||
export const DocsContext: Context<DocsContextProps<AnyFramework>> = globalWindow.__DOCS_CONTEXT__;
|
||||
|
@ -2,9 +2,8 @@ import { extractTitle } from './Title';
|
||||
|
||||
describe('defaultTitleSlot', () => {
|
||||
it('splits on last /', () => {
|
||||
const parameters = {};
|
||||
expect(extractTitle({ kind: 'a/b/c', parameters })).toBe('c');
|
||||
expect(extractTitle({ kind: 'a|b', parameters })).toBe('a|b');
|
||||
expect(extractTitle({ kind: 'a/b/c.d', parameters })).toBe('c.d');
|
||||
expect(extractTitle({ title: 'a/b/c' } as any)).toBe('c');
|
||||
expect(extractTitle({ title: 'a|b' } as any)).toBe('a|b');
|
||||
expect(extractTitle({ title: 'a/b/c.d' } as any)).toBe('c.d');
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,15 @@
|
||||
import React, { FC, useContext } from 'react';
|
||||
import global from 'global';
|
||||
import { Args, BaseAnnotations, BaseMeta } from '@storybook/addons';
|
||||
import { BaseAnnotations } from '@storybook/csf';
|
||||
import { Anchor } from './Anchor';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { getDocsStories } from './utils';
|
||||
import { Component } from './types';
|
||||
|
||||
const { document } = global;
|
||||
|
||||
type MetaProps = BaseMeta<Component> & BaseAnnotations<Args, any>;
|
||||
type MetaProps = BaseAnnotations;
|
||||
|
||||
function getFirstStoryId(docsContext: DocsContextProps): string {
|
||||
const stories = getDocsStories(docsContext);
|
||||
const stories = docsContext.componentStories();
|
||||
|
||||
return stories.length > 0 ? stories[0].id : null;
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import React, { useContext, FC } from 'react';
|
||||
import { Story } from '@storybook/store';
|
||||
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { DocsStory } from './DocsStory';
|
||||
import { getDocsStories } from './utils';
|
||||
|
||||
interface PrimaryProps {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export const Primary: FC<PrimaryProps> = ({ name }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const componentStories = getDocsStories(context);
|
||||
const { componentStories: getComponentStories } = useContext(DocsContext);
|
||||
const componentStories = getComponentStories();
|
||||
let story;
|
||||
if (componentStories) {
|
||||
story = name ? componentStories.find((s) => s.name === name) : componentStories[0];
|
||||
story = name ? componentStories.find((s: Story<any>) => s.name === name) : componentStories[0];
|
||||
}
|
||||
return story ? <DocsStory {...story} expanded={false} withToolbar /> : null;
|
||||
};
|
||||
|
@ -5,8 +5,7 @@ import {
|
||||
SourceProps as PureSourceProps,
|
||||
} from '@storybook/components';
|
||||
import { StoryId } from '@storybook/api';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { StoryContext } from '@storybook/addons';
|
||||
import { Story } from '@storybook/store';
|
||||
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { SourceContext, SourceContextProps } from './SourceContainer';
|
||||
@ -14,6 +13,7 @@ import { CURRENT_SELECTION } from './types';
|
||||
import { SourceType } from '../shared';
|
||||
|
||||
import { enhanceSource } from './enhanceSource';
|
||||
import { useStories } from './useStory';
|
||||
|
||||
export enum SourceState {
|
||||
OPEN = 'open',
|
||||
@ -43,28 +43,8 @@ type NoneProps = CommonProps;
|
||||
|
||||
type SourceProps = SingleSourceProps | MultiSourceProps | CodeProps | NoneProps;
|
||||
|
||||
const getStoryContext = (storyId: StoryId, docsContext: DocsContextProps): StoryContext | null => {
|
||||
const { storyStore } = docsContext;
|
||||
const storyContext = storyStore?.fromId(storyId);
|
||||
|
||||
if (!storyContext) {
|
||||
// Fallback if we can't get the story data for this story
|
||||
logger.warn(`Unable to find information for story ID '${storyId}'`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return storyContext;
|
||||
};
|
||||
|
||||
const getSourceState = (storyIds: string[], docsContext: DocsContextProps) => {
|
||||
const states = storyIds
|
||||
.map((storyId) => {
|
||||
const storyContext = getStoryContext(storyId, docsContext);
|
||||
if (!storyContext) return null;
|
||||
return storyContext.parameters.docs?.source?.state;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const getSourceState = (stories: Story[]) => {
|
||||
const states = stories.map((story) => story.parameters.docs?.source?.state).filter(Boolean);
|
||||
if (states.length === 0) return SourceState.CLOSED;
|
||||
// FIXME: handling multiple stories is a pain
|
||||
return states[0];
|
||||
@ -77,12 +57,12 @@ const getStorySource = (storyId: StoryId, sourceContext: SourceContextProps): st
|
||||
return sources?.[storyId] || '';
|
||||
};
|
||||
|
||||
const getSnippet = (snippet: string, storyContext?: StoryContext): string => {
|
||||
if (!storyContext) {
|
||||
const getSnippet = (snippet: string, story?: Story<any>): string => {
|
||||
if (!story) {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
const { parameters } = storyContext;
|
||||
const { parameters } = story;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const isArgsStory = parameters.__isArgsStory;
|
||||
const type = parameters.docs?.source?.type || SourceType.AUTO;
|
||||
@ -95,16 +75,16 @@ const getSnippet = (snippet: string, storyContext?: StoryContext): string => {
|
||||
|
||||
// if user has explicitly set this as dynamic, use snippet
|
||||
if (type === SourceType.DYNAMIC) {
|
||||
return parameters.docs?.transformSource?.(snippet, storyContext) || snippet;
|
||||
return parameters.docs?.transformSource?.(snippet, story) || snippet;
|
||||
}
|
||||
|
||||
// if this is an args story and there's a snippet
|
||||
if (type === SourceType.AUTO && snippet && isArgsStory) {
|
||||
return parameters.docs?.transformSource?.(snippet, storyContext) || snippet;
|
||||
return parameters.docs?.transformSource?.(snippet, story) || snippet;
|
||||
}
|
||||
|
||||
// otherwise, use the source code logic
|
||||
const enhanced = enhanceSource(storyContext) || parameters;
|
||||
const enhanced = enhanceSource(story) || parameters;
|
||||
return enhanced?.docs?.source?.code || '';
|
||||
};
|
||||
|
||||
@ -112,10 +92,11 @@ type SourceStateProps = { state: SourceState };
|
||||
|
||||
export const getSourceProps = (
|
||||
props: SourceProps,
|
||||
docsContext: DocsContextProps,
|
||||
docsContext: DocsContextProps<any>,
|
||||
sourceContext: SourceContextProps
|
||||
): PureSourceProps & SourceStateProps => {
|
||||
const { id: currentId, parameters = {} } = docsContext;
|
||||
const { id: currentId, storyById } = docsContext;
|
||||
const { parameters } = storyById(currentId);
|
||||
|
||||
const codeProps = props as CodeProps;
|
||||
const singleProps = props as SingleSourceProps;
|
||||
@ -123,21 +104,27 @@ export const getSourceProps = (
|
||||
|
||||
let source = codeProps.code; // prefer user-specified code
|
||||
|
||||
const targetId =
|
||||
singleProps.id === CURRENT_SELECTION || !singleProps.id ? currentId : singleProps.id;
|
||||
const targetIds = multiProps.ids || [targetId];
|
||||
const targetIds = multiProps.ids || [singleProps.id || currentId];
|
||||
const storyIds = targetIds.map((targetId) =>
|
||||
targetId === CURRENT_SELECTION ? currentId : targetId
|
||||
);
|
||||
|
||||
const stories = useStories(storyIds, docsContext);
|
||||
if (!stories.every(Boolean)) {
|
||||
return { error: SourceError.SOURCE_UNAVAILABLE, state: SourceState.NONE };
|
||||
}
|
||||
|
||||
if (!source) {
|
||||
source = targetIds
|
||||
.map((storyId) => {
|
||||
source = storyIds
|
||||
.map((storyId, idx) => {
|
||||
const storySource = getStorySource(storyId, sourceContext);
|
||||
const storyContext = getStoryContext(storyId, docsContext);
|
||||
return getSnippet(storySource, storyContext);
|
||||
const storyObj = stories[idx] as Story;
|
||||
return getSnippet(storySource, storyObj);
|
||||
})
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
const state = getSourceState(targetIds, docsContext);
|
||||
const state = getSourceState(stories as Story[]);
|
||||
|
||||
const { docs: docsParameters = {} } = parameters;
|
||||
const { source: sourceParameters = {} } = docsParameters;
|
||||
|
@ -18,24 +18,21 @@ export const SourceContainer: FC<{}> = ({ children }) => {
|
||||
const [sources, setSources] = useState<StorySources>({});
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const sourcesRef = React.useRef<StorySources>();
|
||||
const handleSnippetRendered = (id: StoryId, newItem: SourceItem) => {
|
||||
if (newItem !== sources[id]) {
|
||||
const newSources = { ...sourcesRef.current, [id]: newItem };
|
||||
sourcesRef.current = newSources;
|
||||
}
|
||||
};
|
||||
|
||||
// Bind this early (instead of inside `useEffect`), because the `SNIPPET_RENDERED` event
|
||||
// is triggered *during* the rendering process, not after. We have to use the ref
|
||||
// to ensure we don't end up calling setState outside the effect though.
|
||||
channel.on(SNIPPET_RENDERED, handleSnippetRendered);
|
||||
|
||||
useEffect(() => {
|
||||
const current = sourcesRef.current || {};
|
||||
if (!deepEqual(sources, current)) {
|
||||
setSources(current);
|
||||
}
|
||||
const handleSnippetRendered = (id: StoryId, newItem: SourceItem) => {
|
||||
if (newItem !== sources[id]) {
|
||||
setSources((current) => {
|
||||
const newSources = { ...current, [id]: newItem };
|
||||
|
||||
if (!deepEqual(current, newSources)) {
|
||||
return newSources;
|
||||
}
|
||||
return current;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
channel.on(SNIPPET_RENDERED, handleSnippetRendered);
|
||||
|
||||
return () => channel.off(SNIPPET_RENDERED, handleSnippetRendered);
|
||||
});
|
||||
|
@ -2,7 +2,6 @@ import React, { useContext, FunctionComponent } from 'react';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { DocsStory } from './DocsStory';
|
||||
import { Heading } from './Heading';
|
||||
import { getDocsStories } from './utils';
|
||||
import { DocsStoryProps } from './types';
|
||||
|
||||
interface StoriesProps {
|
||||
@ -11,10 +10,9 @@ interface StoriesProps {
|
||||
}
|
||||
|
||||
export const Stories: FunctionComponent<StoriesProps> = ({ title, includePrimary = false }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const componentStories = getDocsStories(context);
|
||||
const { componentStories } = useContext(DocsContext);
|
||||
|
||||
let stories: DocsStoryProps[] = componentStories;
|
||||
let stories: DocsStoryProps[] = componentStories();
|
||||
if (!includePrimary) stories = stories.slice(1);
|
||||
|
||||
if (!stories || stories.length === 0) {
|
||||
|
@ -1,17 +1,28 @@
|
||||
import React, { FunctionComponent, ReactNode, ElementType, ComponentProps } from 'react';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactNode,
|
||||
ElementType,
|
||||
ComponentProps,
|
||||
useContext,
|
||||
useRef,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { resetComponents, Story as PureStory } from '@storybook/components';
|
||||
import { toId, storyNameFromExport } from '@storybook/csf';
|
||||
import { Args, BaseAnnotations } from '@storybook/addons';
|
||||
import { CURRENT_SELECTION } from './types';
|
||||
import { StoryId, toId, storyNameFromExport, StoryAnnotations, AnyFramework } from '@storybook/csf';
|
||||
import { Story as StoryType } from '@storybook/store';
|
||||
import global from 'global';
|
||||
|
||||
import { CURRENT_SELECTION } from './types';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { useStory } from './useStory';
|
||||
|
||||
export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;
|
||||
|
||||
type PureStoryProps = ComponentProps<typeof PureStory>;
|
||||
|
||||
type CommonProps = BaseAnnotations<Args, any> & {
|
||||
type CommonProps = StoryAnnotations & {
|
||||
height?: string;
|
||||
inline?: boolean;
|
||||
};
|
||||
@ -34,22 +45,26 @@ export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & Co
|
||||
|
||||
export const lookupStoryId = (
|
||||
storyName: string,
|
||||
{ mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps
|
||||
{ mdxStoryNameToKey, mdxComponentAnnotations }: DocsContextProps
|
||||
) =>
|
||||
toId(
|
||||
mdxComponentMeta.id || mdxComponentMeta.title,
|
||||
mdxComponentAnnotations.id || mdxComponentAnnotations.title,
|
||||
storyNameFromExport(mdxStoryNameToKey[storyName])
|
||||
);
|
||||
|
||||
export const getStoryProps = (props: StoryProps, context: DocsContextProps): PureStoryProps => {
|
||||
export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryId => {
|
||||
const { id } = props as StoryRefProps;
|
||||
const { name } = props as StoryDefProps;
|
||||
const inputId = id === CURRENT_SELECTION ? context.id : id;
|
||||
const previewId = inputId || lookupStoryId(name, context);
|
||||
const data = context.storyStore.fromId(previewId) || {};
|
||||
return inputId || lookupStoryId(name, context);
|
||||
};
|
||||
|
||||
const { height, inline } = props;
|
||||
const { storyFn = undefined, name: storyName = undefined, parameters = {} } = data;
|
||||
export const getStoryProps = <TFramework extends AnyFramework>(
|
||||
{ height, inline }: StoryProps,
|
||||
story: StoryType<TFramework>,
|
||||
context: DocsContextProps<TFramework>
|
||||
): PureStoryProps => {
|
||||
const { name: storyName, parameters } = story;
|
||||
const { docs = {} } = parameters;
|
||||
|
||||
if (docs.disable) {
|
||||
@ -65,33 +80,100 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur
|
||||
);
|
||||
}
|
||||
|
||||
const boundStoryFn = () =>
|
||||
story.unboundStoryFn({
|
||||
...context.getStoryContext(story),
|
||||
loaded: {},
|
||||
});
|
||||
return {
|
||||
parameters,
|
||||
inline: storyIsInline,
|
||||
id: previewId,
|
||||
storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn, data) : storyFn,
|
||||
id: story.id,
|
||||
height: height || (storyIsInline ? undefined : iframeHeight),
|
||||
title: storyName,
|
||||
...(storyIsInline && {
|
||||
parameters,
|
||||
storyFn: () => prepareForInline(boundStoryFn, story),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const Story: FunctionComponent<StoryProps> = (props) => (
|
||||
<DocsContext.Consumer>
|
||||
{(context) => {
|
||||
const storyProps = getStoryProps(props, context);
|
||||
if (!storyProps) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div id={storyBlockIdFromId(storyProps.id)}>
|
||||
<MDXProvider components={resetComponents}>
|
||||
<PureStory {...storyProps} />
|
||||
</MDXProvider>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
const Story: FunctionComponent<StoryProps> = (props) => {
|
||||
const context = useContext(DocsContext);
|
||||
const ref = useRef();
|
||||
const story = useStory(getStoryId(props, context), context);
|
||||
|
||||
// Ensure we wait until this story is properly rendered in the docs context.
|
||||
// The purpose of this is to ensure that that the `DOCS_RENDERED` event isn't emitted
|
||||
// until all stories on the page have rendered.
|
||||
const { id: storyId, registerRenderingStory } = context;
|
||||
const storyRendered = useMemo(registerRenderingStory, [storyId]);
|
||||
useEffect(() => {
|
||||
if (story) storyRendered();
|
||||
}, [story]);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanup: () => void;
|
||||
if (story && ref.current) {
|
||||
const { componentId, id, title, name } = story;
|
||||
const renderContext = {
|
||||
componentId,
|
||||
title,
|
||||
kind: title,
|
||||
id,
|
||||
name,
|
||||
story: name,
|
||||
// TODO what to do when these fail?
|
||||
showMain: () => {},
|
||||
showError: () => {},
|
||||
showException: () => {},
|
||||
};
|
||||
cleanup = context.renderStoryToElement({
|
||||
story,
|
||||
renderContext,
|
||||
element: ref.current as Element,
|
||||
});
|
||||
}
|
||||
return () => cleanup && cleanup();
|
||||
}, [story]);
|
||||
|
||||
if (!story) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const storyProps = getStoryProps(props, story, context);
|
||||
if (!storyProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (global?.FEATURES?.modernInlineRender) {
|
||||
// We do this so React doesn't complain when we replace the span in a secondary render
|
||||
const htmlContents = `<span data-is-loading-indicator="true">loading story...</span>`;
|
||||
|
||||
// FIXME: height/style/etc. lifted from PureStory
|
||||
const { height } = storyProps;
|
||||
return (
|
||||
<div id={storyBlockIdFromId(story.id)}>
|
||||
<MDXProvider components={resetComponents}>
|
||||
{height ? (
|
||||
<style>{`#story--${story.id} { min-height: ${height}; transform: translateZ(0); overflow: auto }`}</style>
|
||||
) : null}
|
||||
<div
|
||||
ref={ref}
|
||||
data-name={story.name}
|
||||
dangerouslySetInnerHTML={{ __html: htmlContents }}
|
||||
/>
|
||||
</MDXProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id={storyBlockIdFromId(story.id)}>
|
||||
<MDXProvider components={resetComponents}>
|
||||
<PureStory {...storyProps} />
|
||||
</MDXProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Story.defaultProps = {
|
||||
children: null,
|
||||
|
@ -7,8 +7,8 @@ interface SubtitleProps {
|
||||
}
|
||||
|
||||
export const Subtitle: FunctionComponent<SubtitleProps> = ({ children }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const { parameters } = context;
|
||||
const { id, storyById } = useContext(DocsContext);
|
||||
const { parameters } = storyById(id);
|
||||
let text: JSX.Element | string = children;
|
||||
if (!text) {
|
||||
text = parameters?.componentSubtitle;
|
||||
|
@ -8,9 +8,9 @@ interface TitleProps {
|
||||
|
||||
const STORY_KIND_PATH_SEPARATOR = /\s*\/\s*/;
|
||||
|
||||
export const extractTitle = ({ kind }: DocsContextProps) => {
|
||||
const groups = kind.trim().split(STORY_KIND_PATH_SEPARATOR);
|
||||
return (groups && groups[groups.length - 1]) || kind;
|
||||
export const extractTitle = ({ title }: DocsContextProps) => {
|
||||
const groups = title.trim().split(STORY_KIND_PATH_SEPARATOR);
|
||||
return (groups && groups[groups.length - 1]) || title;
|
||||
};
|
||||
|
||||
export const Title: FunctionComponent<TitleProps> = ({ children }) => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { combineParameters } from '@storybook/client-api';
|
||||
import { StoryContext, Parameters } from '@storybook/addons';
|
||||
import { Parameters } from '@storybook/addons';
|
||||
import { Story } from '@storybook/store';
|
||||
|
||||
// ============================================================
|
||||
// START @storybook/source-loader/extract-source
|
||||
@ -76,8 +77,8 @@ const extract = (targetId: string, { source, locationsMap }: StorySource) => {
|
||||
return extractSource(location, lines);
|
||||
};
|
||||
|
||||
export const enhanceSource = (context: StoryContext): Parameters => {
|
||||
const { id, parameters } = context;
|
||||
export const enhanceSource = (story: Story<any>): Parameters => {
|
||||
const { id, parameters } = story;
|
||||
const { storySource, docs = {} } = parameters;
|
||||
const { transformSource } = docs;
|
||||
|
||||
@ -87,7 +88,7 @@ export const enhanceSource = (context: StoryContext): Parameters => {
|
||||
}
|
||||
|
||||
const input = extract(id, storySource);
|
||||
const code = transformSource ? transformSource(input, context) : input;
|
||||
const code = transformSource ? transformSource(input, story) : input;
|
||||
|
||||
return { docs: combineParameters(docs, { source: { code } }) };
|
||||
};
|
||||
|
31
addons/docs/src/blocks/useStory.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { StoryId, AnyFramework } from '@storybook/csf';
|
||||
import { Story } from '@storybook/store';
|
||||
|
||||
import { DocsContextProps } from './DocsContext';
|
||||
|
||||
export function useStory<TFramework extends AnyFramework = AnyFramework>(
|
||||
storyId: StoryId,
|
||||
context: DocsContextProps<TFramework>
|
||||
): Story<TFramework> | void {
|
||||
const stories = useStories([storyId], context);
|
||||
return stories && stories[0];
|
||||
}
|
||||
|
||||
export function useStories<TFramework extends AnyFramework = AnyFramework>(
|
||||
storyIds: StoryId[],
|
||||
context: DocsContextProps<TFramework>
|
||||
): (Story<TFramework> | void)[] {
|
||||
const [storiesById, setStories] = useState({} as Record<StoryId, Story<TFramework>>);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all(
|
||||
storyIds.map(async (storyId) => {
|
||||
const story = await context.loadStory(storyId);
|
||||
setStories((current) => ({ ...current, [storyId]: story }));
|
||||
})
|
||||
);
|
||||
}, storyIds);
|
||||
|
||||
return storyIds.map((storyId) => storiesById[storyId]);
|
||||
}
|
@ -1,18 +1,5 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { DocsContextProps } from './DocsContext';
|
||||
import { StoryData, Component } from './types';
|
||||
|
||||
export const getDocsStories = (context: DocsContextProps): StoryData[] => {
|
||||
const { storyStore, kind } = context;
|
||||
|
||||
if (!storyStore) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return storyStore
|
||||
.getStoriesForKind(kind)
|
||||
.filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable));
|
||||
};
|
||||
import { Component } from './types';
|
||||
|
||||
const titleCase = (str: string): string =>
|
||||
str
|
||||
|
@ -8,38 +8,46 @@ Object {
|
||||
"name": "_inputValue",
|
||||
"table": Object {
|
||||
"category": "properties",
|
||||
"defaultValue": Object {
|
||||
"summary": "some value",
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "string",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"_value": Object {
|
||||
"defaultValue": "Private hello",
|
||||
"description": "<p>Private value. </p>
|
||||
",
|
||||
"description": "
|
||||
Private value.",
|
||||
"name": "_value",
|
||||
"table": Object {
|
||||
"category": "properties",
|
||||
"defaultValue": Object {
|
||||
"summary": "Private hello",
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "string",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"accent": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>Specify the accent-type of the button </p>
|
||||
",
|
||||
"description": "Specify the accent-type of the button",
|
||||
"name": "accent",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "ButtonAccent",
|
||||
@ -51,11 +59,13 @@ Object {
|
||||
},
|
||||
"appearance": Object {
|
||||
"defaultValue": "secondary",
|
||||
"description": "<p>Appearance style of the button. </p>
|
||||
",
|
||||
"description": "Appearance style of the button.",
|
||||
"name": "appearance",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": "secondary",
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "\\"primary\\" | \\"secondary\\"",
|
||||
@ -75,6 +85,9 @@ Object {
|
||||
"name": "buttonRef",
|
||||
"table": Object {
|
||||
"category": "view child",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "ElementRef",
|
||||
@ -86,11 +99,17 @@ Object {
|
||||
},
|
||||
"calc": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p>
|
||||
"description": "
|
||||
|
||||
An internal calculation method which adds \`x\` and \`y\` together.
|
||||
|
||||
",
|
||||
"name": "calc",
|
||||
"table": Object {
|
||||
"category": "methods",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": false,
|
||||
"summary": "(x: number, y: string | number) => number",
|
||||
@ -100,13 +119,33 @@ Object {
|
||||
"name": "void",
|
||||
},
|
||||
},
|
||||
"focus": Object {
|
||||
"defaultValue": false,
|
||||
"description": "",
|
||||
"name": "focus",
|
||||
"table": Object {
|
||||
"category": "properties",
|
||||
"defaultValue": Object {
|
||||
"summary": false,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "boolean",
|
||||
},
|
||||
},
|
||||
"inputValue": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p>
|
||||
",
|
||||
"description": "Setter for \`inputValue\` that is also an \`@Input\`.",
|
||||
"name": "inputValue",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "string",
|
||||
@ -118,30 +157,35 @@ Object {
|
||||
},
|
||||
"internalProperty": Object {
|
||||
"defaultValue": "Public hello",
|
||||
"description": "<p>Public value. </p>
|
||||
",
|
||||
"description": "
|
||||
Public value.",
|
||||
"name": "internalProperty",
|
||||
"table": Object {
|
||||
"category": "properties",
|
||||
"defaultValue": Object {
|
||||
"summary": "Public hello",
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "string",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"isDisabled": Object {
|
||||
"defaultValue": false,
|
||||
"description": "<p>Sets the button to a disabled state. </p>
|
||||
",
|
||||
"description": "Sets the button to a disabled state.",
|
||||
"name": "isDisabled",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": false,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": undefined,
|
||||
"summary": "boolean",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
@ -154,6 +198,9 @@ Object {
|
||||
"name": "item",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "[]",
|
||||
@ -165,11 +212,13 @@ Object {
|
||||
},
|
||||
"label": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>The inner text of the button.</p>
|
||||
",
|
||||
"description": "The inner text of the button.",
|
||||
"name": "label",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "string",
|
||||
@ -182,12 +231,18 @@ Object {
|
||||
"onClick": Object {
|
||||
"action": "onClick",
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>Handler to be called when the button is clicked by a user.</p>
|
||||
<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p>
|
||||
"description": "
|
||||
|
||||
Handler to be called when the button is clicked by a user.
|
||||
|
||||
Will also block the emission of the event if \`isDisabled\` is true.
|
||||
",
|
||||
"name": "onClick",
|
||||
"table": Object {
|
||||
"category": "outputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "EventEmitter",
|
||||
@ -197,13 +252,37 @@ Object {
|
||||
"name": "void",
|
||||
},
|
||||
},
|
||||
"onClickListener": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": undefined,
|
||||
"name": "onClickListener",
|
||||
"table": Object {
|
||||
"category": "methods",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": false,
|
||||
"summary": "(btn: ) => void",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
},
|
||||
},
|
||||
"privateMethod": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>A private method.</p>
|
||||
"description": "
|
||||
|
||||
A private method.
|
||||
|
||||
",
|
||||
"name": "privateMethod",
|
||||
"table": Object {
|
||||
"category": "methods",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": false,
|
||||
"summary": "(password: string) => void",
|
||||
@ -219,22 +298,31 @@ Object {
|
||||
"name": "processedItem",
|
||||
"table": Object {
|
||||
"category": "properties",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "T[]",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "object",
|
||||
},
|
||||
},
|
||||
"protectedMethod": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>A protected method.</p>
|
||||
"description": "
|
||||
|
||||
A protected method.
|
||||
|
||||
",
|
||||
"name": "protectedMethod",
|
||||
"table": Object {
|
||||
"category": "methods",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": false,
|
||||
"summary": "(id?: number) => void",
|
||||
@ -246,11 +334,14 @@ Object {
|
||||
},
|
||||
"publicMethod": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>A public method using an interface. </p>
|
||||
",
|
||||
"description": "
|
||||
A public method using an interface.",
|
||||
"name": "publicMethod",
|
||||
"table": Object {
|
||||
"category": "methods",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": false,
|
||||
"summary": "(things: ISomeInterface) => void",
|
||||
@ -266,6 +357,9 @@ Object {
|
||||
"name": "showKeyAlias",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "",
|
||||
@ -277,11 +371,13 @@ Object {
|
||||
},
|
||||
"size": Object {
|
||||
"defaultValue": "medium",
|
||||
"description": "<p>Size of the button. </p>
|
||||
",
|
||||
"description": "Size of the button.",
|
||||
"name": "size",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": "medium",
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "ButtonSize",
|
||||
@ -293,11 +389,13 @@ Object {
|
||||
},
|
||||
"someDataObject": Object {
|
||||
"defaultValue": undefined,
|
||||
"description": "<p>Specifies some arbitrary object </p>
|
||||
",
|
||||
"description": "Specifies some arbitrary object",
|
||||
"name": "someDataObject",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": "ISomeInterface",
|
||||
@ -309,14 +407,16 @@ Object {
|
||||
},
|
||||
"somethingYouShouldNotUse": Object {
|
||||
"defaultValue": false,
|
||||
"description": "<p>Some input you shouldn't use.</p>
|
||||
",
|
||||
"description": "Some input you shouldn't use.",
|
||||
"name": "somethingYouShouldNotUse",
|
||||
"table": Object {
|
||||
"category": "inputs",
|
||||
"defaultValue": Object {
|
||||
"summary": false,
|
||||
},
|
||||
"type": Object {
|
||||
"required": true,
|
||||
"summary": undefined,
|
||||
"summary": "boolean",
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
|
@ -8,10 +8,11 @@ Object {
|
||||
"accessors": Object {
|
||||
"inputValue": Object {
|
||||
"getSignature": Object {
|
||||
"description": "<p>Getter for <code>inputValue</code>. </p>
|
||||
"description": "<p>Getter for <code>inputValue</code>.</p>
|
||||
",
|
||||
"line": 115,
|
||||
"name": "inputValue",
|
||||
"rawdescription": "Getter for \`inputValue\`.",
|
||||
"returnType": "",
|
||||
"type": "",
|
||||
},
|
||||
@ -19,14 +20,20 @@ Object {
|
||||
"setSignature": Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "value",
|
||||
"tagName": Object {
|
||||
"text": "param",
|
||||
@ -36,6 +43,7 @@ Object {
|
||||
],
|
||||
"line": 110,
|
||||
"name": "inputValue",
|
||||
"rawdescription": "Setter for \`inputValue\` that is also an \`@Input\`.",
|
||||
"returnType": "void",
|
||||
"type": "void",
|
||||
},
|
||||
@ -45,17 +53,23 @@ Object {
|
||||
"setSignature": Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "item",
|
||||
"type": "[]",
|
||||
"type": "T[]",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "item",
|
||||
"tagName": Object {
|
||||
"text": "param",
|
||||
},
|
||||
"type": "[]",
|
||||
"type": "T[]",
|
||||
},
|
||||
],
|
||||
"line": 195,
|
||||
@ -66,10 +80,11 @@ Object {
|
||||
},
|
||||
"value": Object {
|
||||
"getSignature": Object {
|
||||
"description": "<p>Get the private value. </p>
|
||||
"description": "<p>Get the private value.</p>
|
||||
",
|
||||
"line": 154,
|
||||
"name": "value",
|
||||
"rawdescription": "Get the private value.",
|
||||
"returnType": "string | number",
|
||||
"type": "",
|
||||
},
|
||||
@ -77,29 +92,38 @@ Object {
|
||||
"setSignature": Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "value",
|
||||
"type": "",
|
||||
"type": "string | number",
|
||||
},
|
||||
],
|
||||
"description": "<p>Set the private value. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Set the private value.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "value",
|
||||
"tagName": Object {
|
||||
"text": "param",
|
||||
},
|
||||
"type": "",
|
||||
"type": "string | number",
|
||||
},
|
||||
],
|
||||
"line": 149,
|
||||
"name": "value",
|
||||
"rawdescription": "Set the private value.",
|
||||
"returnType": "void",
|
||||
"type": "void",
|
||||
},
|
||||
},
|
||||
},
|
||||
"assetsDirs": Array [],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular.</p>
|
||||
<p>It supports <a href=\\"https://en.wikipedia.org/wiki/Markdown\\">markdown</a>, so you can embed formatted text,
|
||||
like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
@ -113,14 +137,19 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
"hostBindings": Array [
|
||||
Object {
|
||||
"defaultValue": "false",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"line": 124,
|
||||
"name": "class.focused",
|
||||
"type": "boolean",
|
||||
},
|
||||
],
|
||||
"hostListeners": Array [
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "btn",
|
||||
"type": "",
|
||||
},
|
||||
@ -128,6 +157,8 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
"argsDecorator": Array [
|
||||
"$event.target",
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"line": 120,
|
||||
"name": "click",
|
||||
},
|
||||
@ -136,298 +167,391 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
"inputs": Array [],
|
||||
"inputsClass": Array [
|
||||
Object {
|
||||
"description": "<p>Specify the accent-type of the button </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Specify the accent-type of the button</p>
|
||||
",
|
||||
"line": 56,
|
||||
"name": "accent",
|
||||
"rawdescription": "Specify the accent-type of the button",
|
||||
"type": "ButtonAccent",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "'secondary'",
|
||||
"description": "<p>Appearance style of the button. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Appearance style of the button.</p>
|
||||
",
|
||||
"line": 52,
|
||||
"name": "appearance",
|
||||
"rawdescription": "Appearance style of the button.",
|
||||
"type": "\\"primary\\" | \\"secondary\\"",
|
||||
},
|
||||
Object {
|
||||
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Setter for <code>inputValue</code> that is also an <code>@Input</code>.</p>
|
||||
",
|
||||
"line": 110,
|
||||
"name": "inputValue",
|
||||
"rawdescription": "Setter for \`inputValue\` that is also an \`@Input\`.",
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "false",
|
||||
"description": "<p>Sets the button to a disabled state. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Sets the button to a disabled state.</p>
|
||||
",
|
||||
"line": 60,
|
||||
"name": "isDisabled",
|
||||
"rawdescription": "Sets the button to a disabled state.",
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"line": 195,
|
||||
"name": "item",
|
||||
"type": "[]",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>The inner text of the button.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"comment": "",
|
||||
"end": 1525,
|
||||
"flags": 4227072,
|
||||
"kind": 317,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 1512,
|
||||
"tagName": Object {
|
||||
"end": 1521,
|
||||
"escapedText": "required",
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 1513,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"transformFlags": 0,
|
||||
},
|
||||
],
|
||||
"line": 68,
|
||||
"name": "label",
|
||||
"rawdescription": "The inner text of the button.",
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"line": 192,
|
||||
"name": "showKeyAlias",
|
||||
"type": "",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "'medium'",
|
||||
"description": "<p>Size of the button. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Size of the button.</p>
|
||||
",
|
||||
"line": 72,
|
||||
"name": "size",
|
||||
"rawdescription": "Size of the button.",
|
||||
"type": "ButtonSize",
|
||||
},
|
||||
Object {
|
||||
"description": "<p>Specifies some arbitrary object </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Specifies some arbitrary object</p>
|
||||
",
|
||||
"line": 75,
|
||||
"name": "someDataObject",
|
||||
"rawdescription": "Specifies some arbitrary object",
|
||||
"type": "ISomeInterface",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "false",
|
||||
"deprecated": true,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Some input you shouldn't use.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"comment": "",
|
||||
"end": 1802,
|
||||
"flags": 4227072,
|
||||
"kind": 321,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 1787,
|
||||
"tagName": Object {
|
||||
"end": 1798,
|
||||
"escapedText": "deprecated",
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 1788,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"transformFlags": 0,
|
||||
},
|
||||
],
|
||||
"line": 83,
|
||||
"name": "somethingYouShouldNotUse",
|
||||
},
|
||||
],
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"atToken": Object {
|
||||
"end": 859,
|
||||
"flags": 0,
|
||||
"kind": 57,
|
||||
"pos": 858,
|
||||
},
|
||||
"comment": "Hello world",
|
||||
"end": 866,
|
||||
"flags": 0,
|
||||
"kind": 288,
|
||||
"pos": 858,
|
||||
"tagName": Object {
|
||||
"end": 865,
|
||||
"escapedText": "string",
|
||||
"flags": 0,
|
||||
"pos": 859,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"atToken": Object {
|
||||
"end": 882,
|
||||
"flags": 0,
|
||||
"kind": 57,
|
||||
"pos": 881,
|
||||
},
|
||||
"comment": "[Example](http://example.com)",
|
||||
"end": 887,
|
||||
"flags": 0,
|
||||
"kind": 288,
|
||||
"pos": 881,
|
||||
"tagName": Object {
|
||||
"end": 886,
|
||||
"escapedText": "link",
|
||||
"flags": 0,
|
||||
"pos": 882,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"atToken": Object {
|
||||
"end": 921,
|
||||
"flags": 0,
|
||||
"kind": 57,
|
||||
"pos": 920,
|
||||
},
|
||||
"comment": "\`ThingThing\`",
|
||||
"end": 926,
|
||||
"flags": 0,
|
||||
"kind": 288,
|
||||
"pos": 920,
|
||||
"tagName": Object {
|
||||
"end": 925,
|
||||
"escapedText": "code",
|
||||
"flags": 0,
|
||||
"pos": 921,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"atToken": Object {
|
||||
"end": 943,
|
||||
"flags": 0,
|
||||
"kind": 57,
|
||||
"pos": 942,
|
||||
},
|
||||
"comment": "<span class=\\"badge\\">aaa</span>",
|
||||
"end": 948,
|
||||
"flags": 0,
|
||||
"kind": 288,
|
||||
"pos": 942,
|
||||
"tagName": Object {
|
||||
"end": 947,
|
||||
"escapedText": "html",
|
||||
"flags": 0,
|
||||
"pos": 943,
|
||||
},
|
||||
"rawdescription": "Some input you shouldn't use.",
|
||||
"type": "boolean",
|
||||
},
|
||||
],
|
||||
"methodsClass": Array [
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "x",
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "y",
|
||||
"type": "string | number",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>An internal calculation method which adds <code>x</code> and <code>y</code> together.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"comment": "<p>Some number you'd like to use.</p>
|
||||
",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": Object {
|
||||
"end": 3518,
|
||||
"escapedText": "x",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3517,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"tagName": Object {
|
||||
"end": 3516,
|
||||
"escapedText": "param",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3511,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"type": "number",
|
||||
},
|
||||
Object {
|
||||
"comment": "<p>Some other number or string you'd like to use, will have <code>parseInt()</code> applied before calculation.</p>
|
||||
",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": Object {
|
||||
"end": 3563,
|
||||
"escapedText": "y",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3562,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"tagName": Object {
|
||||
"end": 3561,
|
||||
"escapedText": "param",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3556,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"type": "string | number",
|
||||
},
|
||||
],
|
||||
"line": 164,
|
||||
"modifierKind": Array [
|
||||
114,
|
||||
122,
|
||||
],
|
||||
"name": "calc",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
|
||||
An internal calculation method which adds \`x\` and \`y\` together.
|
||||
|
||||
",
|
||||
"returnType": "number",
|
||||
"typeParameters": Array [],
|
||||
},
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "btn",
|
||||
"type": "",
|
||||
},
|
||||
],
|
||||
"decorators": Array [
|
||||
Object {
|
||||
"name": "HostListener",
|
||||
"stringifiedArguments": "'click', ['$event.target']",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "btn",
|
||||
"tagName": Object {
|
||||
"text": "param",
|
||||
},
|
||||
"type": "",
|
||||
},
|
||||
],
|
||||
"line": 120,
|
||||
"name": "onClickListener",
|
||||
"optional": false,
|
||||
"returnType": "void",
|
||||
"typeParameters": Array [],
|
||||
},
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "password",
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>A private method.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"comment": "<p>Some <code>password</code>.</p>
|
||||
",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": Object {
|
||||
"end": 4079,
|
||||
"escapedText": "password",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 4071,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"tagName": Object {
|
||||
"end": 4070,
|
||||
"escapedText": "param",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 4065,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"type": "string",
|
||||
},
|
||||
],
|
||||
"line": 187,
|
||||
"modifierKind": Array [
|
||||
112,
|
||||
120,
|
||||
],
|
||||
"name": "privateMethod",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
|
||||
A private method.
|
||||
|
||||
",
|
||||
"returnType": "void",
|
||||
"typeParameters": Array [],
|
||||
},
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "id",
|
||||
"optional": true,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>A protected method.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"comment": "<p>Some <code>id</code>.</p>
|
||||
",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": Object {
|
||||
"end": 3938,
|
||||
"escapedText": "id",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3936,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"optional": true,
|
||||
"tagName": Object {
|
||||
"end": 3935,
|
||||
"escapedText": "param",
|
||||
"flags": 0,
|
||||
"flags": 4227072,
|
||||
"kind": 78,
|
||||
"modifierFlagsCache": 0,
|
||||
"pos": 3930,
|
||||
"transformFlags": 0,
|
||||
},
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"line": 178,
|
||||
"modifierKind": Array [
|
||||
113,
|
||||
121,
|
||||
],
|
||||
"name": "protectedMethod",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
|
||||
A protected method.
|
||||
|
||||
",
|
||||
"returnType": "void",
|
||||
"typeParameters": Array [],
|
||||
},
|
||||
Object {
|
||||
"args": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "things",
|
||||
"type": "ISomeInterface",
|
||||
},
|
||||
],
|
||||
"description": "<p>A public method using an interface. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>A public method using an interface.</p>
|
||||
",
|
||||
"jsdoctags": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "things",
|
||||
"tagName": Object {
|
||||
"text": "param",
|
||||
@ -437,10 +561,12 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
],
|
||||
"line": 169,
|
||||
"modifierKind": Array [
|
||||
114,
|
||||
122,
|
||||
],
|
||||
"name": "publicMethod",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
A public method using an interface.",
|
||||
"returnType": "void",
|
||||
"typeParameters": Array [],
|
||||
},
|
||||
@ -450,21 +576,31 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
"outputsClass": Array [
|
||||
Object {
|
||||
"defaultValue": "new EventEmitter<Event>()",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Handler to be called when the button is clicked by a user.</p>
|
||||
<p>Will also block the emission of the event if <code>isDisabled</code> is true.</p>
|
||||
",
|
||||
"line": 91,
|
||||
"name": "onClick",
|
||||
"rawdescription": "
|
||||
|
||||
Handler to be called when the button is clicked by a user.
|
||||
|
||||
Will also block the emission of the event if \`isDisabled\` is true.
|
||||
",
|
||||
"type": "EventEmitter",
|
||||
},
|
||||
],
|
||||
"propertiesClass": Array [
|
||||
Object {
|
||||
"defaultValue": "'some value'",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 106,
|
||||
"modifierKind": Array [
|
||||
112,
|
||||
120,
|
||||
],
|
||||
"name": "_inputValue",
|
||||
"optional": false,
|
||||
@ -472,14 +608,18 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "'Private hello'",
|
||||
"description": "<p>Private value. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Private value.</p>
|
||||
",
|
||||
"line": 146,
|
||||
"modifierKind": Array [
|
||||
112,
|
||||
120,
|
||||
],
|
||||
"name": "_value",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
Private value.",
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
@ -489,29 +629,53 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
"stringifiedArguments": "'buttonRef', {static: false}",
|
||||
},
|
||||
],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 48,
|
||||
"name": "buttonRef",
|
||||
"optional": false,
|
||||
"type": "ElementRef",
|
||||
},
|
||||
Object {
|
||||
"decorators": Array [
|
||||
Object {
|
||||
"name": "HostBinding",
|
||||
"stringifiedArguments": "'class.focused'",
|
||||
},
|
||||
],
|
||||
"defaultValue": "false",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 124,
|
||||
"name": "focus",
|
||||
"optional": false,
|
||||
"type": "",
|
||||
},
|
||||
Object {
|
||||
"defaultValue": "'Public hello'",
|
||||
"description": "<p>Public value. </p>
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "<p>Public value.</p>
|
||||
",
|
||||
"line": 143,
|
||||
"modifierKind": Array [
|
||||
114,
|
||||
122,
|
||||
],
|
||||
"name": "internalProperty",
|
||||
"optional": false,
|
||||
"rawdescription": "
|
||||
Public value.",
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 199,
|
||||
"modifierKind": Array [
|
||||
114,
|
||||
122,
|
||||
],
|
||||
"name": "processedItem",
|
||||
"optional": false,
|
||||
@ -519,12 +683,16 @@ like <strong>bold</strong>, <em>italic</em>, and <code>inline code</code>.</p>
|
||||
},
|
||||
],
|
||||
"providers": Array [],
|
||||
"rawdescription": "This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular.
|
||||
"rawdescription": "
|
||||
|
||||
This is a simple button that demonstrates various JSDoc handling in Storybook Docs for Angular.
|
||||
|
||||
It supports [markdown](https://en.wikipedia.org/wiki/Markdown), so you can embed formatted text,
|
||||
like **bold**, _italic_, and \`inline code\`.
|
||||
|
||||
> How you like dem apples?! It's never been easier to document all your components.",
|
||||
> How you like dem apples?! It's never been easier to document all your components.
|
||||
|
||||
",
|
||||
"selector": "doc-button",
|
||||
"sourceCode": "import {
|
||||
Component,
|
||||
@ -735,11 +903,11 @@ export class InputComponent<T> {
|
||||
},
|
||||
],
|
||||
"coverage": Object {
|
||||
"count": 23,
|
||||
"count": 21,
|
||||
"files": Array [
|
||||
Object {
|
||||
"coverageCount": "16/23",
|
||||
"coveragePercent": 69,
|
||||
"coverageCount": "16/25",
|
||||
"coveragePercent": 64,
|
||||
"filePath": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"linktype": "component",
|
||||
"name": "InputComponent",
|
||||
@ -769,17 +937,23 @@ export class InputComponent<T> {
|
||||
"status": "low",
|
||||
},
|
||||
"directives": Array [],
|
||||
"guards": Array [],
|
||||
"injectables": Array [],
|
||||
"interceptors": Array [],
|
||||
"interfaces": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"id": "interface-ISomeInterface-568feeafa68e593b062061c27c4625a9",
|
||||
"indexSignatures": Array [],
|
||||
"kind": 150,
|
||||
"kind": 163,
|
||||
"methods": Array [],
|
||||
"name": "ISomeInterface",
|
||||
"properties": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 25,
|
||||
"name": "one",
|
||||
@ -787,6 +961,8 @@ export class InputComponent<T> {
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 27,
|
||||
"name": "three",
|
||||
@ -794,6 +970,8 @@ export class InputComponent<T> {
|
||||
"type": "any[]",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"line": 26,
|
||||
"name": "two",
|
||||
@ -1007,15 +1185,21 @@ export class InputComponent<T> {
|
||||
Object {
|
||||
"childs": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "Normal",
|
||||
"value": "Normal",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "High",
|
||||
"value": "High",
|
||||
},
|
||||
],
|
||||
"ctype": "miscellaneous",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"name": "ButtonAccent",
|
||||
@ -1028,15 +1212,21 @@ export class InputComponent<T> {
|
||||
Object {
|
||||
"childs": Array [
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "Normal",
|
||||
"value": "Normal",
|
||||
},
|
||||
Object {
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"name": "High",
|
||||
"value": "High",
|
||||
},
|
||||
],
|
||||
"ctype": "miscellaneous",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"name": "ButtonAccent",
|
||||
@ -1049,9 +1239,11 @@ export class InputComponent<T> {
|
||||
"addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts": Array [
|
||||
Object {
|
||||
"ctype": "miscellaneous",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"kind": 168,
|
||||
"kind": 183,
|
||||
"name": "ButtonSize",
|
||||
"rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"",
|
||||
"subtype": "typealias",
|
||||
@ -1063,6 +1255,8 @@ export class InputComponent<T> {
|
||||
Object {
|
||||
"ctype": "miscellaneous",
|
||||
"defaultValue": "'An exported constant'",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"name": "exportedConstant",
|
||||
"subtype": "variable",
|
||||
@ -1073,9 +1267,11 @@ export class InputComponent<T> {
|
||||
"typealiases": Array [
|
||||
Object {
|
||||
"ctype": "miscellaneous",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"description": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"kind": 168,
|
||||
"kind": 183,
|
||||
"name": "ButtonSize",
|
||||
"rawtype": "\\"small\\" | \\"medium\\" | \\"large\\" | \\"xlarge\\"",
|
||||
"subtype": "typealias",
|
||||
@ -1085,6 +1281,8 @@ export class InputComponent<T> {
|
||||
Object {
|
||||
"ctype": "miscellaneous",
|
||||
"defaultValue": "'An exported constant'",
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"file": "addons/docs/src/frameworks/angular/__testfixtures__/doc-button/input.ts",
|
||||
"name": "exportedConstant",
|
||||
"subtype": "variable",
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
Pipe,
|
||||
Property,
|
||||
Directive,
|
||||
JsDocTag,
|
||||
} from './types';
|
||||
|
||||
export const isMethod = (methodOrProp: Method | Property): methodOrProp is Method => {
|
||||
@ -42,7 +43,7 @@ export const checkValidCompodocJson = (compodocJson: CompodocJson) => {
|
||||
const hasDecorator = (item: Property, decoratorName: string) =>
|
||||
item.decorators && item.decorators.find((x: any) => x.name === decoratorName);
|
||||
|
||||
const mapPropertyToSection = (key: string, item: Property) => {
|
||||
const mapPropertyToSection = (item: Property) => {
|
||||
if (hasDecorator(item, 'ViewChild')) {
|
||||
return 'view child';
|
||||
}
|
||||
@ -72,7 +73,7 @@ const mapItemToSection = (key: string, item: Method | Property): string => {
|
||||
if (isMethod(item)) {
|
||||
throw new Error("Cannot be of type Method if key === 'propertiesClass'");
|
||||
}
|
||||
return mapPropertyToSection(key, item);
|
||||
return mapPropertyToSection(item);
|
||||
default:
|
||||
throw new Error(`Unknown key: ${key}`);
|
||||
}
|
||||
@ -112,7 +113,9 @@ const displaySignature = (item: Method): string => {
|
||||
|
||||
const extractTypeFromValue = (defaultValue: any) => {
|
||||
const valueType = typeof defaultValue;
|
||||
return defaultValue || valueType === 'boolean' || valueType === 'string' ? valueType : null;
|
||||
return defaultValue || valueType === 'number' || valueType === 'boolean' || valueType === 'string'
|
||||
? valueType
|
||||
: null;
|
||||
};
|
||||
|
||||
const extractEnumValues = (compodocType: any) => {
|
||||
@ -152,10 +155,59 @@ export const extractType = (property: Property, defaultValue: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
const castDefaultValue = (property: Property, defaultValue: any) => {
|
||||
const compodocType = property.type;
|
||||
|
||||
// All these checks are necessary as compodoc does not always set the type ie. @HostBinding have empty types.
|
||||
// null and undefined also have 'any' type
|
||||
if (['boolean', 'number', 'string', 'EventEmitter'].includes(compodocType)) {
|
||||
switch (compodocType) {
|
||||
case 'boolean':
|
||||
return defaultValue === 'true';
|
||||
case 'number':
|
||||
return Number(defaultValue);
|
||||
case 'EventEmitter':
|
||||
return undefined;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
} else {
|
||||
switch (defaultValue) {
|
||||
case 'true':
|
||||
return true;
|
||||
case 'false':
|
||||
return false;
|
||||
case 'null':
|
||||
return null;
|
||||
case 'undefined':
|
||||
return undefined;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const extractDefaultValueFromComments = (property: Property, value: any) => {
|
||||
let commentValue = value;
|
||||
property.jsdoctags.forEach((tag: JsDocTag) => {
|
||||
if (['default', 'defaultvalue'].includes(tag.tagName.escapedText)) {
|
||||
// @ts-ignore
|
||||
const dom = new window.DOMParser().parseFromString(tag.comment, 'text/html');
|
||||
commentValue = dom.body.textContent;
|
||||
}
|
||||
});
|
||||
return commentValue;
|
||||
};
|
||||
|
||||
const extractDefaultValue = (property: Property) => {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
const value = eval(property.defaultValue);
|
||||
let value: string | boolean = property.defaultValue?.replace(/^'(.*)'$/, '$1');
|
||||
value = castDefaultValue(property, value);
|
||||
|
||||
if (value == null && property.jsdoctags?.length > 0) {
|
||||
value = extractDefaultValueFromComments(property, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
} catch (err) {
|
||||
logger.debug(`Error extracting ${property.name}: ${property.defaultValue}`);
|
||||
@ -187,14 +239,16 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec
|
||||
data.forEach((item: Method | Property) => {
|
||||
const section = mapItemToSection(key, item);
|
||||
const defaultValue = isMethod(item) ? undefined : extractDefaultValue(item as Property);
|
||||
|
||||
const type =
|
||||
isMethod(item) || section !== 'inputs'
|
||||
isMethod(item) || (section !== 'inputs' && section !== 'properties')
|
||||
? { name: 'void' }
|
||||
: extractType(item as Property, defaultValue);
|
||||
const action = section === 'outputs' ? { action: item.name } : {};
|
||||
|
||||
const argType = {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
description: item.rawdescription || item.description,
|
||||
defaultValue,
|
||||
type,
|
||||
...action,
|
||||
@ -204,6 +258,7 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec
|
||||
summary: isMethod(item) ? displaySignature(item) : item.type,
|
||||
required: isMethod(item) ? false : !item.optional,
|
||||
},
|
||||
defaultValue: { summary: defaultValue },
|
||||
},
|
||||
};
|
||||
|
||||
@ -215,9 +270,9 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec
|
||||
});
|
||||
|
||||
const SECTIONS = [
|
||||
'properties',
|
||||
'inputs',
|
||||
'outputs',
|
||||
'properties',
|
||||
'methods',
|
||||
'view child',
|
||||
'view children',
|
||||
|
@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
import pLimit from 'p-limit';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { IStory, StoryContext } from '@storybook/angular';
|
||||
import { AngularFramework, StoryContext } from '@storybook/angular';
|
||||
import { rendererFactory } from '@storybook/angular/renderer';
|
||||
import { StoryFn } from '@storybook/addons';
|
||||
import { PartialStoryFn } from '@storybook/csf';
|
||||
|
||||
const limit = pLimit(1);
|
||||
|
||||
/**
|
||||
* Uses the angular renderer to generate a story. Uses p-limit to run synchronously
|
||||
*/
|
||||
export const prepareForInline = (storyFn: StoryFn<IStory>, { id, parameters }: StoryContext) => {
|
||||
export const prepareForInline = (
|
||||
storyFn: PartialStoryFn<AngularFramework>,
|
||||
{ id, parameters }: StoryContext
|
||||
) => {
|
||||
return React.createElement('div', {
|
||||
ref: async (node?: HTMLDivElement): Promise<void> => {
|
||||
if (!node) {
|
||||
@ -18,7 +22,7 @@ export const prepareForInline = (storyFn: StoryFn<IStory>, { id, parameters }: S
|
||||
}
|
||||
|
||||
return limit(async () => {
|
||||
const renderer = await rendererFactory.getRendererInstance(id, node);
|
||||
const renderer = await rendererFactory.getRendererInstance(`${id}-${nanoid(10)}`, node);
|
||||
await renderer.render({
|
||||
forced: false,
|
||||
parameters,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { addons, StoryContext, StoryFn } from '@storybook/addons';
|
||||
import { IStory } from '@storybook/angular';
|
||||
import { addons, useEffect } from '@storybook/addons';
|
||||
import { PartialStoryFn } from '@storybook/csf';
|
||||
import { StoryContext, AngularFramework } from '@storybook/angular';
|
||||
import { computesTemplateSourceFromComponent } from '@storybook/angular/renderer';
|
||||
import prettierHtml from 'prettier/parser-html';
|
||||
import prettier from 'prettier/standalone';
|
||||
@ -30,32 +31,34 @@ const prettyUp = (source: string) => {
|
||||
* @param storyFn Fn
|
||||
* @param context StoryContext
|
||||
*/
|
||||
export const sourceDecorator = (storyFn: StoryFn<IStory>, context: StoryContext) => {
|
||||
export const sourceDecorator = (
|
||||
storyFn: PartialStoryFn<AngularFramework>,
|
||||
context: StoryContext
|
||||
) => {
|
||||
const story = storyFn();
|
||||
if (skipSourceRender(context)) {
|
||||
return story;
|
||||
}
|
||||
const channel = addons.getChannel();
|
||||
const { props, template } = story;
|
||||
const { props, template, userDefinedTemplate } = story;
|
||||
|
||||
const {
|
||||
parameters: { component, argTypes },
|
||||
} = context;
|
||||
const { component, argTypes } = context;
|
||||
|
||||
if (component) {
|
||||
let toEmit: string;
|
||||
useEffect(() => {
|
||||
if (toEmit) channel.emit(SNIPPET_RENDERED, context.id, prettyUp(template));
|
||||
});
|
||||
|
||||
if (component && !userDefinedTemplate) {
|
||||
const source = computesTemplateSourceFromComponent(component, props, argTypes);
|
||||
|
||||
// We might have a story with a Directive or Service defined as the component
|
||||
// In these cases there might exist a template, even if we aren't able to create source from component
|
||||
if (source || template) {
|
||||
channel.emit(SNIPPET_RENDERED, context.id, prettyUp(source || template));
|
||||
toEmit = prettyUp(source || template);
|
||||
}
|
||||
return story;
|
||||
}
|
||||
|
||||
if (template) {
|
||||
channel.emit(SNIPPET_RENDERED, context.id, prettyUp(template));
|
||||
return story;
|
||||
} else if (template) {
|
||||
toEmit = prettyUp(template);
|
||||
}
|
||||
|
||||
return story;
|
||||
|
@ -4,6 +4,14 @@ export interface Method {
|
||||
returnType: string;
|
||||
decorators?: Decorator[];
|
||||
description?: string;
|
||||
rawdescription?: string;
|
||||
}
|
||||
|
||||
export interface JsDocTag {
|
||||
comment?: string;
|
||||
tagName?: {
|
||||
escapedText?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Property {
|
||||
@ -13,6 +21,8 @@ export interface Property {
|
||||
optional: boolean;
|
||||
defaultValue?: string;
|
||||
description?: string;
|
||||
rawdescription?: string;
|
||||
jsdoctags?: JsDocTag[];
|
||||
}
|
||||
|
||||
export interface Class {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ArgType, ArgTypes } from '@storybook/api';
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { StrictInputType } from '@storybook/csf';
|
||||
import { enhanceArgTypes } from './enhanceArgTypes';
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
@ -12,30 +13,28 @@ const enhance = ({
|
||||
extractedArgTypes,
|
||||
isArgsStory = true,
|
||||
}: {
|
||||
argType?: ArgType;
|
||||
argType?: StrictInputType;
|
||||
arg?: any;
|
||||
extractedArgTypes?: ArgTypes;
|
||||
isArgsStory?: boolean;
|
||||
}) => {
|
||||
const context = {
|
||||
id: 'foo--bar',
|
||||
componentId: 'foo',
|
||||
title: 'foo',
|
||||
kind: 'foo',
|
||||
id: 'foo--bar',
|
||||
name: 'bar',
|
||||
story: 'bar',
|
||||
component: 'dummy',
|
||||
parameters: {
|
||||
component: 'dummy',
|
||||
__isArgsStory: isArgsStory,
|
||||
docs: {
|
||||
extractArgTypes: extractedArgTypes && (() => extractedArgTypes),
|
||||
},
|
||||
argTypes: argType && {
|
||||
input: argType,
|
||||
},
|
||||
args: {
|
||||
input: arg,
|
||||
},
|
||||
},
|
||||
args: {},
|
||||
argTypes: {},
|
||||
argTypes: argType && { input: argType },
|
||||
initialArgs: { input: arg },
|
||||
args: { input: arg },
|
||||
globals: {},
|
||||
};
|
||||
return enhanceArgTypes(context);
|
||||
@ -46,7 +45,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('should no-op', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { foo: 'unmodified', type: { name: 'number' } },
|
||||
argType: { name: 'input', foo: 'unmodified', type: { name: 'number' } },
|
||||
isArgsStory: false,
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
@ -66,7 +65,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('number', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { type: { name: 'number' } },
|
||||
argType: { name: 'input', type: { name: 'number' } },
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
@ -99,7 +98,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('range', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { control: { type: 'range', min: 0, max: 100 } },
|
||||
argType: { name: 'input', control: { type: 'range', min: 0, max: 100 } },
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
@ -115,7 +114,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('options', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { control: { type: 'radio', options: [1, 2] } },
|
||||
argType: { name: 'input', control: { type: 'radio', options: [1, 2] } },
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
@ -137,7 +136,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('user-specified argTypes take precedence over extracted argTypes', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { type: { name: 'number' } },
|
||||
argType: { name: 'input', type: { name: 'number' } },
|
||||
extractedArgTypes: { input: { type: { name: 'string' } } },
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
@ -153,7 +152,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('user-specified argTypes take precedence over inferred argTypes', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { type: { name: 'number' } },
|
||||
argType: { name: 'input', type: { name: 'number' } },
|
||||
arg: 'hello',
|
||||
}).input
|
||||
).toMatchInlineSnapshot(`
|
||||
@ -184,7 +183,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('user-specified controls take precedence over inferred controls', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { defaultValue: 5, control: { type: 'range', step: 50 } },
|
||||
argType: { name: 'input', defaultValue: 5, control: { type: 'range', step: 50 } },
|
||||
arg: 3,
|
||||
extractedArgTypes: { input: { name: 'input' } },
|
||||
}).input
|
||||
@ -223,7 +222,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('includes extracted argTypes when user-specified argTypes match', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { type: { name: 'number' } },
|
||||
argType: { name: 'input', type: { name: 'number' } },
|
||||
extractedArgTypes: { input: { name: 'input' }, foo: { type: { name: 'number' } } },
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
@ -246,7 +245,7 @@ describe('enhanceArgTypes', () => {
|
||||
it('excludes extracted argTypes when user-specified argTypes do not match', () => {
|
||||
expect(
|
||||
enhance({
|
||||
argType: { type: { name: 'number' } },
|
||||
argType: { name: 'input', type: { name: 'number' } },
|
||||
extractedArgTypes: { foo: { type: { name: 'number' } } },
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
|
@ -1,17 +1,20 @@
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { ArgTypesEnhancer, combineParameters } from '@storybook/client-api';
|
||||
import { normalizeArgTypes } from './normalizeArgTypes';
|
||||
import { AnyFramework, StoryContextForEnhancers } from '@storybook/csf';
|
||||
import { combineParameters } from '@storybook/store';
|
||||
|
||||
export const enhanceArgTypes: ArgTypesEnhancer = (context) => {
|
||||
const { component, argTypes: userArgTypes = {}, docs = {} } = context.parameters;
|
||||
export const enhanceArgTypes = <TFramework extends AnyFramework>(
|
||||
context: StoryContextForEnhancers<TFramework>
|
||||
) => {
|
||||
const {
|
||||
component,
|
||||
argTypes: userArgTypes,
|
||||
parameters: { docs = {} },
|
||||
} = context;
|
||||
const { extractArgTypes } = docs;
|
||||
|
||||
const normalizedArgTypes = normalizeArgTypes(userArgTypes);
|
||||
const namedArgTypes = mapValues(normalizedArgTypes, (val, key) => ({ name: key, ...val }));
|
||||
const extractedArgTypes = extractArgTypes && component ? extractArgTypes(component) : {};
|
||||
const withExtractedTypes = extractedArgTypes
|
||||
? combineParameters(extractedArgTypes, namedArgTypes)
|
||||
: namedArgTypes;
|
||||
? combineParameters(extractedArgTypes, userArgTypes)
|
||||
: userArgTypes;
|
||||
|
||||
return withExtractedTypes;
|
||||
};
|
||||
|
@ -1,18 +0,0 @@
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { SBType } from '@storybook/client-api';
|
||||
|
||||
const normalizeType = (type: SBType | string) => (typeof type === 'string' ? { name: type } : type);
|
||||
|
||||
const normalizeControl = (control?: any) =>
|
||||
typeof control === 'string' ? { type: control } : control;
|
||||
|
||||
export const normalizeArgTypes = (argTypes: ArgTypes) =>
|
||||
mapValues(argTypes, (argType) => {
|
||||
if (!argType) return argType;
|
||||
const normalized = { ...argType };
|
||||
const { type, control } = argType;
|
||||
if (type) normalized.type = normalizeType(type);
|
||||
if (control) normalized.control = normalizeControl(control);
|
||||
return normalized;
|
||||
});
|
16
addons/docs/src/frameworks/html/config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { sourceDecorator } from './sourceDecorator';
|
||||
import { prepareForInline } from './prepareForInline';
|
||||
import { SourceType } from '../../shared';
|
||||
|
||||
export const decorators = [sourceDecorator];
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
inlineStories: true,
|
||||
prepareForInline,
|
||||
source: {
|
||||
type: SourceType.DYNAMIC,
|
||||
language: 'html',
|
||||
},
|
||||
},
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StoryFn } from '@storybook/addons';
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
inlineStories: true,
|
||||
prepareForInline: (storyFn: StoryFn<string>) => {
|
||||
const html = storyFn();
|
||||
if (typeof html === 'string') {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
ref={(node?: HTMLDivElement): never | null => (node ? node.appendChild(html) : null)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
13
addons/docs/src/frameworks/html/prepareForInline.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { PartialStoryFn } from '@storybook/csf';
|
||||
|
||||
export function prepareForInline(storyFn: PartialStoryFn<any>) {
|
||||
const html = storyFn();
|
||||
if (typeof html === 'string') {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
||||
return (
|
||||
<div ref={(node?: HTMLDivElement): never | null => (node ? node.appendChild(html) : null)} />
|
||||
);
|
||||
}
|
122
addons/docs/src/frameworks/html/sourceDecorator.test.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { addons, StoryContext, useEffect } from '@storybook/addons';
|
||||
import { sourceDecorator } from './sourceDecorator';
|
||||
import { SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
const mockedAddons = addons as jest.Mocked<typeof addons>;
|
||||
const mockedUseEffect = useEffect as jest.Mocked<typeof useEffect>;
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
print: (val: any) => val,
|
||||
test: (val) => typeof val === 'string',
|
||||
});
|
||||
|
||||
const tick = () => new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
const makeContext = (name: string, parameters: any, args: any, extra?: object): StoryContext => ({
|
||||
id: `html-test--${name}`,
|
||||
kind: 'js-text',
|
||||
name,
|
||||
parameters,
|
||||
args,
|
||||
argTypes: {},
|
||||
globals: {},
|
||||
...extra,
|
||||
});
|
||||
|
||||
describe('sourceDecorator', () => {
|
||||
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
|
||||
beforeEach(() => {
|
||||
mockedAddons.getChannel.mockReset();
|
||||
mockedUseEffect.mockImplementation((cb) => setTimeout(cb, 0));
|
||||
|
||||
mockChannel = { on: jest.fn(), emit: jest.fn() };
|
||||
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
|
||||
});
|
||||
|
||||
it('should render dynamically for args stories', async () => {
|
||||
const storyFn = (args: any) => `<div>args story</div>`;
|
||||
const context = makeContext('args', { __isArgsStory: true }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'html-test--args',
|
||||
'<div>args story</div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should dedent source by default', async () => {
|
||||
const storyFn = (args: any) => `
|
||||
<div>
|
||||
args story
|
||||
</div>
|
||||
`;
|
||||
const context = makeContext('args', { __isArgsStory: true }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'html-test--args',
|
||||
['<div>', ' args story', '</div>'].join('\n')
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip dynamic rendering for no-args stories', async () => {
|
||||
const storyFn = () => `<div>classic story</div>`;
|
||||
const context = makeContext('classic', {}, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the originalStoryFn if excludeDecorators is set', async () => {
|
||||
const storyFn = (args: any) => `<div>args story</div>`;
|
||||
const decoratedStoryFn = (args: any) => `
|
||||
<div style="padding: 25px; border: 3px solid red;">${storyFn(args)}</div>
|
||||
`;
|
||||
const context = makeContext(
|
||||
'args',
|
||||
{
|
||||
__isArgsStory: true,
|
||||
docs: {
|
||||
source: {
|
||||
excludeDecorators: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ originalStoryFn: storyFn }
|
||||
);
|
||||
sourceDecorator(decoratedStoryFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'html-test--args',
|
||||
'<div>args story</div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows the snippet output to be modified by transformSource', async () => {
|
||||
const storyFn = (args: any) => `<div>args story</div>`;
|
||||
const transformSource = (dom: string) => `<p>${dom}</p>`;
|
||||
const docs = { transformSource };
|
||||
const context = makeContext('args', { __isArgsStory: true, docs }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'html-test--args',
|
||||
'<p><div>args story</div></p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('provides the story context to transformSource', () => {
|
||||
const storyFn = (args: any) => `<div>args story</div>`;
|
||||
const transformSource = jest.fn((x) => x);
|
||||
const docs = { transformSource };
|
||||
const context = makeContext('args', { __isArgsStory: true, docs }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
expect(transformSource).toHaveBeenCalledWith('<div>args story</div>', context);
|
||||
});
|
||||
});
|
52
addons/docs/src/frameworks/html/sourceDecorator.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/* global window */
|
||||
import { addons, useEffect } from '@storybook/addons';
|
||||
import { ArgsStoryFn, PartialStoryFn, StoryContext } from '@storybook/csf';
|
||||
import dedent from 'ts-dedent';
|
||||
import { HtmlFramework } from '@storybook/html';
|
||||
|
||||
import { SNIPPET_RENDERED, SourceType } from '../../shared';
|
||||
|
||||
function skipSourceRender(context: StoryContext<HtmlFramework>) {
|
||||
const sourceParams = context?.parameters.docs?.source;
|
||||
const isArgsStory = context?.parameters.__isArgsStory;
|
||||
|
||||
// always render if the user forces it
|
||||
if (sourceParams?.type === SourceType.DYNAMIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// never render if the user is forcing the block to render code, or
|
||||
// if the user provides code, or if it's not an args story.
|
||||
return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
|
||||
}
|
||||
|
||||
// By default, just remove indentation
|
||||
function defaultTransformSource(source: string) {
|
||||
// Have to wrap dedent so it doesn't serialize the context
|
||||
return dedent(source);
|
||||
}
|
||||
|
||||
function applyTransformSource(source: string, context: StoryContext<HtmlFramework>): string {
|
||||
const docs = context.parameters.docs ?? {};
|
||||
const transformSource = docs.transformSource ?? defaultTransformSource;
|
||||
return transformSource(source, context);
|
||||
}
|
||||
|
||||
export function sourceDecorator(
|
||||
storyFn: PartialStoryFn<HtmlFramework>,
|
||||
context: StoryContext<HtmlFramework>
|
||||
) {
|
||||
const story = context?.parameters.docs?.source?.excludeDecorators
|
||||
? (context.originalStoryFn as ArgsStoryFn<HtmlFramework>)(context.args, context)
|
||||
: storyFn();
|
||||
|
||||
let source: string;
|
||||
if (typeof story === 'string' && !skipSourceRender(context)) {
|
||||
source = applyTransformSource(story, context);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (source) addons.getChannel().emit(SNIPPET_RENDERED, context.id, source);
|
||||
});
|
||||
|
||||
return story;
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { StoryFn } from '@storybook/addons';
|
||||
import { PartialStoryFn } from '@storybook/csf';
|
||||
import { ReactFramework } from '@storybook/react';
|
||||
|
||||
import { extractArgTypes } from './extractArgTypes';
|
||||
import { extractComponentDescription } from '../../lib/docgen';
|
||||
import { jsxDecorator } from './jsxDecorator';
|
||||
@ -7,7 +9,7 @@ export const parameters = {
|
||||
docs: {
|
||||
inlineStories: true,
|
||||
// NOTE: that the result is a react element. Hooks support is provided by the outer code.
|
||||
prepareForInline: (storyFn: StoryFn) => storyFn(),
|
||||
prepareForInline: (storyFn: PartialStoryFn<ReactFramework>) => storyFn(),
|
||||
extractArgTypes,
|
||||
extractComponentDescription,
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { StrictArgTypes } from '@storybook/csf';
|
||||
import { PropDef, ArgTypesExtractor } from '../../lib/docgen';
|
||||
import { extractProps } from './extractProps';
|
||||
|
||||
@ -6,7 +6,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => {
|
||||
if (component) {
|
||||
const { rows } = extractProps(component);
|
||||
if (rows) {
|
||||
return rows.reduce((acc: ArgTypes, row: PropDef) => {
|
||||
return rows.reduce((acc: StrictArgTypes, row: PropDef) => {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
|
@ -1,12 +1,13 @@
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { addons, StoryContext } from '@storybook/addons';
|
||||
import { addons, StoryContext, useEffect } from '@storybook/addons';
|
||||
import { renderJsx, jsxDecorator } from './jsxDecorator';
|
||||
import { SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
const mockedAddons = addons as jest.Mocked<typeof addons>;
|
||||
const mockedUseEffect = useEffect as jest.Mocked<typeof useEffect>;
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
print: (val: any) => val,
|
||||
@ -168,15 +169,17 @@ describe('jsxDecorator', () => {
|
||||
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
|
||||
beforeEach(() => {
|
||||
mockedAddons.getChannel.mockReset();
|
||||
mockedUseEffect.mockImplementation((cb) => setTimeout(cb, 0));
|
||||
|
||||
mockChannel = { on: jest.fn(), emit: jest.fn() };
|
||||
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
|
||||
});
|
||||
|
||||
it('should render dynamically for args stories', () => {
|
||||
it('should render dynamically for args stories', async () => {
|
||||
const storyFn = (args: any) => <div>args story</div>;
|
||||
const context = makeContext('args', { __isArgsStory: true }, {});
|
||||
jsxDecorator(storyFn, context);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'jsx-test--args',
|
||||
@ -184,7 +187,7 @@ describe('jsxDecorator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render decorators when provided excludeDecorators parameter', () => {
|
||||
it('should not render decorators when provided excludeDecorators parameter', async () => {
|
||||
const storyFn = (args: any) => <div>args story</div>;
|
||||
const decoratedStoryFn = (args: any) => (
|
||||
<div style={{ padding: 25, border: '3px solid red' }}>{storyFn(args)}</div>
|
||||
@ -203,6 +206,8 @@ describe('jsxDecorator', () => {
|
||||
{ originalStoryFn: storyFn }
|
||||
);
|
||||
jsxDecorator(decoratedStoryFn, context);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'jsx-test--args',
|
||||
@ -210,20 +215,24 @@ describe('jsxDecorator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip dynamic rendering for no-args stories', () => {
|
||||
it('should skip dynamic rendering for no-args stories', async () => {
|
||||
const storyFn = () => <div>classic story</div>;
|
||||
const context = makeContext('classic', {}, {});
|
||||
jsxDecorator(storyFn, context);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(mockChannel.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// This is deprecated, but still test it
|
||||
it('allows the snippet output to be modified by onBeforeRender', () => {
|
||||
it('allows the snippet output to be modified by onBeforeRender', async () => {
|
||||
const storyFn = (args: any) => <div>args story</div>;
|
||||
const onBeforeRender = (dom: string) => `<p>${dom}</p>`;
|
||||
const jsx = { onBeforeRender };
|
||||
const context = makeContext('args', { __isArgsStory: true, jsx }, {});
|
||||
jsxDecorator(storyFn, context);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'jsx-test--args',
|
||||
@ -231,12 +240,14 @@ describe('jsxDecorator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('allows the snippet output to be modified by transformSource', () => {
|
||||
it('allows the snippet output to be modified by transformSource', async () => {
|
||||
const storyFn = (args: any) => <div>args story</div>;
|
||||
const transformSource = (dom: string) => `<p>${dom}</p>`;
|
||||
const jsx = { transformSource };
|
||||
const context = makeContext('args', { __isArgsStory: true, jsx }, {});
|
||||
jsxDecorator(storyFn, context);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'jsx-test--args',
|
||||
@ -253,7 +264,7 @@ describe('jsxDecorator', () => {
|
||||
expect(transformSource).toHaveBeenCalledWith('<div>\n args story\n</div>', context);
|
||||
});
|
||||
|
||||
it('renders MDX properly', () => {
|
||||
it('renders MDX properly', async () => {
|
||||
// FIXME: generate this from actual MDX
|
||||
const mdxElement = {
|
||||
type: { displayName: 'MDXCreateElement' },
|
||||
@ -265,6 +276,7 @@ describe('jsxDecorator', () => {
|
||||
};
|
||||
|
||||
jsxDecorator(() => mdxElement, makeContext('mdx-args', { __isArgsStory: true }, {}));
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
|
@ -3,8 +3,10 @@ import reactElementToJSXString, { Options } from 'react-element-to-jsx-string';
|
||||
import dedent from 'ts-dedent';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
import { addons, StoryContext } from '@storybook/addons';
|
||||
import { addons, useEffect } from '@storybook/addons';
|
||||
import { StoryContext, ArgsStoryFn, PartialStoryFn } from '@storybook/csf';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { ReactFramework } from '@storybook/react';
|
||||
|
||||
import { SourceType, SNIPPET_RENDERED } from '../../shared';
|
||||
import { getDocgenSection } from '../../lib/docgen';
|
||||
@ -22,7 +24,7 @@ type JSXOptions = Options & {
|
||||
/** Deprecated: A function ran after the story is rendered */
|
||||
onBeforeRender?(dom: string): string;
|
||||
/** A function ran after a story is rendered (prefer this over `onBeforeRender`) */
|
||||
transformSource?(dom: string, context?: StoryContext): string;
|
||||
transformSource?(dom: string, context?: StoryContext<ReactFramework>): string;
|
||||
};
|
||||
|
||||
/** Run the user supplied onBeforeRender function if it exists */
|
||||
@ -44,7 +46,11 @@ const applyBeforeRender = (domString: string, options: JSXOptions) => {
|
||||
};
|
||||
|
||||
/** Run the user supplied transformSource function if it exists */
|
||||
const applyTransformSource = (domString: string, options: JSXOptions, context?: StoryContext) => {
|
||||
const applyTransformSource = (
|
||||
domString: string,
|
||||
options: JSXOptions,
|
||||
context?: StoryContext<ReactFramework>
|
||||
) => {
|
||||
if (typeof options.transformSource !== 'function') {
|
||||
return domString;
|
||||
}
|
||||
@ -115,12 +121,14 @@ export const renderJsx = (code: React.ReactElement, options: JSXOptions) => {
|
||||
// @ts-ignore FIXME: workaround react-element-to-jsx-string
|
||||
const child = typeof c === 'number' ? c.toString() : c;
|
||||
let string = applyBeforeRender(reactElementToJSXString(child, opts as Options), options);
|
||||
const matches = string.match(/\S+=\\"([^"]*)\\"/g);
|
||||
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
string = string.replace(match, match.replace(/"/g, "'"));
|
||||
});
|
||||
if (string.indexOf('"') > -1) {
|
||||
const matches = string.match(/\S+=\\"([^"]*)\\"/g);
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
string = string.replace(match, match.replace(/"/g, "'"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
@ -136,7 +144,7 @@ const defaultOpts = {
|
||||
showDefaultProps: false,
|
||||
};
|
||||
|
||||
export const skipJsxRender = (context: StoryContext) => {
|
||||
export const skipJsxRender = (context: StoryContext<ReactFramework>) => {
|
||||
const sourceParams = context?.parameters.docs?.source;
|
||||
const isArgsStory = context?.parameters.__isArgsStory;
|
||||
|
||||
@ -163,17 +171,26 @@ const mdxToJsx = (node: any) => {
|
||||
return createElement(originalType, rest, ...jsxChildren);
|
||||
};
|
||||
|
||||
export const jsxDecorator = (storyFn: any, context: StoryContext) => {
|
||||
export const jsxDecorator = (
|
||||
storyFn: PartialStoryFn<ReactFramework>,
|
||||
context: StoryContext<ReactFramework>
|
||||
) => {
|
||||
const channel = addons.getChannel();
|
||||
const skip = skipJsxRender(context);
|
||||
const story = storyFn();
|
||||
|
||||
let jsx = '';
|
||||
|
||||
useEffect(() => {
|
||||
if (!skip) channel.emit(SNIPPET_RENDERED, (context || {}).id, jsx);
|
||||
});
|
||||
|
||||
// We only need to render JSX if the source block is actually going to
|
||||
// consume it. Otherwise it's just slowing us down.
|
||||
if (skipJsxRender(context)) {
|
||||
if (skip) {
|
||||
return story;
|
||||
}
|
||||
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const options = {
|
||||
...defaultOpts,
|
||||
...(context?.parameters.jsx || {}),
|
||||
@ -181,18 +198,15 @@ export const jsxDecorator = (storyFn: any, context: StoryContext) => {
|
||||
|
||||
// Exclude decorators from source code snippet by default
|
||||
const storyJsx = context?.parameters.docs?.source?.excludeDecorators
|
||||
? context.originalStoryFn(context.args)
|
||||
? (context.originalStoryFn as ArgsStoryFn<ReactFramework>)(context.args, context)
|
||||
: story;
|
||||
|
||||
const sourceJsx = mdxToJsx(storyJsx);
|
||||
|
||||
let jsx = '';
|
||||
const rendered = renderJsx(sourceJsx, options);
|
||||
if (rendered) {
|
||||
jsx = applyTransformSource(rendered, options, context);
|
||||
}
|
||||
|
||||
channel.emit(SNIPPET_RENDERED, (context || {}).id, jsx);
|
||||
|
||||
return story;
|
||||
};
|
||||
|
@ -10,8 +10,8 @@ import { Component } from '../../blocks';
|
||||
|
||||
const argsTableProps = (component: Component) => {
|
||||
const argTypes = extractArgTypes(component);
|
||||
const parameters = { __isArgsStory: true, argTypes };
|
||||
const rows = inferControls(({ parameters } as unknown) as StoryContext);
|
||||
const parameters = { __isArgsStory: true };
|
||||
const rows = inferControls(({ argTypes, parameters } as unknown) as StoryContext<any>);
|
||||
return { rows };
|
||||
};
|
||||
|
||||
|
@ -3,8 +3,9 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { transformFileSync, transformSync } from '@babel/core';
|
||||
import { inferControls } from '@storybook/client-api';
|
||||
import { inferControls } from '@storybook/store';
|
||||
import { StoryContext } from '@storybook/react';
|
||||
import { AnyFramework } from '@storybook/csf';
|
||||
import requireFromString from 'require-from-string';
|
||||
|
||||
import { extractProps } from './extractProps';
|
||||
@ -69,8 +70,11 @@ describe('react component properties', () => {
|
||||
|
||||
// snapshot the output of `extractArgTypes`
|
||||
const argTypes = extractArgTypes(component);
|
||||
const parameters = { __isArgsStory: true, argTypes };
|
||||
const rows = inferControls(({ parameters } as unknown) as StoryContext);
|
||||
const parameters = { __isArgsStory: true };
|
||||
const rows = inferControls(({
|
||||
argTypes,
|
||||
parameters,
|
||||
} as unknown) as StoryContext<AnyFramework>);
|
||||
expect(rows).toMatchSpecificSnapshot(path.join(testDir, 'argTypes.snapshot'));
|
||||
});
|
||||
}
|
||||
|
@ -73,7 +73,8 @@ describe('Extracting Arguments', () => {
|
||||
"category": "events",
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "other",
|
||||
"value": "void",
|
||||
},
|
||||
},
|
||||
"event_click": Object {
|
||||
@ -83,7 +84,8 @@ describe('Extracting Arguments', () => {
|
||||
"category": "events",
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "other",
|
||||
"value": "void",
|
||||
},
|
||||
},
|
||||
"rounded": Object {
|
||||
@ -115,7 +117,8 @@ describe('Extracting Arguments', () => {
|
||||
"category": "slots",
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
"name": "other",
|
||||
"value": "void",
|
||||
},
|
||||
},
|
||||
"text": Object {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { SBScalarType, StrictArgTypes } from '@storybook/csf';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import type {
|
||||
SvelteComponentDoc,
|
||||
@ -31,7 +31,7 @@ export const extractArgTypes: ArgTypesExtractor = (component: ComponentWithDocge
|
||||
};
|
||||
|
||||
export const createArgTypes = (docgen: SvelteComponentDoc) => {
|
||||
const results: ArgTypes = {};
|
||||
const results: StrictArgTypes = {};
|
||||
docgen.data.forEach((item) => {
|
||||
results[item.name] = {
|
||||
control: parseTypeToControl(item.type),
|
||||
@ -39,7 +39,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => {
|
||||
description: item.description,
|
||||
type: {
|
||||
required: hasKeyword('required', item.keywords),
|
||||
name: item.type?.text,
|
||||
name: item.type?.text as SBScalarType['name'],
|
||||
},
|
||||
table: {
|
||||
type: {
|
||||
@ -57,7 +57,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => {
|
||||
results[`event_${item.name}`] = {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
type: { name: 'void' },
|
||||
type: { name: 'other', value: 'void' },
|
||||
table: {
|
||||
category: 'events',
|
||||
},
|
||||
@ -70,7 +70,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => {
|
||||
description: [item.description, item.params?.map((p) => `\`${p.name}\``).join(' ')]
|
||||
.filter((p) => p)
|
||||
.join('\n\n'),
|
||||
type: { name: 'void' },
|
||||
type: { name: 'other', value: 'void' },
|
||||
table: {
|
||||
category: 'slots',
|
||||
},
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { StoryFn } from '@storybook/addons';
|
||||
import { AnyFramework, StoryFn } from '@storybook/csf';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import HOC from './HOC.svelte';
|
||||
|
||||
export const prepareForInline = (storyFn: StoryFn) => {
|
||||
export const prepareForInline = (storyFn: StoryFn<AnyFramework>) => {
|
||||
const el = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
const root = new HOC({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { addons, StoryContext } from '@storybook/addons';
|
||||
import { ArgTypes, Args } from '@storybook/api';
|
||||
import { addons, useEffect } from '@storybook/addons';
|
||||
import { ArgTypes, Args, StoryContext, AnyFramework } from '@storybook/csf';
|
||||
|
||||
import { SourceType, SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
@ -8,7 +8,7 @@ import { SourceType, SNIPPET_RENDERED } from '../../shared';
|
||||
*
|
||||
* @param context StoryContext
|
||||
*/
|
||||
const skipSourceRender = (context: StoryContext) => {
|
||||
const skipSourceRender = (context: StoryContext<AnyFramework>) => {
|
||||
const sourceParams = context?.parameters.docs?.source;
|
||||
const isArgsStory = context?.parameters.__isArgsStory;
|
||||
|
||||
@ -144,15 +144,22 @@ function getWrapperProperties(component: any) {
|
||||
* @param storyFn Fn
|
||||
* @param context StoryContext
|
||||
*/
|
||||
export const sourceDecorator = (storyFn: any, context: StoryContext) => {
|
||||
export const sourceDecorator = (storyFn: any, context: StoryContext<AnyFramework>) => {
|
||||
const channel = addons.getChannel();
|
||||
const skip = skipSourceRender(context);
|
||||
const story = storyFn();
|
||||
|
||||
if (skipSourceRender(context)) {
|
||||
let source: string;
|
||||
useEffect(() => {
|
||||
if (!skip && source) {
|
||||
channel.emit(SNIPPET_RENDERED, (context || {}).id, source);
|
||||
}
|
||||
});
|
||||
|
||||
if (skip) {
|
||||
return story;
|
||||
}
|
||||
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const { parameters = {}, args = {} } = context || {};
|
||||
let { Component: component = {} } = story;
|
||||
|
||||
@ -161,11 +168,7 @@ export const sourceDecorator = (storyFn: any, context: StoryContext) => {
|
||||
component = parameters.component;
|
||||
}
|
||||
|
||||
const source = generateSvelteSource(component, args, context?.argTypes, slotProperty);
|
||||
|
||||
if (source) {
|
||||
channel.emit(SNIPPET_RENDERED, (context || {}).id, source);
|
||||
}
|
||||
source = generateSvelteSource(component, args, context?.argTypes, slotProperty);
|
||||
|
||||
return story;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { StrictArgTypes } from '@storybook/csf';
|
||||
import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen';
|
||||
import { convert } from '../../lib/convert';
|
||||
|
||||
@ -8,7 +8,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => {
|
||||
if (!hasDocgen(component)) {
|
||||
return null;
|
||||
}
|
||||
const results: ArgTypes = {};
|
||||
const results: StrictArgTypes = {};
|
||||
SECTIONS.forEach((section) => {
|
||||
const props = extractComponentProps(component, section);
|
||||
props.forEach(({ propDef, docgenInfo, jsDocTags }) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Vue from 'vue';
|
||||
import { StoryFn, StoryContext } from '@storybook/addons';
|
||||
import { StoryContext, PartialStoryFn } from '@storybook/csf';
|
||||
import { VueFramework } from '@storybook/vue';
|
||||
|
||||
// Inspired by https://github.com/egoist/vue-to-react,
|
||||
// modified to store args as props in the root store
|
||||
@ -9,7 +10,10 @@ import { StoryFn, StoryContext } from '@storybook/addons';
|
||||
const COMPONENT = 'STORYBOOK_COMPONENT';
|
||||
const VALUES = 'STORYBOOK_VALUES';
|
||||
|
||||
export const prepareForInline = (storyFn: StoryFn, { args }: StoryContext) => {
|
||||
export const prepareForInline = (
|
||||
storyFn: PartialStoryFn<VueFramework>,
|
||||
{ args }: StoryContext<VueFramework>
|
||||
) => {
|
||||
const component = storyFn();
|
||||
const el = React.useRef(null);
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
/* eslint no-underscore-dangle: ["error", { "allow": ["_vnode"] }] */
|
||||
|
||||
import { addons, StoryContext } from '@storybook/addons';
|
||||
import { StoryContext } from '@storybook/csf';
|
||||
import { addons } from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import prettier from 'prettier/standalone';
|
||||
import prettierHtml from 'prettier/parser-html';
|
||||
import type Vue from 'vue';
|
||||
import { VueFramework } from '@storybook/vue';
|
||||
|
||||
import { SourceType, SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
export const skipSourceRender = (context: StoryContext) => {
|
||||
export const skipSourceRender = (context: StoryContext<VueFramework>) => {
|
||||
const sourceParams = context?.parameters.docs?.source;
|
||||
const isArgsStory = context?.parameters.__isArgsStory;
|
||||
|
||||
@ -22,7 +24,7 @@ export const skipSourceRender = (context: StoryContext) => {
|
||||
return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
|
||||
};
|
||||
|
||||
export const sourceDecorator = (storyFn: any, context: StoryContext) => {
|
||||
export const sourceDecorator = (storyFn: any, context: StoryContext<VueFramework>) => {
|
||||
const story = storyFn();
|
||||
|
||||
// See ../react/jsxDecorator.tsx
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { StrictArgTypes } from '@storybook/csf';
|
||||
import { ArgTypesExtractor, hasDocgen, extractComponentProps } from '../../lib/docgen';
|
||||
import { convert } from '../../lib/convert';
|
||||
|
||||
@ -8,7 +8,7 @@ export const extractArgTypes: ArgTypesExtractor = (component) => {
|
||||
if (!hasDocgen(component)) {
|
||||
return null;
|
||||
}
|
||||
const results: ArgTypes = {};
|
||||
const results: StrictArgTypes = {};
|
||||
SECTIONS.forEach((section) => {
|
||||
const props = extractComponentProps(component, section);
|
||||
props.forEach(({ propDef, docgenInfo, jsDocTags }) => {
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import * as Vue from 'vue';
|
||||
import { StoryFn, StoryContext } from '@storybook/addons';
|
||||
import { app } from '@storybook/vue3';
|
||||
import { StoryContext, PartialStoryFn } from '@storybook/csf';
|
||||
import { app, VueFramework } from '@storybook/vue3';
|
||||
|
||||
// This is cast as `any` to workaround type errors caused by Vue 2 types
|
||||
const { render, h } = Vue as any;
|
||||
|
||||
export const prepareForInline = (storyFn: StoryFn, { args }: StoryContext) => {
|
||||
export const prepareForInline = (
|
||||
storyFn: PartialStoryFn<VueFramework>,
|
||||
{ args }: StoryContext<VueFramework>
|
||||
) => {
|
||||
const component = storyFn();
|
||||
|
||||
const vnode = h(component, args);
|
||||
|
@ -0,0 +1,448 @@
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"readme": "",
|
||||
"modules": [
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "demo-wc-card.js",
|
||||
"declarations": [],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "custom-element-definition",
|
||||
"name": "demo-wc-card",
|
||||
"declaration": {
|
||||
"name": "DemoWcCard",
|
||||
"module": "/src/stories/misc/to-update/DemoWcCard.js"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/typings.d.ts",
|
||||
"declarations": [],
|
||||
"exports": []
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/components/sb-button.ts",
|
||||
"declarations": [
|
||||
{
|
||||
"kind": "class",
|
||||
"description": "",
|
||||
"name": "SbButton",
|
||||
"cssProperties": [
|
||||
{
|
||||
"description": "Controls the color of bar",
|
||||
"name": "--sb-primary-color",
|
||||
"default": "#1ea7fd"
|
||||
}
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "primary",
|
||||
"type": {
|
||||
"text": "boolean"
|
||||
},
|
||||
"description": "Set button in primary mode",
|
||||
"privacy": "public"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "backgroundColor",
|
||||
"type": {
|
||||
"text": "string"
|
||||
},
|
||||
"privacy": "public"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "size",
|
||||
"type": {
|
||||
"text": "'small' | 'medium' | 'large'"
|
||||
},
|
||||
"default": "'medium'",
|
||||
"privacy": "public"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "label",
|
||||
"default": "''",
|
||||
"privacy": "public"
|
||||
},
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "onClick",
|
||||
"privacy": "private"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "sb-button:click",
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
},
|
||||
"description": "Custom event send when the button is clicked"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"type": {
|
||||
"text": "string"
|
||||
},
|
||||
"description": "Label of the button",
|
||||
"name": "label"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"text": "string"
|
||||
},
|
||||
"description": "Size of the button, can be \"small\", \"medium\" or \"large\"; default is \"medium\".",
|
||||
"name": "size"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"text": "string"
|
||||
},
|
||||
"description": "Color of the button's background",
|
||||
"name": "backgroundColor"
|
||||
},
|
||||
{
|
||||
"name": "label",
|
||||
"fieldName": "label"
|
||||
},
|
||||
{
|
||||
"name": "primary",
|
||||
"fieldName": "primary"
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"fieldName": "size"
|
||||
},
|
||||
{
|
||||
"name": "backgroundColor",
|
||||
"fieldName": "backgroundColor"
|
||||
}
|
||||
],
|
||||
"superclass": {
|
||||
"name": "LitElement",
|
||||
"package": "lit"
|
||||
},
|
||||
"tagName": "sb-button",
|
||||
"summary": "This is a simple Storybook Button",
|
||||
"customElement": true
|
||||
}
|
||||
],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "js",
|
||||
"name": "SbButton",
|
||||
"declaration": {
|
||||
"name": "SbButton",
|
||||
"module": "src/components/sb-button.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "custom-element-definition",
|
||||
"name": "sb-button",
|
||||
"declaration": {
|
||||
"name": "SbButton",
|
||||
"module": "src/components/sb-button.ts"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/components/sb-header.ts",
|
||||
"declarations": [
|
||||
{
|
||||
"kind": "class",
|
||||
"description": "",
|
||||
"name": "SbHeader",
|
||||
"members": [
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "user",
|
||||
"type": {
|
||||
"text": "{}"
|
||||
},
|
||||
"privacy": "public"
|
||||
},
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "dispatchCustomEvent",
|
||||
"privacy": "private",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "eventName",
|
||||
"type": {
|
||||
"text": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "logInOutButton",
|
||||
"privacy": "private"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
},
|
||||
"description": "Event send when user clicks on create account button",
|
||||
"name": "sb-header:createAccount"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
},
|
||||
"description": "Event send when user clicks on login button",
|
||||
"name": "sb-header:login"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
},
|
||||
"description": "Event send when user clicks on logout button",
|
||||
"name": "sb-header:logout"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"type": {
|
||||
"text": "Object"
|
||||
},
|
||||
"description": "User of the app",
|
||||
"name": "user"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"fieldName": "user"
|
||||
}
|
||||
],
|
||||
"superclass": {
|
||||
"name": "LitElement",
|
||||
"package": "lit"
|
||||
},
|
||||
"tagName": "sb-header",
|
||||
"summary": "This is a simple Storybook Header",
|
||||
"customElement": true
|
||||
}
|
||||
],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "js",
|
||||
"name": "SbHeader",
|
||||
"declaration": {
|
||||
"name": "SbHeader",
|
||||
"module": "src/components/sb-header.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "custom-element-definition",
|
||||
"name": "sb-header",
|
||||
"declaration": {
|
||||
"name": "SbHeader",
|
||||
"module": "src/components/sb-header.ts"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/components/sb-page.ts",
|
||||
"declarations": [
|
||||
{
|
||||
"kind": "class",
|
||||
"description": "",
|
||||
"name": "SbPage",
|
||||
"members": [
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "user",
|
||||
"type": {
|
||||
"text": "{}"
|
||||
},
|
||||
"privacy": "public"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"type": {
|
||||
"text": "Object"
|
||||
},
|
||||
"description": "User of the app",
|
||||
"name": "user"
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"fieldName": "user"
|
||||
}
|
||||
],
|
||||
"superclass": {
|
||||
"name": "LitElement",
|
||||
"package": "lit"
|
||||
},
|
||||
"tagName": "sb-page",
|
||||
"summary": "This is a simple Storybook Page",
|
||||
"customElement": true
|
||||
}
|
||||
],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "js",
|
||||
"name": "SbPage",
|
||||
"declaration": {
|
||||
"name": "SbPage",
|
||||
"module": "src/components/sb-page.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "custom-element-definition",
|
||||
"name": "sb-page",
|
||||
"declaration": {
|
||||
"name": "SbPage",
|
||||
"module": "src/components/sb-page.ts"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/stories/misc/to-update/DemoWcCard.js",
|
||||
"declarations": [
|
||||
{
|
||||
"kind": "class",
|
||||
"description": "This is a container looking like a card with a back and front side you can switch",
|
||||
"name": "DemoWcCard",
|
||||
"cssProperties": [
|
||||
{
|
||||
"description": "Header font size",
|
||||
"name": "--demo-wc-card-header-font-size"
|
||||
},
|
||||
{
|
||||
"description": "Font color for front",
|
||||
"name": "--demo-wc-card-front-color"
|
||||
},
|
||||
{
|
||||
"description": "Font color for back",
|
||||
"name": "--demo-wc-card-back-color"
|
||||
}
|
||||
],
|
||||
"cssParts": [
|
||||
{
|
||||
"description": "Front of the card",
|
||||
"name": "front"
|
||||
},
|
||||
{
|
||||
"description": "Back of the card",
|
||||
"name": "back"
|
||||
}
|
||||
],
|
||||
"slots": [
|
||||
{
|
||||
"description": "This is an unnamed slot (the default slot)",
|
||||
"name": ""
|
||||
}
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "toggle"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "backSide",
|
||||
"privacy": "public",
|
||||
"description": "Indicates that the back of the card is shown",
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "header",
|
||||
"privacy": "public",
|
||||
"description": "Header message",
|
||||
"default": "'Your Message'"
|
||||
},
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "rows",
|
||||
"privacy": "public",
|
||||
"description": "Data rows",
|
||||
"default": "[]"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "side-changed",
|
||||
"type": {
|
||||
"text": "CustomEvent"
|
||||
},
|
||||
"description": "Fires whenever it switches between front/back"
|
||||
}
|
||||
],
|
||||
"attributes": [
|
||||
{
|
||||
"name": "back-side",
|
||||
"fieldName": "backSide"
|
||||
},
|
||||
{
|
||||
"name": "header",
|
||||
"fieldName": "header"
|
||||
},
|
||||
{
|
||||
"name": "rows",
|
||||
"fieldName": "rows"
|
||||
}
|
||||
],
|
||||
"superclass": {
|
||||
"name": "LitElement",
|
||||
"package": "lit"
|
||||
},
|
||||
"tagName": "demo-wc-card",
|
||||
"customElement": true
|
||||
}
|
||||
],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "js",
|
||||
"name": "DemoWcCard",
|
||||
"declaration": {
|
||||
"name": "DemoWcCard",
|
||||
"module": "src/stories/misc/to-update/DemoWcCard.js"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "javascript-module",
|
||||
"path": "src/stories/misc/to-update/demoWcCardStyle.css.js",
|
||||
"declarations": [
|
||||
{
|
||||
"kind": "variable",
|
||||
"name": "demoWcCardStyle"
|
||||
}
|
||||
],
|
||||
"exports": [
|
||||
{
|
||||
"kind": "js",
|
||||
"name": "demoWcCardStyle",
|
||||
"declaration": {
|
||||
"name": "demoWcCardStyle",
|
||||
"module": "src/stories/misc/to-update/demoWcCardStyle.css.js"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -2,23 +2,6 @@
|
||||
|
||||
exports[`web-components component properties lit-element-demo-card 1`] = `
|
||||
Object {
|
||||
"": Object {
|
||||
"description": "This is an unnamed slot (the default slot)",
|
||||
"name": "",
|
||||
"required": false,
|
||||
"table": Object {
|
||||
"category": "slots",
|
||||
"defaultValue": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
"type": Object {
|
||||
"summary": undefined,
|
||||
},
|
||||
},
|
||||
"type": Object {
|
||||
"name": "void",
|
||||
},
|
||||
},
|
||||
"--demo-wc-card-back-color": Object {
|
||||
"description": "Font color for back",
|
||||
"name": "--demo-wc-card-back-color",
|
||||
@ -155,6 +138,15 @@ Object {
|
||||
"name": "string",
|
||||
},
|
||||
},
|
||||
"onSideChanged": Object {
|
||||
"action": Object {
|
||||
"name": "side-changed",
|
||||
},
|
||||
"name": "onSideChanged",
|
||||
"table": Object {
|
||||
"disable": true,
|
||||
},
|
||||
},
|
||||
"rows": Object {
|
||||
"description": "Data rows",
|
||||
"name": "rows",
|
||||
|
@ -1,29 +0,0 @@
|
||||
/* global window */
|
||||
import React from 'react';
|
||||
import { render } from 'lit-html';
|
||||
import { extractArgTypes, extractComponentDescription } from './custom-elements';
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
extractArgTypes,
|
||||
extractComponentDescription,
|
||||
inlineStories: true,
|
||||
prepareForInline: (storyFn) => {
|
||||
class Story extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.wrapperRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
render(storyFn(), this.wrapperRef.current);
|
||||
}
|
||||
|
||||
render() {
|
||||
return React.createElement('div', { ref: this.wrapperRef });
|
||||
}
|
||||
}
|
||||
return React.createElement(Story);
|
||||
},
|
||||
},
|
||||
};
|
19
addons/docs/src/frameworks/web-components/config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { extractArgTypes, extractComponentDescription } from './custom-elements';
|
||||
import { sourceDecorator } from './sourceDecorator';
|
||||
import { prepareForInline } from './prepareForInline';
|
||||
import { SourceType } from '../../shared';
|
||||
|
||||
export const decorators = [sourceDecorator];
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
extractArgTypes,
|
||||
extractComponentDescription,
|
||||
inlineStories: true,
|
||||
prepareForInline,
|
||||
source: {
|
||||
type: SourceType.DYNAMIC,
|
||||
language: 'html',
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import global from 'global';
|
||||
import { extractArgTypes } from './custom-elements';
|
||||
import customElementsManifest from './__testfixtures__/custom-elements.json';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__: any;
|
||||
}
|
||||
}
|
||||
|
||||
const { window } = global;
|
||||
|
||||
describe('extractArgTypes', () => {
|
||||
beforeEach(() => {
|
||||
window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = customElementsManifest;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__ = undefined;
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
it('should map to an action event handler', () => {
|
||||
const { onSbHeaderCreateAccount } = extractArgTypes('sb-header');
|
||||
|
||||
expect(onSbHeaderCreateAccount).toEqual({
|
||||
name: 'onSbHeaderCreateAccount',
|
||||
action: { name: 'sb-header:createAccount' },
|
||||
table: { disable: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('should map to a regular item', () => {
|
||||
const { 'sb-header:createAccount': item } = extractArgTypes('sb-header');
|
||||
|
||||
expect(item).toEqual({
|
||||
name: 'sb-header:createAccount',
|
||||
required: false,
|
||||
description: 'Event send when user clicks on create account button',
|
||||
type: { name: 'void' },
|
||||
table: {
|
||||
category: 'events',
|
||||
type: { summary: 'CustomEvent' },
|
||||
defaultValue: { summary: undefined },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,12 +1,13 @@
|
||||
import { getCustomElements, isValidComponent, isValidMetaData } from '@storybook/web-components';
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { ArgType, ArgTypes } from '@storybook/api';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
|
||||
interface TagItem {
|
||||
name: string;
|
||||
type: string;
|
||||
type: { [key: string]: any };
|
||||
description: string;
|
||||
default?: any;
|
||||
kind?: string;
|
||||
defaultValue?: any;
|
||||
}
|
||||
|
||||
@ -17,6 +18,7 @@ interface Tag {
|
||||
properties?: TagItem[];
|
||||
events?: TagItem[];
|
||||
methods?: TagItem[];
|
||||
members?: TagItem[];
|
||||
slots?: TagItem[];
|
||||
cssProperties?: TagItem[];
|
||||
cssParts?: TagItem[];
|
||||
@ -24,8 +26,17 @@ interface Tag {
|
||||
|
||||
interface CustomElements {
|
||||
tags: Tag[];
|
||||
modules?: [];
|
||||
}
|
||||
|
||||
interface Module {
|
||||
declarations?: [];
|
||||
exports?: [];
|
||||
}
|
||||
|
||||
interface Declaration {
|
||||
tagName: string;
|
||||
}
|
||||
interface Sections {
|
||||
attributes?: any;
|
||||
properties?: any;
|
||||
@ -38,25 +49,59 @@ interface Sections {
|
||||
function mapData(data: TagItem[], category: string) {
|
||||
return (
|
||||
data &&
|
||||
data.reduce((acc, item) => {
|
||||
const type = category === 'properties' ? { name: item.type } : { name: 'void' };
|
||||
acc[item.name] = {
|
||||
name: item.name,
|
||||
required: false,
|
||||
description: item.description,
|
||||
type,
|
||||
table: {
|
||||
category,
|
||||
type: { summary: item.type },
|
||||
defaultValue: { summary: item.default !== undefined ? item.default : item.defaultValue },
|
||||
},
|
||||
};
|
||||
return acc;
|
||||
}, {} as ArgTypes)
|
||||
data
|
||||
.filter((item) => item && item.name)
|
||||
.reduce((acc, item) => {
|
||||
if (item.kind === 'method') return acc;
|
||||
|
||||
switch (category) {
|
||||
case 'events':
|
||||
mapEvent(item).forEach((argType) => {
|
||||
acc[argType.name] = argType;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
acc[item.name] = mapItem(item, category);
|
||||
break;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {} as ArgTypes)
|
||||
);
|
||||
}
|
||||
|
||||
const getMetaData = (tagName: string, customElements: CustomElements) => {
|
||||
function mapItem(item: TagItem, category: string): ArgType {
|
||||
const type =
|
||||
category === 'properties' ? { name: item.type?.text || item.type } : { name: 'void' };
|
||||
|
||||
return {
|
||||
name: item.name,
|
||||
required: false,
|
||||
description: item.description,
|
||||
type,
|
||||
table: {
|
||||
category,
|
||||
type: { summary: item.type?.text || item.type },
|
||||
defaultValue: {
|
||||
summary: item.default !== undefined ? item.default : item.defaultValue,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapEvent(item: TagItem): ArgType[] {
|
||||
let name = item.name
|
||||
.replace(/(-|_|:|\.|\s)+(.)?/g, (_match, _separator, chr: string) => {
|
||||
return chr ? chr.toUpperCase() : '';
|
||||
})
|
||||
.replace(/^([A-Z])/, (match) => match.toLowerCase());
|
||||
|
||||
name = `on${name.charAt(0).toUpperCase() + name.substr(1)}`;
|
||||
|
||||
return [{ name, action: { name: item.name }, table: { disable: true } }, mapItem(item, 'events')];
|
||||
}
|
||||
|
||||
const getMetaDataExperimental = (tagName: string, customElements: CustomElements) => {
|
||||
if (!isValidComponent(tagName) || !isValidMetaData(customElements)) {
|
||||
return null;
|
||||
}
|
||||
@ -69,14 +114,34 @@ const getMetaData = (tagName: string, customElements: CustomElements) => {
|
||||
return metaData;
|
||||
};
|
||||
|
||||
const getMetaDataV1 = (tagName: string, customElements: CustomElements) => {
|
||||
if (!isValidComponent(tagName) || !isValidMetaData(customElements)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let metadata;
|
||||
customElements?.modules?.forEach((_module: Module) => {
|
||||
_module?.declarations?.forEach((declaration: Declaration) => {
|
||||
if (declaration.tagName === tagName) {
|
||||
metadata = declaration;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!metadata) {
|
||||
logger.warn(`Component not found in custom-elements.json: ${tagName}`);
|
||||
}
|
||||
return metadata;
|
||||
};
|
||||
|
||||
export const extractArgTypesFromElements = (tagName: string, customElements: CustomElements) => {
|
||||
const metaData = getMetaData(tagName, customElements);
|
||||
return (
|
||||
metaData && {
|
||||
...mapData(metaData.attributes, 'attributes'),
|
||||
...mapData(metaData.members, 'properties'),
|
||||
...mapData(metaData.properties, 'properties'),
|
||||
...mapData(metaData.events, 'events'),
|
||||
...mapData(metaData.methods, 'methods'),
|
||||
...mapData(metaData.slots, 'slots'),
|
||||
...mapData(metaData.cssProperties, 'css custom properties'),
|
||||
...mapData(metaData.cssParts, 'css shadow parts'),
|
||||
@ -84,8 +149,16 @@ export const extractArgTypesFromElements = (tagName: string, customElements: Cus
|
||||
);
|
||||
};
|
||||
|
||||
const getMetaData = (tagName: string, manifest: any) => {
|
||||
if (manifest?.version === 'experimental') {
|
||||
return getMetaDataExperimental(tagName, manifest);
|
||||
}
|
||||
return getMetaDataV1(tagName, manifest);
|
||||
};
|
||||
|
||||
export const extractArgTypes = (tagName: string) => {
|
||||
return extractArgTypesFromElements(tagName, getCustomElements());
|
||||
const cem = getCustomElements();
|
||||
return extractArgTypesFromElements(tagName, cem);
|
||||
};
|
||||
|
||||
export const extractComponentDescription = (tagName: string) => {
|
||||
|
@ -0,0 +1,21 @@
|
||||
import type { PartialStoryFn } from '@storybook/csf';
|
||||
import { WebComponentsFramework } from '@storybook/web-components';
|
||||
import React from 'react';
|
||||
|
||||
import { render } from 'lit-html';
|
||||
|
||||
export const prepareForInline = (storyFn: PartialStoryFn<WebComponentsFramework>) => {
|
||||
class Story extends React.Component {
|
||||
wrapperRef = React.createRef<HTMLElement>();
|
||||
|
||||
componentDidMount(): void {
|
||||
render(storyFn(), this.wrapperRef.current);
|
||||
}
|
||||
|
||||
render(): React.ReactElement {
|
||||
return React.createElement('div', { ref: this.wrapperRef });
|
||||
}
|
||||
}
|
||||
|
||||
return (React.createElement(Story) as unknown) as React.CElement<{}, React.Component>;
|
||||
};
|
@ -0,0 +1,108 @@
|
||||
import { html } from 'lit-html';
|
||||
import { styleMap } from 'lit-html/directives/style-map';
|
||||
import { addons, StoryContext, useEffect } from '@storybook/addons';
|
||||
import { sourceDecorator } from './sourceDecorator';
|
||||
import { SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
jest.mock('@storybook/addons');
|
||||
const mockedAddons = addons as jest.Mocked<typeof addons>;
|
||||
const mockedUseEffect = useEffect as jest.Mocked<typeof useEffect>;
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
print: (val: any) => val,
|
||||
test: (val) => typeof val === 'string',
|
||||
});
|
||||
|
||||
const tick = () => new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
const makeContext = (name: string, parameters: any, args: any, extra?: object): StoryContext => ({
|
||||
id: `lit-test--${name}`,
|
||||
kind: 'js-text',
|
||||
name,
|
||||
parameters,
|
||||
args,
|
||||
argTypes: {},
|
||||
globals: {},
|
||||
...extra,
|
||||
});
|
||||
|
||||
describe('sourceDecorator', () => {
|
||||
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
|
||||
beforeEach(() => {
|
||||
mockedAddons.getChannel.mockReset();
|
||||
mockedUseEffect.mockImplementation((cb) => setTimeout(cb, 0));
|
||||
|
||||
mockChannel = { on: jest.fn(), emit: jest.fn() };
|
||||
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
|
||||
});
|
||||
|
||||
it('should render dynamically for args stories', async () => {
|
||||
const storyFn = (args: any) => html`<div>args story</div>`;
|
||||
const context = makeContext('args', { __isArgsStory: true }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'lit-test--args',
|
||||
'<div>args story</div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip dynamic rendering for no-args stories', async () => {
|
||||
const storyFn = () => html`<div>classic story</div>`;
|
||||
const context = makeContext('classic', {}, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use the originalStoryFn if excludeDecorators is set', async () => {
|
||||
const storyFn = (args: any) => html`<div>args story</div>`;
|
||||
const decoratedStoryFn = (args: any) => html`
|
||||
<div style=${styleMap({ padding: `${25}px`, border: '3px solid red' })}>${storyFn(args)}</div>
|
||||
`;
|
||||
const context = makeContext(
|
||||
'args',
|
||||
{
|
||||
__isArgsStory: true,
|
||||
docs: {
|
||||
source: {
|
||||
excludeDecorators: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ originalStoryFn: storyFn }
|
||||
);
|
||||
sourceDecorator(decoratedStoryFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'lit-test--args',
|
||||
'<div>args story</div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('allows the snippet output to be modified by transformSource', async () => {
|
||||
const storyFn = (args: any) => html`<div>args story</div>`;
|
||||
const transformSource = (dom: string) => `<p>${dom}</p>`;
|
||||
const docs = { transformSource };
|
||||
const context = makeContext('args', { __isArgsStory: true, docs }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
await tick();
|
||||
expect(mockChannel.emit).toHaveBeenCalledWith(
|
||||
SNIPPET_RENDERED,
|
||||
'lit-test--args',
|
||||
'<p><div>args story</div></p>'
|
||||
);
|
||||
});
|
||||
|
||||
it('provides the story context to transformSource', () => {
|
||||
const storyFn = (args: any) => html`<div>args story</div>`;
|
||||
const transformSource = jest.fn((x) => x);
|
||||
const docs = { transformSource };
|
||||
const context = makeContext('args', { __isArgsStory: true, docs }, {});
|
||||
sourceDecorator(storyFn, context);
|
||||
expect(transformSource).toHaveBeenCalledWith('<div>args story</div>', context);
|
||||
});
|
||||
});
|
51
addons/docs/src/frameworks/web-components/sourceDecorator.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/* global window */
|
||||
import { render } from 'lit-html';
|
||||
import { ArgsStoryFn, PartialStoryFn, StoryContext } from '@storybook/csf';
|
||||
import { addons, useEffect } from '@storybook/addons';
|
||||
import { WebComponentsFramework } from '@storybook/web-components';
|
||||
|
||||
import { SNIPPET_RENDERED, SourceType } from '../../shared';
|
||||
|
||||
function skipSourceRender(context: StoryContext<WebComponentsFramework>) {
|
||||
const sourceParams = context?.parameters.docs?.source;
|
||||
const isArgsStory = context?.parameters.__isArgsStory;
|
||||
|
||||
// always render if the user forces it
|
||||
if (sourceParams?.type === SourceType.DYNAMIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// never render if the user is forcing the block to render code, or
|
||||
// if the user provides code, or if it's not an args story.
|
||||
return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE;
|
||||
}
|
||||
|
||||
function applyTransformSource(
|
||||
source: string,
|
||||
context: StoryContext<WebComponentsFramework>
|
||||
): string {
|
||||
const { transformSource } = context.parameters.docs ?? {};
|
||||
if (typeof transformSource !== 'function') return source;
|
||||
return transformSource(source, context);
|
||||
}
|
||||
|
||||
export function sourceDecorator(
|
||||
storyFn: PartialStoryFn<WebComponentsFramework>,
|
||||
context: StoryContext<WebComponentsFramework>
|
||||
) {
|
||||
const story = context?.parameters.docs?.source?.excludeDecorators
|
||||
? (context.originalStoryFn as ArgsStoryFn<WebComponentsFramework>)(context.args, context)
|
||||
: storyFn();
|
||||
|
||||
let source: string;
|
||||
useEffect(() => {
|
||||
if (source) addons.getChannel().emit(SNIPPET_RENDERED, context.id, source);
|
||||
});
|
||||
if (!skipSourceRender(context)) {
|
||||
const container = window.document.createElement('div');
|
||||
render(story, container);
|
||||
source = applyTransformSource(container.innerHTML.replace(/<!---->/g, ''), context);
|
||||
}
|
||||
|
||||
return story;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { SBType } from '@storybook/client-api';
|
||||
import { SBType } from '@storybook/csf';
|
||||
import { FlowType, FlowSigType, FlowLiteralType } from './types';
|
||||
|
||||
const isLiteral = (type: FlowType) => type.name === 'literal';
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { SBType } from '@storybook/client-api';
|
||||
import { SBType } from '@storybook/csf';
|
||||
import { PTType } from './types';
|
||||
import { trimQuotes } from '../utils';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { SBType } from '@storybook/client-api';
|
||||
import { SBType } from '@storybook/csf';
|
||||
import { TSType, TSSigType } from './types';
|
||||
|
||||
const convertSig = (type: TSSigType) => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ArgTypes } from '@storybook/api';
|
||||
import { StrictArgTypes } from '@storybook/csf';
|
||||
import { PropDef } from './PropDef';
|
||||
import { Component } from '../../blocks/types';
|
||||
|
||||
export type PropsExtractor = (component: Component) => { rows?: PropDef[] } | null;
|
||||
|
||||
export type ArgTypesExtractor = (component: Component) => ArgTypes | null;
|
||||
export type ArgTypesExtractor = (component: Component) => StrictArgTypes | null;
|
||||
|
||||
export interface DocgenType {
|
||||
name: string;
|
||||
|
@ -12,10 +12,10 @@
|
||||
- Add to your `.storybook/preview.js`
|
||||
|
||||
```js
|
||||
import { setCustomElements } from '@storybook/web-components';
|
||||
import { setCustomElementsManifest } from '@storybook/web-components';
|
||||
import customElements from '../custom-elements.json';
|
||||
|
||||
setCustomElements(customElements);
|
||||
setCustomElementsManifest(customElements);
|
||||
```
|
||||
|
||||
- Add to your story files
|
||||
@ -33,8 +33,12 @@ In order to get [Props tables](..docs/../../docs/props-tables.md) documentation
|
||||
|
||||
You can hand write it or better generate it. Depending on the web components sugar you are choosing your milage may vary.
|
||||
|
||||
Known analyzers that output `custom-elements.json`:
|
||||
Known analyzers that output `custom-elements.json` v1.0.0:
|
||||
|
||||
- [@custom-elements-manifest/analyzer](https://github.com/open-wc/custom-elements-manifest)
|
||||
- Supports Vanilla, LitElement, FASTElement, Stencil, Catalyst, Atomico
|
||||
|
||||
Known analyzers that output older versions of `custom-elements.json`:
|
||||
- [web-component-analyzer](https://github.com/runem/web-component-analyzer)
|
||||
- Supports LitElement, Polymer, Vanilla, (Stencil)
|
||||
- [stenciljs](https://stenciljs.com/)
|
||||
@ -53,22 +57,51 @@ The file looks something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"tags": [
|
||||
"schemaVersion": "1.0.0",
|
||||
"readme": "",
|
||||
"modules": [
|
||||
{
|
||||
"name": "demo-wc-card",
|
||||
"properties": [
|
||||
"kind": "javascript-module",
|
||||
"path": "src/my-element.js",
|
||||
"declarations": [
|
||||
{
|
||||
"name": "header",
|
||||
"type": "String",
|
||||
"attribute": "header",
|
||||
"description": "Shown at the top of the card",
|
||||
"default": "Your Message"
|
||||
"kind": "class",
|
||||
"description": "",
|
||||
"name": "MyElement",
|
||||
"members": [
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "disabled"
|
||||
},
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "fire"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "disabled-changed",
|
||||
"type": {
|
||||
"text": "Event"
|
||||
}
|
||||
}
|
||||
],
|
||||
"superclass": {
|
||||
"name": "HTMLElement"
|
||||
},
|
||||
"tagName": "my-element"
|
||||
}
|
||||
],
|
||||
"events": [],
|
||||
"slots": [],
|
||||
"cssProperties": []
|
||||
"exports": [
|
||||
{
|
||||
"kind": "custom-element-definition",
|
||||
"name": "my-element",
|
||||
"declaration": {
|
||||
"name": "MyElement",
|
||||
"module": "src/my-element.js"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@storybook/addon-essentials",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Curated addons to bring out the best of Storybook",
|
||||
"keywords": [
|
||||
"addon",
|
||||
"essentials",
|
||||
"storybook"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/essentials",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/essentials",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -26,7 +26,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -39,31 +39,31 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addon-actions": "6.4.0-alpha.0",
|
||||
"@storybook/addon-backgrounds": "6.4.0-alpha.0",
|
||||
"@storybook/addon-controls": "6.4.0-alpha.0",
|
||||
"@storybook/addon-docs": "6.4.0-alpha.0",
|
||||
"@storybook/addon-measure": "^1.2.3",
|
||||
"@storybook/addon-toolbars": "6.4.0-alpha.0",
|
||||
"@storybook/addon-viewport": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/node-logger": "6.4.0-alpha.0",
|
||||
"@storybook/addon-actions": "6.4.0-beta.3",
|
||||
"@storybook/addon-backgrounds": "6.4.0-beta.3",
|
||||
"@storybook/addon-controls": "6.4.0-beta.3",
|
||||
"@storybook/addon-docs": "6.4.0-beta.3",
|
||||
"@storybook/addon-measure": "6.4.0-beta.3",
|
||||
"@storybook/addon-outline": "6.4.0-beta.3",
|
||||
"@storybook/addon-toolbars": "6.4.0-beta.3",
|
||||
"@storybook/addon-viewport": "6.4.0-beta.3",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/node-logger": "6.4.0-beta.3",
|
||||
"core-js": "^3.8.2",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"storybook-addon-outline": "^1.3.3",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@storybook/vue": "6.4.0-alpha.0",
|
||||
"@storybook/vue": "6.4.0-beta.3",
|
||||
"@types/jest": "^26.0.16",
|
||||
"@types/webpack-env": "^1.16.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@storybook/vue": "6.4.0-alpha.0",
|
||||
"@storybook/web-components": "6.4.0-alpha.0",
|
||||
"@storybook/vue": "6.4.0-beta.3",
|
||||
"@storybook/web-components": "6.4.0-beta.3",
|
||||
"babel-loader": "^8.0.0",
|
||||
"lit-html": "^1.4.1 || ^2.0.0-rc.3",
|
||||
"react": "^16.8.0 || ^17.0.0",
|
||||
@ -93,6 +93,6 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js"
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export function addons(options: PresetOptions = {}) {
|
||||
return (
|
||||
['docs', 'controls', 'actions', 'backgrounds', 'viewport', 'toolbars', 'measure', 'outline']
|
||||
.filter((key) => (options as any)[key] !== false)
|
||||
.map((key) => (key === 'outline' ? `storybook-addon-${key}` : `@storybook/addon-${key}`))
|
||||
.map((key) => `@storybook/addon-${key}`)
|
||||
.filter((addon) => !checkInstalled(addon, main))
|
||||
// Use `require.resolve` to ensure Yarn PnP compatibility
|
||||
// Files of various addons should be resolved in the context of `addon-essentials` as they are listed as deps here
|
||||
|
@ -1,28 +1,36 @@
|
||||
# Storybook addon Jest
|
||||
|
||||
Brings Jest results in storybook.
|
||||
Storybook addon for inspecting Jest unit test results.
|
||||
|
||||
[Framework Support](https://github.com/storybookjs/storybook/blob/main/ADDONS_SUPPORT.md)
|
||||
|
||||
[](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel)
|
||||
|
||||
> Checkout the above [Live Storybook](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel).
|
||||
> Check out the above [Live Storybook](http://storybooks-official.netlify.com/?selectedKind=Addons%7Cjest&selectedStory=withTests&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Ftests%2Fpanel).
|
||||
|
||||
## Getting started
|
||||
## Installation
|
||||
|
||||
### Install
|
||||
Install this addon by adding the `@storybook/addon-jest` as a development dependency with:
|
||||
|
||||
`npm install --save-dev @storybook/addon-jest`
|
||||
|
||||
or
|
||||
Or if you're using yarn as a package manager:
|
||||
|
||||
`yarn add --dev @storybook/addon-jest`
|
||||
|
||||
### Jest Configuration
|
||||
## Configuration
|
||||
|
||||
When running **Jest**, be sure to save the results in a json file:
|
||||
Register the addon in your [`.storybook/main.js`](https://storybook.js.org/docs/react/configure/overview#configure-your-storybook-project):
|
||||
|
||||
`package.json`
|
||||
```js
|
||||
module.exports = {
|
||||
addons: ['@storybook/addon-jest'],
|
||||
};
|
||||
```
|
||||
|
||||
## Jest Configuration
|
||||
|
||||
When running **Jest**, be sure to save the results in a JSON file:
|
||||
|
||||
```js
|
||||
"scripts": {
|
||||
@ -30,30 +38,27 @@ When running **Jest**, be sure to save the results in a json file:
|
||||
}
|
||||
```
|
||||
|
||||
You may want to add it the result file to `.gitignore`, since it's a generated file:
|
||||
You may want to add the result file to `.gitignore`, since it's a generated file:
|
||||
|
||||
```
|
||||
.jest-test-results.json
|
||||
```
|
||||
|
||||
But much like lockfiles and snapshots checking-in generated files can have certain advantages as well. It's up to you.
|
||||
We recommend to **do** check in the test results file so starting storybook from an clean git clone doesn't require running all tests first,
|
||||
but this can mean you'll experience merge conflicts on this file in the future. (_re-generating this file is very similar to re-generating lockfiles and snapshots_)
|
||||
But much like lockfiles and snapshots, checking-in generated files can have certain advantages as well. It's up to you.
|
||||
We recommend to **do** check in the test results file so starting Storybook from a clean git clone doesn't require running all tests first,
|
||||
but this can mean you'll encounter merge conflicts on this file in the future (_re-generating this file is very similar to re-generating lockfiles and snapshots_).
|
||||
|
||||
## Generating the test results
|
||||
### Generating the test results
|
||||
|
||||
You need to make sure the generated test-results file exists before you start storybook.
|
||||
During development you will likely start jest in watch-mode
|
||||
and so the json file will be re-generated every time code or tests change.
|
||||
Ensure the generated test-results file exists before you start Storybook. During development, you will likely start Jest in watch-mode and so the JSON file will be re-generated every time code or tests change.
|
||||
|
||||
```sh
|
||||
npm run test:generate-output -- --watch
|
||||
```
|
||||
|
||||
This change will then be HMR (hot module reloaded) using webpack and displayed by this addon.
|
||||
This change will then be HMR (hot module reloaded) using Webpack and displayed by the addon.
|
||||
|
||||
If you want to pre-run jest automatically during development or a static build,
|
||||
you may need to consider that if your tests fail, the script receives a non-0 exit code and will exit.
|
||||
If you want to pre-run Jest automatically during development or a static build, you may need to consider that if your tests fail, the script receives a non-0 exit code and will exit.
|
||||
You could create a `prebuild:storybook` npm script, which will never fail by appending `|| true`:
|
||||
|
||||
```json
|
||||
@ -67,125 +72,175 @@ You could create a `prebuild:storybook` npm script, which will never fail by app
|
||||
}
|
||||
```
|
||||
|
||||
### Register
|
||||
|
||||
within `.storybook/main.js`:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
addons: ['@storybook/addon-jest'],
|
||||
};
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Assuming that you have created test files `MyComponent.test.js` and `MyOtherComponent.test.js`
|
||||
Assuming that you have already created a test file for your component (e.g., `MyComponent.test.js`).
|
||||
|
||||
In your `story.js`
|
||||
### Story-level
|
||||
|
||||
In your story file, add a [decorator](https://storybook.js.org/docs/react/writing-stories/decorators) to your story's default export to display the results:
|
||||
|
||||
```js
|
||||
// MyComponent.stories.js | MyComponent.stories.jsx
|
||||
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
import results from '../.jest-test-results.json';
|
||||
|
||||
import { withTests } from '@storybook/addon-jest';
|
||||
|
||||
export default {
|
||||
component: MyComponent,
|
||||
title: 'MyComponent',
|
||||
decorators: [withTests({ results })],
|
||||
};
|
||||
```
|
||||
|
||||
export const defaultView = () => <div>Jest results in storybook</div>;
|
||||
defaultView.parameters = {
|
||||
jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'],
|
||||
You can also add multiple tests results within your story by including the `jest` [parameter](https://storybook.js.org/docs/react/writing-stories/parameters), for example:
|
||||
|
||||
```js
|
||||
// MyComponent.stories.js | MyComponent.stories.jsx
|
||||
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
import results from '../.jest-test-results.json';
|
||||
|
||||
import { withTests } from '@storybook/addon-jest';
|
||||
|
||||
export default {
|
||||
component: MyComponent,
|
||||
title: 'MyComponent',
|
||||
decorators: [withTests({ results })],
|
||||
};
|
||||
|
||||
const Template = (args) => <MyComponent {....args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
text: 'Jest results in Storybook',
|
||||
};
|
||||
Default.parameters = {
|
||||
jest: ['MyComponent.test.js', 'MyOtherComponent.test.js']
|
||||
};
|
||||
```
|
||||
|
||||
Or in order to avoid importing `.jest-test-results.json` in each story, add the decorator in your `.storybook/preview.js` and results will display for stories that you have set the `jest` parameter on:
|
||||
### Global level
|
||||
|
||||
To avoid importing the results of the tests in each story, you can update
|
||||
your [`.storybook/preview.js`](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering) and include a decorator allowing you to display the results only for the stories that have the `jest` parameter defined:
|
||||
|
||||
```js
|
||||
import { addDecorator } from '@storybook/react'; // <- or your view layer
|
||||
import { withTests } from '@storybook/addon-jest';
|
||||
// .storybook/preview.js
|
||||
|
||||
import { withTests } from "@storybook/addon-jest";
|
||||
|
||||
import results from '../.jest-test-results.json';
|
||||
|
||||
addDecorator(
|
||||
export const decorators = [
|
||||
withTests({
|
||||
results,
|
||||
})
|
||||
);
|
||||
}),
|
||||
];
|
||||
```
|
||||
|
||||
Then in your story:
|
||||
Then in your story file:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
// MyComponent.stories.js | MyComponent.stories.jsx
|
||||
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
export default {
|
||||
component: MyComponent,
|
||||
title: 'MyComponent',
|
||||
};
|
||||
|
||||
export const defaultView = () => <div>Jest results in storybook</div>;
|
||||
defaultView.parameters = {
|
||||
jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'],
|
||||
const Template = (args) => <MyComponent {....args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args={
|
||||
text: 'Jest results in Storybook',
|
||||
};
|
||||
Default.parameters = {
|
||||
jest: ['MyComponent.test.js'],
|
||||
};
|
||||
```
|
||||
|
||||
The jest parameter will default to inferring from your story file name if not provided. For example, if your story file is `MyComponent.stories.js`,
|
||||
then "MyComponent" will be used to find your test file results. This currently doesn't work in production environments.
|
||||
The `jest` parameter will default to inferring from your story file name if not provided. For example, if your story file is `MyComponent.stories.js`,
|
||||
then "MyComponent" will be used to find your test file results. It currently doesn't work in production environments.
|
||||
|
||||
### Disabling
|
||||
|
||||
You can disable the addon for a single story by setting the `jest` parameter to `{disable: true}`:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
export default {
|
||||
component: MyComponent,
|
||||
title: 'MyComponent',
|
||||
};
|
||||
|
||||
export const defaultView = () => <div>Jest results in storybook</div>;
|
||||
defaultView.parameters = {
|
||||
const Template = (args) => <MyComponent {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args={
|
||||
text: 'Jest results in Storybook',
|
||||
};
|
||||
Default.parameters = {
|
||||
jest: { disable: true },
|
||||
};
|
||||
```
|
||||
|
||||
### withTests(options)
|
||||
|
||||
- **options.results**: OBJECT jest output results. _mandatory_
|
||||
- **filesExt**: STRING test file extension. _optional_. This allows you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That means it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...
|
||||
|
||||
## Usage with Angular
|
||||
|
||||
Assuming that you have created test files `my.component.spec.ts` and `my-other.component.spec.ts`
|
||||
Using this addon with Angular will require some additional configuration. You'll need to install and configure Jest with [jest-preset-angular](https://www.npmjs.com/package/jest-preset-angular).
|
||||
|
||||
Configure Jest with [jest-preset-angular](https://www.npmjs.com/package/jest-preset-angular)
|
||||
Then, in your `.storybook/preview.js`, you'll need to add a decorator with the following:
|
||||
|
||||
In project's `typings.d.ts` add
|
||||
|
||||
```ts
|
||||
declare module '*.json' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
```
|
||||
|
||||
In your `.storybook/preview.ts`:
|
||||
|
||||
```ts
|
||||
import { addDecorator } from '@storybook/angular';
|
||||
```js
|
||||
import { withTests } from '@storybook/addon-jest';
|
||||
|
||||
import results from '../.jest-test-results.json';
|
||||
|
||||
addDecorator(
|
||||
export const decorators = [
|
||||
withTests({
|
||||
results,
|
||||
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$',
|
||||
})
|
||||
);
|
||||
filesExt: "((\\.specs?)|(\\.tests?))?(\\.ts)?$",
|
||||
}),
|
||||
];
|
||||
```
|
||||
|
||||
Finally, in your story, you'll need to include the following:
|
||||
|
||||
```ts
|
||||
import { Meta, Story } from '@storybook/angular/types-6-0';
|
||||
|
||||
import MyComponent from './MyComponent.component';
|
||||
|
||||
export default {
|
||||
component: MyComponent,
|
||||
title: 'MyComponent',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<MyComponent> = (args: MyComponent) => ({
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.parameters = {
|
||||
jest: ['MyComponent.component'],
|
||||
};
|
||||
```
|
||||
|
||||
##### Example [here](https://github.com/storybookjs/storybook/tree/main/examples/angular-cli)
|
||||
|
||||
## Available options
|
||||
|
||||
- **options.results**: OBJECT jest output results. _mandatory_
|
||||
- **filesExt**: STRING test file extension. _optional_. This allows you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That means it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Add coverage
|
||||
@ -197,7 +252,7 @@ addDecorator(
|
||||
|
||||
## Contributing
|
||||
|
||||
All ideas and contributions are welcomed.
|
||||
All ideas and contributions are welcome.
|
||||
|
||||
## Licence
|
||||
|
||||
|
Before Width: | Height: | Size: 783 KiB After Width: | Height: | Size: 644 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@storybook/addon-jest",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "React storybook addon that show component jest report",
|
||||
"keywords": [
|
||||
"addon",
|
||||
@ -12,7 +12,7 @@
|
||||
"unit-testing",
|
||||
"test"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/jest",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/jest",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -32,7 +32,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -47,11 +47,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/api": "6.4.0-alpha.0",
|
||||
"@storybook/components": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/theming": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/api": "6.4.0-beta.3",
|
||||
"@storybook/components": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/theming": "6.4.0-beta.3",
|
||||
"core-js": "^3.8.2",
|
||||
"global": "^4.4.0",
|
||||
"react-sizeme": "^3.0.1",
|
||||
@ -76,7 +76,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Jest",
|
||||
|
@ -143,4 +143,4 @@ It accepts all the props the `a` element does, plus `story` and `kind`. It the `
|
||||
</LinkTo>
|
||||
```
|
||||
|
||||
To implement such a component for another framework, you need to add special handling for `click` event on native `a` element. See [`RoutedLink` sources](https://github.com/storybookjs/storybook/blob/main/addons/links/src/react/components/RoutedLink.js#L20-L24) for reference.
|
||||
To implement such a component for another framework, you need to add special handling for `click` event on native `a` element. See [`RoutedLink` sources](https://github.com/storybookjs/storybook/blob/main/addons/links/src/react/components/RoutedLink.tsx) for reference.
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@storybook/addon-links",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"version": "6.4.0-beta.3",
|
||||
"description": "Link stories together to build demos and prototypes with your UI components",
|
||||
"keywords": [
|
||||
"addon",
|
||||
"storybook",
|
||||
"organize"
|
||||
],
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/master/addons/links",
|
||||
"homepage": "https://github.com/storybookjs/storybook/tree/main/addons/links",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybookjs/storybook/issues"
|
||||
},
|
||||
@ -26,7 +26,7 @@
|
||||
"types": "dist/ts3.9/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<3.8": {
|
||||
"*": [
|
||||
"dist/ts3.9/*": [
|
||||
"dist/ts3.4/*"
|
||||
]
|
||||
}
|
||||
@ -41,11 +41,11 @@
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "6.4.0-alpha.0",
|
||||
"@storybook/client-logger": "6.4.0-alpha.0",
|
||||
"@storybook/core-events": "6.4.0-alpha.0",
|
||||
"@storybook/csf": "0.0.1",
|
||||
"@storybook/router": "6.4.0-alpha.0",
|
||||
"@storybook/addons": "6.4.0-beta.3",
|
||||
"@storybook/client-logger": "6.4.0-beta.3",
|
||||
"@storybook/core-events": "6.4.0-beta.3",
|
||||
"@storybook/csf": "0.0.2--canary.6aca495.0",
|
||||
"@storybook/router": "6.4.0-beta.3",
|
||||
"@types/qs": "^6.9.5",
|
||||
"core-js": "^3.8.2",
|
||||
"global": "^4.4.0",
|
||||
@ -72,7 +72,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "32441319e4b2550b1887ebf38bf228123ab1dfcd",
|
||||
"gitHead": "0fc9200599c97a5797c5af886792200cd29e2046",
|
||||
"sbmodern": "dist/modern/index.js",
|
||||
"storybook": {
|
||||
"displayName": "Links",
|
||||
|