Merge branch 'next' into shilman/default-to-react-docgen

This commit is contained in:
Michael Shilman 2023-10-22 20:03:03 +08:00
commit 4aa689e992
254 changed files with 4022 additions and 2813 deletions

View File

@ -17,7 +17,7 @@ executors:
default: 'small'
working_directory: /tmp/storybook
docker:
- image: cimg/node:16.20.0
- image: cimg/node:18.18.0
environment:
NODE_OPTIONS: --max_old_space_size=6144
resource_class: <<parameters.class>>
@ -30,7 +30,7 @@ executors:
default: 'small'
working_directory: /tmp/storybook
docker:
- image: cimg/node:16.20.0-browsers
- image: cimg/node:18.18.0-browsers
environment:
NODE_OPTIONS: --max_old_space_size=6144
resource_class: <<parameters.class>>
@ -555,19 +555,19 @@ workflows:
requires:
- unit-tests
- create-sandboxes:
parallelism: 20
parallelism: 21
requires:
- build
- build-sandboxes:
parallelism: 20
parallelism: 21
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 17
parallelism: 18
requires:
- build-sandboxes
- e2e-production:
parallelism: 17
parallelism: 18
requires:
- build-sandboxes
- e2e-dev:
@ -575,7 +575,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 17
parallelism: 18
requires:
- build-sandboxes
- bench:
@ -609,22 +609,22 @@ workflows:
requires:
- build
- create-sandboxes:
parallelism: 33
parallelism: 36
requires:
- build
# - smoke-test-sandboxes: # disabled for now
# requires:
# - create-sandboxes
- build-sandboxes:
parallelism: 33
parallelism: 36
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 30
parallelism: 33
requires:
- build-sandboxes
- e2e-production:
parallelism: 30
parallelism: 33
requires:
- build-sandboxes
- e2e-dev:
@ -632,7 +632,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 30
parallelism: 33
requires:
- build-sandboxes
# TODO: reenable once we find out the source of flakyness

View File

@ -19,12 +19,12 @@ jobs:
YARN_ENABLE_IMMUTABLE_INSTALLS: false
CLEANUP_SANDBOX_NODE_MODULES: true
steps:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/checkout@v3
with:
ref: main
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Setup git user
run: |
git config --global user.name "Storybook Bot"
@ -43,7 +43,7 @@ jobs:
run: yarn wait-on http://localhost:6001
working-directory: ./code
- name: Generate
run: yarn generate-sandboxes --local-registry --exclude=angular-cli/prerelease
run: yarn generate-sandboxes --local-registry
working-directory: ./code
- name: Publish
run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=main

View File

@ -19,12 +19,12 @@ jobs:
YARN_ENABLE_IMMUTABLE_INSTALLS: false
CLEANUP_SANDBOX_NODE_MODULES: true
steps:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/checkout@v3
with:
ref: next
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Setup git user
run: |
git config --global user.name "Storybook Bot"
@ -43,7 +43,7 @@ jobs:
run: yarn wait-on http://localhost:6001
working-directory: ./code
- name: Generate
run: yarn generate-sandboxes --local-registry --exclude=angular-cli/prerelease --debug
run: yarn generate-sandboxes --local-registry --debug
working-directory: ./code
- name: Publish
run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/sandboxes.git --push --branch=next

View File

@ -1,5 +1,5 @@
name: Prepare prerelease PR
run-name: Prepare prerelease PR, triggered by ${{ github.triggering_actor }}
name: Prepare non-patch PR
run-name: Prepare non-patch PR, triggered by ${{ github.triggering_actor }}
on:
push:
@ -34,8 +34,8 @@ concurrency:
cancel-in-progress: true
jobs:
prepare-prerelease-pull-request:
name: Prepare prerelease pull request
prepare-non-patch-pull-request:
name: Prepare non-patch pull request
runs-on: ubuntu-latest
environment: release
defaults:
@ -93,13 +93,14 @@ jobs:
run: git fetch --tags origin
- name: Check for unreleased changes
if: github.event_name != 'workflow_dispatch'
id: unreleased-changes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn release:unreleased-changes-exists
- name: Cancel when no release necessary
if: steps.unreleased-changes.outputs.has-changes-to-release == 'false'
if: steps.unreleased-changes.outputs.has-changes-to-release == 'false' && github.event_name != 'workflow_dispatch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# From https://stackoverflow.com/a/75809743
@ -112,21 +113,38 @@ jobs:
run: |
yarn release:version --deferred --release-type ${{ inputs.release-type || 'prerelease' }} ${{ inputs.pre-id && format('{0} {1}', '--pre-id', inputs.pre-id) || '' }} --verbose
- name: Check release vs prerelease
id: is-prerelease
run: yarn release:is-prerelease ${{ steps.bump-version.outputs.next-version }} --verbose
- name: Write changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
yarn release:write-changelog ${{ steps.bump-version.outputs.next-version }} --verbose
- name: 'Commit changes to branch: version-prerelease-from-${{ steps.bump-version.outputs.current-version }}'
- name: 'Commit changes to branch: version-non-patch-from-${{ steps.bump-version.outputs.current-version }}'
working-directory: .
run: |
git config --global user.name 'storybook-bot'
git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
git checkout -b version-prerelease-from-${{ steps.bump-version.outputs.current-version }}
git checkout -b version-non-patch-from-${{ steps.bump-version.outputs.current-version }}
git add .
git commit -m "Write changelog for ${{ steps.bump-version.outputs.next-version }}" || true
git push --force origin version-prerelease-from-${{ steps.bump-version.outputs.current-version }}
git commit -m "Write changelog for ${{ steps.bump-version.outputs.next-version }} [skip ci]" || true
git push --force origin version-non-patch-from-${{ steps.bump-version.outputs.current-version }}
- name: Resolve merge-conflicts with base branch
if: steps.is-prerelease.outputs.prerelease == 'false'
working-directory: .
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config pull.rebase false
git pull --no-commit --no-ff origin latest-release || true
git checkout --ours .
git add .
git commit --no-verify -m "Merge latest-release into version-non-patch-from-${{ steps.bump-version.outputs.current-version }} with conflicts resolved to ours [skip ci]"
git push origin version-non-patch-from-${{ steps.bump-version.outputs.current-version }}
- name: Generate PR description
id: description
@ -144,14 +162,15 @@ jobs:
gh pr edit \
--repo "${{github.repository }}" \
--title "Release: $CAPITALIZED_RELEASE_TYPE ${{ inputs.pre-id && format('{0} ', inputs.pre-id) }}${{ steps.bump-version.outputs.next-version }}" \
--base ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'next-release' || 'latest-release' }} \
--body "${{ steps.description.outputs.description }}"
else
gh pr create \
--repo "${{github.repository }}"\
--title "Release: $CAPITALIZED_RELEASE_TYPE ${{ inputs.pre-id && format('{0} ', inputs.pre-id) }}${{ steps.bump-version.outputs.next-version }}" \
--label "release" \
--base next-release \
--head version-prerelease-from-${{ steps.bump-version.outputs.current-version }} \
--base ${{ steps.is-prerelease.outputs.prerelease == 'true' && 'next-release' || 'latest-release' }} \
--head version-non-patch-from-${{ steps.bump-version.outputs.current-version }} \
--body "${{ steps.description.outputs.description }}"
fi

View File

@ -88,6 +88,15 @@ jobs:
git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
yarn release:pick-patches
- name: Cancel when no patches to pick
if: steps.pick-patches.outputs.pr-count == '0' && steps.pick-patches.outputs.pr-count != null
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# From https://stackoverflow.com/a/75809743
run: |
gh run cancel ${{ github.run_id }}
gh run watch ${{ github.run_id }}
- name: Bump version deferred
id: bump-version
if: steps.unreleased-changes.outputs.has-changes-to-release == 'true'
@ -121,7 +130,7 @@ jobs:
git config --global user.email '32066757+storybook-bot@users.noreply.github.com'
git checkout -b version-patch-from-${{ steps.versions.outputs.current }}
git add .
git commit -m "Write changelog for ${{ steps.versions.outputs.next }}" || true
git commit -m "Write changelog for ${{ steps.versions.outputs.next }} [skip ci]" || true
git push --force origin version-patch-from-${{ steps.versions.outputs.current }}
- name: Generate PR description

View File

@ -62,8 +62,12 @@ jobs:
run: |
yarn install
- name: Cancel all release preparation runs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn release:cancel-preparation-runs
- name: Apply deferred version bump and commit
id: version-bump
working-directory: .
run: |
CURRENT_VERSION=$(cat ./code/package.json | jq '.version')
@ -94,7 +98,7 @@ jobs:
- name: Check release vs prerelease
if: steps.publish-needed.outputs.published == 'false'
id: is-prerelease
run: yarn release:is-prerelease
run: yarn release:is-prerelease ${{ steps.version.outputs.current-version }} --verbose
- name: Install code dependencies
if: steps.publish-needed.outputs.published == 'false'
@ -122,12 +126,11 @@ jobs:
run: git fetch --tags origin
# when this is a patch release from main, label any patch PRs included in the release
# when this is a stable release from next, label ALL patch PRs found, as they will per definition be "patched" now
- name: Label patch PRs as picked
if: github.ref_name == 'latest-release' || (steps.publish-needed.outputs.published == 'false' && steps.target.outputs.target == 'next' && !steps.is-prerelease.outputs.prerelease)
if: github.ref_name == 'latest-release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn release:label-patches ${{ steps.target.outputs.target == 'next' && '--all' || '' }}
run: yarn release:label-patches
- name: Create GitHub Release
if: steps.publish-needed.outputs.published == 'false'
@ -151,8 +154,20 @@ jobs:
git merge ${{ github.ref_name }}
git push origin ${{ steps.target.outputs.target }}
- name: Ensure `next` is a minor version ahead of `main`
if: steps.target.outputs.target == 'main'
run: |
git checkout next
git pull
yarn release:ensure-next-ahead --main-version "${{ steps.version.outputs.current-version }}"
git add ..
git commit -m "Bump next to be one minor ahead of main [skip ci]"
git push origin next
- name: Sync CHANGELOG.md from `main` to `next`
if: github.ref_name == 'latest-release'
if: steps.target.outputs.target == 'main'
working-directory: .
run: |
git fetch origin next
@ -160,7 +175,7 @@ jobs:
git pull
git checkout origin/main ./CHANGELOG.md
git add ./CHANGELOG.md
git commit -m "Update CHANGELOG.md for v${{ steps.version.outputs.current-version }} [skip ci]"
git commit -m "Update CHANGELOG.md for v${{ steps.version.outputs.current-version }} [skip ci]" || true
git push origin next
- name: Sync version JSONs from `next-release` to `main`
@ -176,10 +191,6 @@ jobs:
git commit -m "Update $VERSION_FILE for v${{ steps.version.outputs.current-version }}"
git push origin main
- name: Overwrite main with next
if: steps.target.outputs.target == 'next' && steps.is-prerelease.outputs.prerelease == 'false'
run: git push --force origin next:main
- name: Report job failure to Discord
if: failure()
env:

View File

@ -9,19 +9,18 @@ on:
jobs:
build:
name: Core Unit Tests node-${{ matrix.node_version }}, ${{ matrix.os }}
name: Core Unit Tests, ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest]
node_version: [16]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set node version to ${{ matrix.node_version }}
- name: Set node version
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
node-version-file: '.nvmrc'
- name: install and compile
run: yarn task --task compile --start-from=auto --no-link
- name: test

View File

@ -1,3 +1,52 @@
## 7.5.0
Storybook 7.5 enhances your Storybook experience with several key updates:
- 💃🏼 Now supports Lit 3.0 and Vite 5
- 👻 storiesOf and storyStoreV6 officially deprecated
- 🔨 Fix Webpack5 build errors not being propagated
- 🀄 Support rename font import for Next.js
- ⬆️ Upgrade react-docgen to 6.0.x and improve argTypes
- ✨ Many Angular improvements such as introducing argsToTemplate , new schema debugging options, support for standalone directives, etc.
<details>
<summary>
List of all updates
</summary>
- Angular: Introduce argsToTemplate for property and event Bindings - [#24434](https://github.com/storybookjs/storybook/pull/24434), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!
- Angular: Add schema options (debugWebpack, webpackStatsJson, and more) - [#24388](https://github.com/storybookjs/storybook/pull/24388), thanks [@yannbf](https://github.com/yannbf)!
- Angular: Categorize legacy build options error - [#24014](https://github.com/storybookjs/storybook/pull/24014), thanks [@yannbf](https://github.com/yannbf)!
- Angular: Fix Angular 15 support and add zone.js v0.14.x support - [#24367](https://github.com/storybookjs/storybook/pull/24367), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!
- Angular: Allow loading standalone directives - [#24448](https://github.com/storybookjs/storybook/pull/24448), thanks [@osnoser1](https://github.com/osnoser1)!
- CLI: Inform the user how to dedupe and strip color from info command - [#24087](https://github.com/storybookjs/storybook/pull/24087), thanks [@kasperpeulen](https://github.com/kasperpeulen)!
- CLI: Add more information to storybook info command - [#24003](https://github.com/storybookjs/storybook/pull/24003), thanks [@JReinhold](https://github.com/JReinhold)!
- CLI: Change /Date$/ to /Dates$/i - [#24195](https://github.com/storybookjs/storybook/pull/24195), thanks [@arup1221](https://github.com/arup1221)!
- CLI: Improve sanitization logic in crash reports - [#24028](https://github.com/storybookjs/storybook/pull/24028), thanks [@yannbf](https://github.com/yannbf)!
- CLI: Remove random commas in storybook upgrade logs - [#22333](https://github.com/storybookjs/storybook/pull/22333), thanks [@joaonunomota](https://github.com/joaonunomota)!
- Controls: Fix select / multiselect when value contains multiple spaces - [#22334](https://github.com/storybookjs/storybook/pull/22334), thanks [@oxcened](https://github.com/oxcened)!
- Core: Add class name to Storybook error name - [#24371](https://github.com/storybookjs/storybook/pull/24371), thanks [@yannbf](https://github.com/yannbf)!
- Core: Deprecate storyStoreV6 (including storiesOf) and storyIndexers - [#23938](https://github.com/storybookjs/storybook/pull/23938), thanks [@JReinhold](https://github.com/JReinhold)!
- Doc Blocks: Add title to Meta prop types - [#23370](https://github.com/storybookjs/storybook/pull/23370), thanks [@iqbalcodes6602](https://github.com/iqbalcodes6602)!
- ManagerAPI: Fix bug with story redirection when URL has partial storyId - [#24345](https://github.com/storybookjs/storybook/pull/24345), thanks [@ndelangen](https://github.com/ndelangen)!
- NextJS: Support rename font import - [#24406](https://github.com/storybookjs/storybook/pull/24406), thanks [@yoshi2no](https://github.com/yoshi2no)!
- NextJS: Change babel plugins from proposal-... to transform-... - [#24290](https://github.com/storybookjs/storybook/pull/24290), thanks [@roottool](https://github.com/roottool)!
- NextJS: Fix default next image loader when src has params - [#24187](https://github.com/storybookjs/storybook/pull/24187), thanks [@json-betsec](https://github.com/json-betsec)!
- NextJS: Fix Image Context re-use via singleton - [#24146](https://github.com/storybookjs/storybook/pull/24146), thanks [@martinnabhan](https://github.com/martinnabhan)!
- NextJS: Improve support for Windows-style paths - [#23695](https://github.com/storybookjs/storybook/pull/23695), thanks [@T99](https://github.com/T99)!
- React: Upgrade `react-docgen` to `6.0.x` and improve argTypes - [#23825](https://github.com/storybookjs/storybook/pull/23825), thanks [@shilman](https://github.com/shilman)!
- Svelte: Fix docs generating when using `lang="ts"` or optional chaining - [#24096](https://github.com/storybookjs/storybook/pull/24096), thanks [@j3rem1e](https://github.com/j3rem1e)!
- UI: Filter some manager errors - [#24217](https://github.com/storybookjs/storybook/pull/24217), thanks [@yannbf](https://github.com/yannbf)!
- UI: Update ScrollArea with radix - [#24413](https://github.com/storybookjs/storybook/pull/24413), thanks [@cdedreuille](https://github.com/cdedreuille)!
- UI: Improve contrast ratio between focus / hover - [#24205](https://github.com/storybookjs/storybook/pull/24205), thanks [@chocoscoding](https://github.com/chocoscoding)!
- UI: Fix className missing in syntaxhighlighter - [#24491](https://github.com/storybookjs/storybook/pull/24491), thanks [@ndelangen](https://github.com/ndelangen)!
- Vite: Move mdx-plugin from `@storybook/builder-vite` to `@storybook/addon-docs` - [#24166](https://github.com/storybookjs/storybook/pull/24166), thanks [@bryanjtc](https://github.com/bryanjtc)!
- Vite: Support Vite 5 - [#24395](https://github.com/storybookjs/storybook/pull/24395), thanks [@IanVS](https://github.com/IanVS)!
- Web-components: Add Lit3 support - [#24437](https://github.com/storybookjs/storybook/pull/24437), thanks [@shilman](https://github.com/shilman)!
- Webpack: Display errors on build - [#24377](https://github.com/storybookjs/storybook/pull/24377), thanks [@yannbf](https://github.com/yannbf)!
- Webpack: Categorize builder error - [#24031](https://github.com/storybookjs/storybook/pull/24031), thanks [@yannbf](https://github.com/yannbf)!
- Webpack: Use logger.warn on warnings - [#24472](https://github.com/storybookjs/storybook/pull/24472), thanks [@yannbf](https://github.com/yannbf)!
</details>
## 7.4.6
- CLI: Fix Nextjs project detection - [#24346](https://github.com/storybookjs/storybook/pull/24346), thanks [@yannbf](https://github.com/yannbf)!

View File

@ -1,3 +1,41 @@
## 7.6.0-alpha.1
- Angular: Add source-map option to builder - [#24466](https://github.com/storybookjs/storybook/pull/24466), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!
- Angular: update wrong type for webpackStatsJson in start-storybook schema.json - [#24494](https://github.com/storybookjs/storybook/pull/24494), thanks [@LucaVazz](https://github.com/LucaVazz)!
- CLI: Add @storybook/addon-designs to non-core list - [#24507](https://github.com/storybookjs/storybook/pull/24507), thanks [@yannbf](https://github.com/yannbf)!
- Doc Blocks: Add support for `of` prop to `Primary` block - [#23849](https://github.com/storybookjs/storybook/pull/23849), thanks [@Wilson2k](https://github.com/Wilson2k)!
- Doc Blocks: Remove `defaultProps` in `Stories` block - [#24506](https://github.com/storybookjs/storybook/pull/24506), thanks [@WouterK12](https://github.com/WouterK12)!
- Themes: Run postinstall in shell for windows - [#24389](https://github.com/storybookjs/storybook/pull/24389), thanks [@Integrayshaun](https://github.com/Integrayshaun)!
## 7.6.0-alpha.0
Empty release identical to `7.5.0`.
## 7.5.0-alpha.7
- Angular: Allow loading standalone directives - [#24448](https://github.com/storybookjs/storybook/pull/24448), thanks [@osnoser1](https://github.com/osnoser1)!
- Svelte: Fix docs generating when using `lang="ts"` or optional chaining - [#24096](https://github.com/storybookjs/storybook/pull/24096), thanks [@j3rem1e](https://github.com/j3rem1e)!
- Vite: Support Vite 5 - [#24395](https://github.com/storybookjs/storybook/pull/24395), thanks [@IanVS](https://github.com/IanVS)!
## 7.5.0-alpha.6
- Angular: Introduce argsToTemplate for property and event Bindings - [#24434](https://github.com/storybookjs/storybook/pull/24434), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!
- Controls: Fix select / multiselect when value contains multiple spaces - [#22334](https://github.com/storybookjs/storybook/pull/22334), thanks [@oxcened](https://github.com/oxcened)!
- Next.js: Support rename font import - [#24406](https://github.com/storybookjs/storybook/pull/24406), thanks [@yoshi2no](https://github.com/yoshi2no)!
- UI: Update ScrollArea with radix - [#24413](https://github.com/storybookjs/storybook/pull/24413), thanks [@cdedreuille](https://github.com/cdedreuille)!
- Web-components: Add Lit3 support - [#24437](https://github.com/storybookjs/storybook/pull/24437), thanks [@shilman](https://github.com/shilman)!
## 7.5.0-alpha.5
- Angular: Add CLI options (debugWebpack, webpackStatsJson, and more) - [#24388](https://github.com/storybookjs/storybook/pull/24388), thanks [@yannbf](https://github.com/yannbf)!
- Angular: Fix Angular 15 support and add zone.js v0.14.x support - [#24367](https://github.com/storybookjs/storybook/pull/24367), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)!
- Core: Add class name to Storybook error name - [#24371](https://github.com/storybookjs/storybook/pull/24371), thanks [@yannbf](https://github.com/yannbf)!
- ManagerAPI: Fix bug with story redirection when URL has partial storyId - [#24345](https://github.com/storybookjs/storybook/pull/24345), thanks [@ndelangen](https://github.com/ndelangen)!
- NextJS: Fix Image Context re-use via singleton - [#24146](https://github.com/storybookjs/storybook/pull/24146), thanks [@martinnabhan](https://github.com/martinnabhan)!
- NextJS: Fix default next image loader when src has params - [#24187](https://github.com/storybookjs/storybook/pull/24187), thanks [@json-betsec](https://github.com/json-betsec)!
- React: Upgrade `react-docgen` to 6.0.x and improve argTypes - [#23825](https://github.com/storybookjs/storybook/pull/23825), thanks [@shilman](https://github.com/shilman)!
- Webpack: Display errors on build - [#24377](https://github.com/storybookjs/storybook/pull/24377), thanks [@yannbf](https://github.com/yannbf)!
## 7.5.0-alpha.4
- CLI: Fix Nextjs project detection - [#24346](https://github.com/storybookjs/storybook/pull/24346), thanks [@yannbf](https://github.com/yannbf)!
@ -97,7 +135,7 @@
- Publish: Don't distribute src files or unnecessary template files - [#23853](https://github.com/storybookjs/storybook/pull/23853), thanks [@shilman](https://github.com/shilman)!
- UI: Add an experimental API for adding sidebar filter functions at runtime - [#23722](https://github.com/storybookjs/storybook/pull/23722), thanks [@ndelangen](https://github.com/ndelangen)!
- UI: Removal of experimental components - [#23907](https://github.com/storybookjs/storybook/pull/23907), thanks [@ndelangen](https://github.com/ndelangen)!
- Vue3: Add support for Global Apps install - [#23772](https://github.com/storybookjs/storybook/pull/23772), thanks [@chakAs3](https://github.com/chakAs3)!
- Vue3: Add support for Global Apps install - [#23772](https://github.com/storybookjs/storybook/pull/23772), thanks [@chakAs3](https://github.com/chakAs3)!
- Vue3: Use slot value directly if it's a string in source decorator - [#23784](https://github.com/storybookjs/storybook/pull/23784), thanks [@nasvillanueva](https://github.com/nasvillanueva)!
## 7.4.0-alpha.0

View File

@ -8,8 +8,8 @@
- [Introduction](#introduction)
- [Branches](#branches)
- [Release Pull Requests](#release-pull-requests)
- [Prereleases](#prereleases)
- [Patch Releases](#patch-releases)
- [Non-patch Releases](#non-patch-releases)
- [Publishing](#publishing)
- [👉 How to Release](#-how-to-release)
- [1. Find the Prepared Pull Request](#1-find-the-prepared-pull-request)
@ -21,6 +21,8 @@
- [7. See the "Publish" Workflow Finish](#7-see-the-publish-workflow-finish)
- [Releasing Locally in an Emergency 🚨](#releasing-locally-in-an-emergency-)
- [Canary Releases](#canary-releases)
- [With GitHub UI](#with-github-ui)
- [With the CLI](#with-the-cli)
- [Versioning Scenarios](#versioning-scenarios)
- [Prereleases - `7.1.0-alpha.12` -\> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13)
- [Prerelease promotions - `7.1.0-alpha.13` -\> `7.1.0-beta.0`](#prerelease-promotions---710-alpha13---710-beta0)
@ -31,7 +33,7 @@
- [Prerelease of upcoming patch release - `7.0.20` -\> `7.0.21-alpha.0`](#prerelease-of-upcoming-patch-release---7020---7021-alpha0)
- [Merges to `main` without versioning](#merges-to-main-without-versioning)
- [FAQ](#faq)
- [When should I use the "patch" label?](#when-should-i-use-the-patch-label)
- [When should I use the "patch:yes" label?](#when-should-i-use-the-patchyes-label)
- [How do I make changes to the release tooling/process?](#how-do-i-make-changes-to-the-release-toolingprocess)
- [Why do I need to re-trigger workflows to update the changelog?](#why-do-i-need-to-re-trigger-workflows-to-update-the-changelog)
- [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need)
@ -43,19 +45,19 @@
This document explains the release process for the Storybook monorepo. There are two types:
1. Prereleases and major/minor releases - releasing any content that is on the `next` branch
1. Non-patch releases - releasing any content that is on the `next` branch, either prereleases or stable releases
2. Patch releases - picking any content from `next` to `main`, that needs to be patched back to the current stable minor release
The release process is based on automatically created "Release Pull Requests", that when merged will trigger a new version to be released.
A designated Releaser -- which may rotate between core team members -- will go through the release process in the current Release PR. This process is implemented with NodeJS scripts in [`scripts/release`](../scripts/release/) and three GitHub Actions workflows:
- [Prepare Prerelease PR](../.github/workflows/prepare-prerelease.yml)
- [Prepare Patch PR](../.github/workflows/prepare-patch-release.yml)
- [Prepare `next` PR](../.github/workflows/prepare-non-patch-release.yml)
- [Prepare patch PR](../.github/workflows/prepare-patch-release.yml)
- [Publish](../.github/workflows/publish.yml)
> **Note**
> This document distinguishes between **patch** releases and **prereleases**. This is a simplification; stable major and minor releases work the same way as prereleases. The distinction reflects the difference between patching an existing minor version on `main` or releasing a new minor/major/prerelease from `next`.
> This document distinguishes between **patch** and **non-patch** releases. The distinction reflects the difference between patching an existing minor version on `main` or releasing a new minor/major/prerelease from `next`.
### Branches
@ -101,7 +103,7 @@ Two GitHub Actions workflows automatically create release pull requests, one for
The high-level flow is:
1. When a PR is merged to `next` (or a commit is pushed), both release pull requests are (re)generated.
2. They create a new branch - `version-(patch|prerelease)-from-<CURRENT-VERSION>`.
2. They create a new branch - `version-(patch|non-patch)-from-<CURRENT-VERSION>`.
3. They calculate which version to bump to according to the version strategy.
4. They update `CHANGELOG(.prerelease).md` with all changes detected.
5. They commit everything.
@ -115,62 +117,20 @@ A few key points to note in this flow:
- The changelogs are committed during the preparation, but the packages are not version bumped and not published until later.
- The release pull requests don't target their working branches (`next` and `main`), but rather `next-release` and `latest-release`.
### Prereleases
> **Note**
> Workflow: [`prepare-prerelease.yml`](../.github/workflows/prepare-prerelease.yml)
Prereleases are prepared with all content from the `next` branch. The changelog is generated by examining the git history, and looking up all the commits and pull requests between the current prerelease (on `next-release`) and `HEAD` of `next`.
The default versioning strategy is to increase the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (i.e., we just released a new stable minor/major version), it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` by default.
Prerelease PRs are only created if there are actual changes to release. Content labeled with "build" or "documentation" is [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean) and is not user-facing, so it doesn't make sense to create a release. This is explained in more detail in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared).
The preparation workflow will create a new branch from `next`, called `version-prerelease-from-<CURRENT-PRERELEASE-VERSION>`, and open a pull request targeting `next-release`. When the Releaser merges it, the [publish workflow](#publishing) will merge `next-release` into `next`.
Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the commits highlighted with square dots are the ones that will be considered when generating the changelog.
```mermaid
%%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%%
gitGraph
commit
branch next-release
commit tag: "7.1.0-alpha.28"
checkout next
merge next-release
commit type: HIGHLIGHT id: "direct commit"
branch new-feature
commit
commit
checkout next
merge new-feature type: HIGHLIGHT
branch some-bugfix
commit
checkout next
merge some-bugfix type: HIGHLIGHT
branch version-prerelease-from-7.1.0-alpha.28
commit id: "write changelog"
checkout next-release
merge version-prerelease-from-7.1.0-alpha.28
commit id: "bump versions" tag: "7.1.0-alpha.29"
checkout next
merge next-release
```
### Patch Releases
> **Note**
> Workflow: [`prepare-patch-release.yml`](../.github/workflows/prepare-patch-release.yml)
Patch releases are created by [cherry-picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests that have the "**patch**" label applied to the `next` branch. The merge commit of said pull requests are cherry-picked.
Patch releases are created by [cherry-picking](https://www.atlassian.com/git/tutorials/cherry-pick) any merged, unreleased pull requests that have the "**patch:yes**" label applied to the `next` branch. The merge commit of said pull requests are cherry-picked.
Sometimes it is desired to pick pull requests back to `main` even if they are not considered "releasable". Unlike prerelease preparation, patch releases will not be canceled if the content is not releasable. It might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. However, getting the changes back to `main` is the only way to deploy the documentation to the production docs site. You may also want to cherry-pick changes to internal CI to fix issues. These are valid scenarios where you want to cherry-pick the changes without being blocked on "releasable" content. In these cases, where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. This pull request does not bump versions or update changelogs; it just cherry-picks the changes and allows you to merge them into `latest-release` -> `main`.
Sometimes it is desired to pick pull requests back to `main` even if they are not considered "releasable". Unlike non-patch release preparation, patch releases will not be canceled if the content is not releasable. It might not make sense to create a new patch release if the changes are only for documentation and/or internal build systems. However, getting the changes back to `main` is the only way to deploy the documentation to the production docs site. You may also want to cherry-pick changes to internal CI to fix issues. These are valid scenarios where you want to cherry-pick the changes without being blocked on "releasable" content. In these cases, where all cherry picks are non-releasable, the preparation workflow creates a "merging" pull request instead of a "releasing" pull request. This pull request does not bump versions or update changelogs; it just cherry-picks the changes and allows you to merge them into `latest-release` -> `main`.
The preparation workflow sequentially cherry-picks each patch pull request to its branch. If this cherry-picking fails due to conflicts or other reasons, it is ignored and the next pull request is processed. All failing cherry-picks are listed in the release pull request's description, for the Releaser to manually cherry-pick during the release process. This problem occurs more often when `main` and `next` diverge, i.e. the longer it has been since a stable major/minor release.
Similar to the prerelease flow, the preparation workflow for patches will create a new branch from `main` called `version-patch-from-<CURRENT-STABLE-VERSION>`, and open a pull request that targets `latest-release`. When the pull request is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`.
Similar to the non-patch release flow, the preparation workflow for patches will create a new branch from `main` called `version-patch-from-<CURRENT-STABLE-VERSION>`, and open a pull request that targets `latest-release`. When the pull request is merged by the Releaser, the [publish workflow](#publishing) will eventually merge `latest-release` into `main`.
Here is an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch**" label, so only those two go into the new `7.0.19` release. Note that it is the merge commits to `next` that are cherry-picked, not the commits on the bugfix branches.
Here is an example of a workflow where a feature and two bug fixes have been merged to `next`. Only the bug fixes have the "**patch:yes**" label, so only those two go into the new `7.0.19` release. Note that it is the merge commits to `next` that are cherry-picked, not the commits on the bugfix branches.
```mermaid
gitGraph
@ -208,21 +168,62 @@ gitGraph
merge latest-release
```
### Non-patch Releases
> **Note**
> Workflow: [`prepare-non-patch-release.yml`](../.github/workflows/prepare-non-patch-release.yml)
Non-patch releases are prepared with all content from the `next` branch. The changelog is generated by examining the git history, and looking up all the commits and pull requests between the current prerelease (on `next-release`) and `HEAD` of `next`.
The default versioning strategy is to increase the current prerelease number, as described in [Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`](#prereleases---710-alpha12---710-alpha13). If there is no prerelease number (i.e., we just released a new stable minor/major version), it will add one to a patch bump, so it would go from `7.2.0` to `7.2.1-0` by default.
`next`-PRs are only created if there are actual changes to release. Content labeled with "build" or "documentation" is [not considered "releasable"](#which-changes-are-considered-releasable-and-what-does-it-mean) and is not user-facing, so it doesn't make sense to create a release. This is explained in more detail in [Why are no release PRs being prepared?](#why-are-no-release-prs-being-prepared).
The preparation workflow will create a new branch from `next`, called `version-non-patch-from-<CURRENT-NEXT-VERSION>`, and open a pull request targeting `next-release`. When the Releaser merges it, the [publish workflow](#publishing) will merge `next-release` into `next`.
Here's an example of a workflow where a feature and a bugfix have been created and then released to a new `7.1.0-alpha.29` version. All the commits highlighted with square dots are the ones that will be considered when generating the changelog.
```mermaid
%%{init: { 'gitGraph': { 'mainBranchName': 'next' } } }%%
gitGraph
commit
branch next-release
commit tag: "7.1.0-alpha.28"
checkout next
merge next-release
commit type: HIGHLIGHT id: "direct commit"
branch new-feature
commit
commit
checkout next
merge new-feature type: HIGHLIGHT
branch some-bugfix
commit
checkout next
merge some-bugfix type: HIGHLIGHT
branch version-non-patch-from-7.1.0-alpha.28
commit id: "write changelog"
checkout next-release
merge version-non-patch-from-7.1.0-alpha.28
commit id: "bump versions" tag: "7.1.0-alpha.29"
checkout next
merge next-release
```
### Publishing
> **Note**
> Workflow: [`publish.yml`](../.github/workflows/publish.yml)
When either a prerelease or a patch release branch is merged into `main` or `next-release`, the publishing workflow is triggered. This workflow performs the following tasks:
When either a non-patch release or a patch release branch is merged into `latest-release` or `next-release`, the publishing workflow is triggered. This workflow performs the following tasks:
1. Bump versions of all packages according to the plan from the prepared PRs
2. Install dependencies and build all packages.
3. Publish packages to npm.
4. (If this is a patch release, add the "**picked**" label to all relevant pull requests.)
4. (If this is a patch release, add the "**patch:done**" label to all relevant pull requests.)
5. Create a new GitHub Release, including a version tag in the release branch (`latest-release` or `next-release`).
6. Merge the release branch into the core branch (`main` or `next`).
7. (If this is a patch release, copy the `CHANGELOG.md` changes from `main` to `next`.)
8. (If this is [a promotion from a prerelease to a stable release](#minormajor-releases---710-rc2---710-or-800-rc3---800), force push `next` to `main`.)
The publish workflow runs in the "release" GitHub environment, which has the npm token required to publish packages to the `@storybook` npm organization. For security reasons, this environment can only be accessed from the four "core" branches: `main`, `next`, `latest-release` and `next-release`.
@ -244,7 +245,7 @@ The high-level workflow for a Releaser is:
Look for the release pull request that has been prepared for the type of release you're about to release:
- "Release: Prerelease `<NEXT-VERSION>`" for prereleases
- "Release: Prerelease|Minor|Major `<NEXT-VERSION>`" for releases from `next`
- "Release: Patch `<NEXT-VERSION>`" for patch releases
- "Release: Merge patches to `main` (without version bump)" for patches without releases
@ -266,7 +267,7 @@ It is important to verify that the release includes the right content. Key eleme
For example, check if it's a breaking change that isn't allowed in a minor prerelease, or if it's a new feature in a patch release. If it's not suitable, revert the pull request and notify the author.
Sometimes when doing a patch release, a pull request can have the "patch" label but you don't want that change to be part of this release. Maybe you're not confident in the change, or you require more input from maintainers before releasing it. In those situations you should remove the "patch" label from the pull request and follow through with the release (make sure to re-trigger the workflow). When the release is done, add the patch label back again, so it will be part of the next release.
Sometimes when doing a patch release, a pull request can have the "patch:yes" label but you don't want that change to be part of this release. Maybe you're not confident in the change, or you require more input from maintainers before releasing it. In those situations you should remove the "patch:yes" label from the pull request and follow through with the release (make sure to re-trigger the workflow). When the release is done, add the "patch:yes" label back again, so it will be part of the next release.
2. Is the pull request title correct?
@ -300,12 +301,12 @@ When triggering the workflows, always choose the `next` branch as the base, unle
The workflows can be triggered here:
- [Prepare prerelease PR](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml)
- [Prepare next PR](https://github.com/storybookjs/storybook/actions/workflows/prepare-non-patch-release.yml)
- [Prepare patch PR](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml)
Crucially for prereleases, this is also where you change the versioning strategy if you need something else than the default as described in [Preparing - Prereleases](#prereleases). When triggering the prerelease workflow manually, you can optionally add inputs:
Crucially for prereleases, this is also where you change the versioning strategy if you need something else than the default as described in [Preparing - Non-patch Releases](#non-patch-releases). When triggering the non-patch workflow manually, you can optionally add inputs:
![Screenshot of triggering the prerelease workflow in GitHub Actions, with a form that shows a release type selector and a prerelease identifier text field](prerelease-workflow-inputs.png)
![Screenshot of triggering the non-patch release workflow in GitHub Actions, with a form that shows a release type selector and a prerelease identifier text field](prerelease-workflow-inputs.png)
See [Versioning Scenarios](#versioning-scenarios) for a description of each version bump scenario, how to activate it and what it does, and [Which combination of inputs creates the version bump I need?](#which-combination-of-inputs-creates-the-version-bump-i-need) for a detailed description of the workflow inputs.
@ -339,7 +340,7 @@ You can inspect the workflows to see what they are running and copy that, but he
Before you start you should make sure that your working tree is clean and the repository is in a clean state by running `git clean -xdf`.
1. Create a new branch from either `next` (prereleases) or `main` (patches)
1. Create a new branch from either `next` or `main` (patches)
2. Get all tags: `git fetch --tags origin`
3. Install dependencies: `yarn task --task=install --start-from=install`
4. `cd scripts`
@ -375,7 +376,7 @@ Before you start you should make sure that your working tree is clean and the re
4. `git add ./CHANGELOG.md`
5. `git commit -m "Update CHANGELOG.md for v<NEXT_VERSION>"`
6. `git push origin`
19. (If prerelease) Sync `versions/next.json` from `next` to `main`
19. (If non-patch release) Sync `versions/next.json` from `next` to `main`
1. `git checkout main`
2. `git pull`
3. `git checkout origin/next ./docs/versions/next.json`
@ -434,7 +435,7 @@ There are multiple types of releases that use the same principles, but are done
### Prereleases - `7.1.0-alpha.12` -> `7.1.0-alpha.13`
This is the default strategy for prereleases, there's nothing special needed to trigger this scenario.
This is the default strategy for Non-patch releases, there's nothing special needed to trigger this scenario.
### Prerelease promotions - `7.1.0-alpha.13` -> `7.1.0-beta.0`
@ -445,14 +446,12 @@ To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workf
### Minor/major releases - `7.1.0-rc.2` -> `7.1.0` or `8.0.0-rc.3` -> `8.0.0`
To promote a prerelease to a new prerelease ID, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose:
To promote a prerelease to a stable reelase, during the [Re-trigger the Workflow](#4-re-trigger-the-workflow) step, choose:
- Release type: Patch
- Release type: Patch, Minor or Major
- Prerelease ID: Leave empty
The "Patch" release type ensures the current prerelease version gets promoted to a stable version without any major/minor/patch bumps.
This scenario is special as it turns the `next` branch into a stable branch (until the next prerelease). Therefore, this will also force push `next` to `main`, to ensure that `main` contains the latest stable release. Consequently, the history for `main` is lost.
This scenario is special as it will target `latest-release` instead of `next-release`, and thus merge into `main` when done, and not `next`. So it goes `next` -> `version-non-patch-from-<CURRENT-VERSION-ON_NEXT>` -> `latest-release` -> `main`.
### First prerelease of new major/minor - `7.1.0` -> `7.2.0-alpha.0` or `8.0.0-alpha.0`
@ -481,13 +480,13 @@ As described in more details in [the Patch Releases section](#patch-releases), t
## FAQ
### When should I use the "patch" label?
### When should I use the "patch:yes" label?
Not all pull requests need to be patched back to the stable release, which is why only those with the **"patch"** label gets that treatment. But how do you decide whether or not a give pull requests should have that label?
Not all pull requests need to be patched back to the stable release, which is why only those with the **"patch:yes"** label gets that treatment. But how do you decide whether or not a give pull requests should have that label?
First of all, patches are only for fixes and minor improvements, and not completely new features. A pull request that introduces a new feature shouldn't be patched back to the stable release.
First of all, patches are only for important and time-sensitive fixes, and not minor improvements or completely new features. A pull request that introduces a new feature shouldn't be patched back to the stable release.
Second, any destabilizing changes shouldn't be patched back either. Breaking changes are reserved for major releases, but changes can be destabilizing without being strictly breaking, and those shouldn't be patched back either. An example is moving the settings panel in the manager to a completely different place, but with the same functionality. Many wouldn't consider this breaking because no usage will stop working because of this, but it can be considered a destabilizing change because user behavior have to change as a result of this.
Second, PRs that changes the code in a big architectural way should ideally not be patched back either, because that makes merge conflicts more likely in the future.
When in doubt ask the core team for their input.
@ -497,12 +496,15 @@ The whole process is based on [GitHub Action workflows](../.github/workflows/) a
The short answer to "how", is to make changes as a regular pull request that is also patched back to `main`.
There's a longer answer too, but it's pretty confusing:
<details>
<summary>There's a longer answer too, but it's pretty confusing</summary>
The scripts run from either `main` or `next`, so if you're changing a release script, you must patch it back to `main` for it to have an effect on patch releases. If you need the change to take effect immediately, you must manually cherry pick it to `main`.
For workflow file changes, they usually run from `next`, but patching them back is recommended for consistency. The "publish" workflow runs from `latest-release` and `next-release`, so you should always patch changes back for _that_. 🙃
</details>
### Why do I need to re-trigger workflows to update the changelog?
Changes to pull requests' titles, labels or even reverts won't be reflected in the release pull request. This is because the workflow only triggers on pushes to `next`, not when pull request meta data is changed.
@ -536,7 +538,7 @@ If a pull request does not have any of the above labels at the time of release,
This is most likely because `next` only contains [unreleasable changes](#which-changes-are-considered-releasable-and-what-does-it-mean), which causes the preparation workflow to cancel itself. That's because it doesn't make sense to prepare a new release if all the changes are unreleasable, as that wouldn't bump the version nor write a new changelog entry, so "releasing" it would just merge it back to `next` without any differences.
You can always see the workflows and if they have been cancelled [here for prereleases](https://github.com/storybookjs/storybook/actions/workflows/prepare-prerelease.yml) and [here for patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml).
You can always see the workflows and if they have been cancelled [here for non-patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-non-patch-release.yml) and [here for patch releases](https://github.com/storybookjs/storybook/actions/workflows/prepare-patch-release.yml).
### Why do we need separate release branches?
@ -558,11 +560,11 @@ gitGraph
branch some-simultaneous-bugfix
commit
checkout next
branch version-prerelease-from-7.1.0-alpha.28
branch version-non-patch-from-7.1.0-alpha.28
commit id
checkout next
merge some-simultaneous-bugfix type: HIGHLIGHT id: "whoops!"
merge version-prerelease-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29"
merge version-non-patch-from-7.1.0-alpha.28 tag: "v7.1.0-alpha.29"
```
When publishing at the last commit with tag `v7.1.0-alpha.29`, it will publish whatever the content is at that point (all the square dots), which includes the "whoops!" commit from merging the bugfix. But the bugfix was never part of the release pull request because it got prepared before the bugfix was merged in.
@ -582,19 +584,19 @@ gitGraph
branch some-simultanous-bugfix
commit
checkout next
branch version-prerelease-from-7.1.0-alpha.28
branch version-non-patch-from-7.1.0-alpha.28
commit id: "write changelog"
checkout next
merge some-simultanous-bugfix id: "whoops!"
checkout next-release
merge version-prerelease-from-7.1.0-alpha.28
merge version-non-patch-from-7.1.0-alpha.28
commit id: "bump versions" tag: "v7.1.0-alpha.29"
checkout next
merge next-release
branch version-prerelease-from-7.1.0-alpha.29
branch version-non-patch-from-7.1.0-alpha.29
commit id: "write changelog again"
checkout next-release
merge version-prerelease-from-7.1.0-alpha.29
merge version-non-patch-from-7.1.0-alpha.29
commit id: "bump versions again" tag: "v7.1.0-alpha.30"
checkout next
merge next-release

View File

@ -3,11 +3,13 @@
- [From version 7.x to 8.0.0](#from-version-7x-to-800)
- [Core changes](#core-changes)
- [UI layout state has changed shape](#ui-layout-state-has-changed-shape)
- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
- [From version 7.4.0 to 7.5.0](#from-version-740-to-750)
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
- [From version 7.0.0 to 7.2.0](#from-version-700-to-720)
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
- [From version 6.5.x to 7.0.0](#from-version-65x-to-700)
- [7.0 breaking changes](#70-breaking-changes)
- [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below)
@ -33,7 +35,7 @@
- [Deploying build artifacts](#deploying-build-artifacts)
- [Dropped support for file URLs](#dropped-support-for-file-urls)
- [Serving with nginx](#serving-with-nginx)
- [Ignore story files from node_modules](#ignore-story-files-from-node_modules)
- [Ignore story files from node\_modules](#ignore-story-files-from-node_modules)
- [7.0 Core changes](#70-core-changes)
- [7.0 feature flags removed](#70-feature-flags-removed)
- [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates)
@ -45,7 +47,7 @@
- [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default)
- [7.0 Vite changes](#70-vite-changes)
- [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically)
- [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [7.0 Webpack changes](#70-webpack-changes)
- [Webpack4 support discontinued](#webpack4-support-discontinued)
- [Babel mode v7 exclusively](#babel-mode-v7-exclusively)
@ -95,7 +97,7 @@
- [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration)
- [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration)
- [Autoplay in docs](#autoplay-in-docs)
- [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global)
- [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global)
- [7.0 Deprecations and default changes](#70-deprecations-and-default-changes)
- [storyStoreV7 enabled by default](#storystorev7-enabled-by-default)
- [`Story` type deprecated](#story-type-deprecated)
@ -320,6 +322,12 @@ In Storybook 7 it was possible to use `addons.setConfig({...});` to configure St
- `showPanel: boolean` is now split into `bottomPanelHeight: number` and `rightPanelWidth: number`, where the numbers represents the size of the panel in pixels.
- `isFullscreen: boolean` is no longer supported, but can be achieved by setting a combination of the above.
## From version 7.5.0 to 7.6.0
#### Primary doc block accepts of prop
The `Primary` doc block now also accepts an `of` prop as described in the [Doc Blocks](#doc-blocks) section. It still accepts being passed `name` or no props at all.
## From version 7.4.0 to 7.5.0
#### `storyStoreV6` and `storiesOf` is deprecated

11
RESOLUTIONS.md Normal file
View File

@ -0,0 +1,11 @@
# Resolutions and Exact versions
This file keeps track of any resolutions or exact versions specified in any `package.json` file. Resolutions are used to specify a specific version of a package to be used, even if a different version is specified as a dependency of another package.
## code/renderers/svelte/package.json
svelte-check@3.4.6 (bug: 3.5.x): Type issues
## code/ui/components/package.json
overlayscrollbars@2.2.1 (bug: 2.3.x): The Scrollbar doesn't disappear anymore by default. It might has something to do with the `scrollbars.autoHideSuspend` option, which was introduced in 2.3.0. https://github.com/KingSora/OverlayScrollbars/blob/master/packages/overlayscrollbars/CHANGELOG.md#230

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Test component compliance with web accessibility standards",
"keywords": [
"a11y",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [
"storybook",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Switch backgrounds to view components in different settings",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-controls",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [
"addon",

View File

@ -26,8 +26,8 @@ export default {
control: { type: 'radio', options: ['a', 'b', 'c'], labels: ['alpha', 'beta', 'gamma'] },
},
inlineRadio: { control: { type: 'inline-radio', options: ['a', 'b', 'c'] } },
select: { control: { type: 'select', options: ['a', 'b', 'c'] } },
multiSelect: { control: { type: 'multi-select', options: ['a', 'b', 'c'] } },
select: { control: 'select', options: ['a', 'b', 'c', 'double space'] },
multiSelect: { control: { type: 'multi-select' }, options: ['a', 'b', 'c', 'double space'] },
range: { control: 'range' },
rangeCustom: { control: { type: 'range', min: 0, max: 1000, step: 100 } },
text: { control: 'text' },

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Document component usage and properties in Markdown",
"keywords": [
"addon",

View File

@ -28,7 +28,7 @@ class ErrorBoundary extends Component<{
const { hasError } = this.state;
const { children } = this.props;
return hasError ? null : children;
return hasError ? null : <>{children}</>;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-essentials",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Curated addons to bring out the best of Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-mdx-gfm",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "GitHub Flavored Markdown in Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-highlight",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Highlight DOM nodes within your stories",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-interactions",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Automate, test and debug user interactions",
"keywords": [
"storybook-addons",
@ -91,7 +91,7 @@
"@devtools-ds/object-inspector": "^1.1.2",
"@storybook/jest": "next",
"@storybook/testing-library": "next",
"@types/node": "^16.0.0",
"@types/node": "^18.0.0",
"formik": "^2.2.9",
"typescript": "~4.9.3"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-measure",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Inspect layouts by visualizing the box model",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-outline",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Take a code snapshot of every story automatically with Jest",
"keywords": [
"addon",

View File

@ -19,7 +19,7 @@ When running Puppeteer tests for your stories, you have two options:
- Have a storybook running (ie. accessible via http(s), for instance using `npm run storybook`)
- Have a static build of the storybook (for instance, using `npm run build-storybook`)
Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served)
Then you will need to reference the storybook URL (`http(s)://...`)
## _puppeteerTest_
@ -72,21 +72,6 @@ initStoryshots({
The above config will use _<https://my-specific-domain.com:9010>_ for tests. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`).
You may also use a local static build of storybook if you do not want to run the webpack dev-server:
```js
import initStoryshots from '@storybook/addon-storyshots';
import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
initStoryshots({
suite: 'Puppeteer storyshots',
test: puppeteerTest({
storybookUrl: 'file:///path/to/my/storybook-static',
// storybookUrl: 'file://${path.resolve(__dirname, '../storybook-static')}'
}),
});
```
### Specifying options to _goto()_ (Puppeteer API)
You might use `getGotoOptions` to specify options when the storybook is navigating to a story (using the `goto` method). Will be passed to [Puppeteer .goto() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options)

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots-puppeteer",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Image snapshots addition to StoryShots based on puppeteer",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "View a storys source code to see how it works and paste into your app",
"keywords": [
"addon",

View File

@ -1,8 +1,8 @@
# `@storybook/addon-themes
# @storybook/addon-themes
Storybook Addon Themes can be used which between multiple themes for components inside the preview in [Storybook](https://storybook.js.org).
![React Storybook Screenshot](https://user-images.githubusercontent.com/42671/98158421-dada2300-1ea8-11eb-8619-af1e7018e1ec.png)
![React Storybook Screenshot](https://user-images.githubusercontent.com/18172605/274302488-77a39112-cdbe-4d16-9966-0d8e9e7e3399.gif)
## Usage

View File

@ -7,7 +7,7 @@
Takes your provider component, global styles, and theme(s)to wrap your stories in.
```js
import { withThemeFromJSXProvider } from '@storybook/addon-styling';
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
export const decorators = [
withThemeFromJSXProvider({
@ -36,7 +36,7 @@ Available options:
Takes your theme class names to apply your parent element to enable your theme(s).
```js
import { withThemeByClassName } from '@storybook/addon-styling';
import { withThemeByClassName } from '@storybook/addon-themes';
export const decorators = [
withThemeByClassName({
@ -62,7 +62,7 @@ Available options:
Takes your theme names and data attribute to apply your parent element to enable your theme(s).
```js
import { withThemeByDataAttribute } from '@storybook/addon-styling';
import { withThemeByDataAttribute } from '@storybook/addon-themes';
export const decorators = [
withThemeByDataAttribute({
@ -94,7 +94,7 @@ If none of these decorators work for your library there is still hope. We've pro
Pulls the selected theme from storybook's global state.
```js
import { DecoratorHelpers } from '@storybook/addon-styling';
import { DecoratorHelpers } from '@storybook/addon-themes';
const { pluckThemeFromContext } = DecoratorHelpers;
export const myCustomDecorator =
@ -111,7 +111,7 @@ export const myCustomDecorator =
Returns the theme parameters for this addon.
```js
import { DecoratorHelpers } from '@storybook/addon-styling';
import { DecoratorHelpers } from '@storybook/addon-themes';
const { useThemeParameters } = DecoratorHelpers;
export const myCustomDecorator =
@ -128,7 +128,7 @@ export const myCustomDecorator =
Used to register the themes and defaultTheme with the addon state.
```js
import { DecoratorHelpers } from '@storybook/addon-styling';
import { DecoratorHelpers } from '@storybook/addon-themes';
const { initializeThemeState } = DecoratorHelpers;
export const myCustomDecorator = ({ themes, defaultState, ...rest }) => {
@ -147,7 +147,7 @@ Let's use Vuetify as an example. Vuetify uses it's own global state to know whic
```js
// .storybook/withVeutifyTheme.decorator.js
import { DecoratorHelpers } from '@storybook/addon-styling';
import { DecoratorHelpers } from '@storybook/addon-themes';
import { useTheme } from 'vuetify';
const { initializeThemeState, pluckThemeFromContext, useThemeParameters } = DecoratorHelpers;

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-themes",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Switch between multiple themes for you components in Storybook",
"keywords": [
"css",
@ -114,6 +114,6 @@
"unsupportedFrameworks": [
"react-native"
],
"icon": ""
"icon": "https://user-images.githubusercontent.com/18172605/264114587-e4b32190-a9b7-4afa-b739-c873fc0498a6.png"
}
}

View File

@ -1,17 +1,24 @@
const { spawn } = require('child_process');
const PACKAGE_MANAGER_TO_COMMAND = {
npm: 'npx',
yarn1: 'npx',
yarn2: 'yarn dlx',
pnpm: 'pnpm dlx',
npm: ['npx'],
pnpm: ['pnpm', 'dlx'],
yarn1: ['npx'],
yarn2: ['yarn', 'dlx'],
};
module.exports = function postinstall(options) {
const command = PACKAGE_MANAGER_TO_COMMAND[options.packageManager];
const selectPackageManagerCommand = (packageManager) => PACKAGE_MANAGER_TO_COMMAND[packageManager];
spawn(command, ['@storybook/auto-config', 'themes'], {
const spawnPackageManagerScript = async (packageManager, args) => {
const [command, ...baseArgs] = selectPackageManagerCommand(packageManager);
await spawn(command, [...baseArgs, ...args], {
stdio: 'inherit',
cwd: process.cwd(),
shell: true,
});
};
module.exports = async function postinstall({ packageManager = 'npm' }) {
await spawnPackageManagerScript(packageManager, ['@storybook/auto-config', 'themes']);
};

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-toolbars",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Create your own toolbar items that control story rendering",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Build responsive components by adjusting Storybooks viewport size and orientation",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-manager",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook manager builder",
"keywords": [
"storybook"

View File

@ -45,7 +45,7 @@ export const getConfig: ManagerBuilder['getConfig'] = async (options) => {
return {
entryPoints: realEntryPoints,
outdir: join(options.outputDir || './', 'sb-addons'),
format: 'esm',
format: 'iife',
write: false,
ignoreAnnotations: true,
resolveExtensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-vite",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "A plugin to run and build Storybooks with Vite",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme",
"bugs": {
@ -62,7 +62,7 @@
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.0.0",
"@types/node": "^18.0.0",
"glob": "^10.0.0",
"rollup": "^3.20.1",
"slash": "^5.0.0",
@ -72,7 +72,7 @@
"peerDependencies": {
"@preact/preset-vite": "*",
"typescript": ">= 4.3.x",
"vite": "^3.0.0 || ^4.0.0",
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0",
"vite-plugin-glimmerx": "*"
},
"peerDependenciesMeta": {

View File

@ -1,10 +1,10 @@
import { build as viteBuild, mergeConfig } from 'vite';
import type { Options } from '@storybook/types';
import { commonConfig } from './vite-config';
import { sanitizeEnvVars } from './envs';
export async function build(options: Options) {
const { build: viteBuild, mergeConfig } = await import('vite');
const { presets } = options;
const config = await commonConfig(options, 'build');
@ -21,6 +21,5 @@ export async function build(options: Options) {
}).build;
const finalConfig = await presets.apply('viteFinal', config, options);
await viteBuild(await sanitizeEnvVars(options, finalConfig));
}

View File

@ -1,15 +1,19 @@
import { loadPreviewOrConfigFile } from '@storybook/core-common';
import type { Options } from '@storybook/types';
import slash from 'slash';
import { normalizePath } from 'vite';
import { listStories } from './list-stories';
const absoluteFilesToImport = (files: string[], name: string) =>
const absoluteFilesToImport = async (
files: string[],
name: string,
normalizePath: (id: string) => string
) =>
files
.map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'/@fs/${normalizePath(el)}'`)
.join('\n');
export async function generateVirtualStoryEntryCode(options: Options) {
const { normalizePath } = await import('vite');
const storyEntries = await listStories(options);
const resolveMap = storyEntries.reduce<Record<string, string>>(
(prev, entry) => ({ ...prev, [entry]: entry.replace(slash(process.cwd()), '.') }),
@ -18,7 +22,7 @@ export async function generateVirtualStoryEntryCode(options: Options) {
const modules = storyEntries.map((entry, i) => `${JSON.stringify(entry)}: story_${i}`).join(',');
return `
${absoluteFilesToImport(storyEntries, 'story')}
${await absoluteFilesToImport(storyEntries, 'story', normalizePath)}
function loadable(key) {
return {${modules}}[key];

View File

@ -1,5 +1,5 @@
import * as path from 'path';
import { normalizePath } from 'vite';
import type { Options } from '@storybook/types';
import { logger } from '@storybook/node-logger';
@ -26,6 +26,7 @@ function toImportPath(relativePath: string) {
* @param stories An array of absolute story paths.
*/
async function toImportFn(stories: string[]) {
const { normalizePath } = await import('vite');
const objectEntries = stories.map((file) => {
const ext = path.extname(file);
const relativePath = normalizePath(path.relative(process.cwd(), file));

View File

@ -4,9 +4,10 @@ import { glob } from 'glob';
import { normalizeStories, commonGlobOptions } from '@storybook/core-common';
import type { Options } from '@storybook/types';
import { normalizePath } from 'vite';
export async function listStories(options: Options) {
const { normalizePath } = await import('vite');
return (
await Promise.all(
normalizeStories(await options.presets.apply('stories', [], options), {

View File

@ -1,5 +1,4 @@
import * as path from 'path';
import { normalizePath, resolveConfig } from 'vite';
import type { InlineConfig as ViteInlineConfig, UserConfig } from 'vite';
import type { Options } from '@storybook/types';
import { listStories } from './list-stories';
@ -128,6 +127,7 @@ const asyncFilter = async (arr: string[], predicate: (val: string) => Promise<bo
export async function getOptimizeDeps(config: ViteInlineConfig, options: Options) {
const { root = process.cwd() } = config;
const { normalizePath, resolveConfig } = await import('vite');
const absoluteStories = await listStories(options);
const stories = absoluteStories.map((storyPath) => normalizePath(path.relative(root, storyPath)));
// TODO: check if resolveConfig takes a lot of time, possible optimizations here

View File

@ -3,7 +3,6 @@ import findCacheDirectory from 'find-cache-dir';
import { init, parse } from 'es-module-lexer';
import MagicString from 'magic-string';
import { ensureFile, writeFile } from 'fs-extra';
import { mergeAlias } from 'vite';
import type { Alias, Plugin } from 'vite';
const escapeKeys = (key: string) => key.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
@ -38,6 +37,8 @@ const replacementMap = new Map([
*/
export async function externalGlobalsPlugin(externals: Record<string, string>) {
await init;
const { mergeAlias } = await import('vite');
return {
name: 'storybook:external-globals-plugin',
enforce: 'post',

View File

@ -1,32 +1,35 @@
import { parse } from 'es-module-lexer';
import MagicString from 'magic-string';
import { createFilter } from 'vite';
const include = [/\.stories\.([tj])sx?$/, /(stories|story).mdx$/];
const filter = createFilter(include);
export async function injectExportOrderPlugin() {
const { createFilter } = await import('vite');
export const injectExportOrderPlugin = {
name: 'storybook:inject-export-order-plugin',
// This should only run after the typescript has been transpiled
enforce: 'post',
async transform(code: string, id: string) {
if (!filter(id)) return undefined;
const include = [/\.stories\.([tj])sx?$/, /(stories|story).mdx$/];
const filter = createFilter(include);
// TODO: Maybe convert `injectExportOrderPlugin` to function that returns object,
// and run `await init;` once and then call `parse()` without `await`,
// instead of calling `await parse()` every time.
const [, exports] = await parse(code);
return {
name: 'storybook:inject-export-order-plugin',
// This should only run after the typescript has been transpiled
enforce: 'post',
async transform(code: string, id: string) {
if (!filter(id)) return undefined;
if (exports.includes('__namedExportsOrder')) {
// user has defined named exports already
return undefined;
}
const s = new MagicString(code);
const orderedExports = exports.filter((e) => e !== 'default');
s.append(`;export const __namedExportsOrder = ${JSON.stringify(orderedExports)};`);
return {
code: s.toString(),
map: s.generateMap({ hires: true, source: id }),
};
},
};
// TODO: Maybe convert `injectExportOrderPlugin` to function that returns object,
// and run `await init;` once and then call `parse()` without `await`,
// instead of calling `await parse()` every time.
const [, exports] = await parse(code);
if (exports.includes('__namedExportsOrder')) {
// user has defined named exports already
return undefined;
}
const s = new MagicString(code);
const orderedExports = exports.filter((e) => e !== 'default');
s.append(`;export const __namedExportsOrder = ${JSON.stringify(orderedExports)};`);
return {
code: s.toString(),
map: s.generateMap({ hires: true, source: id }),
};
},
};
}

View File

@ -1,5 +1,4 @@
import type { Plugin } from 'vite';
import { createFilter } from 'vite';
import MagicString from 'magic-string';
/**
@ -7,7 +6,9 @@ import MagicString from 'magic-string';
* as hmr boundaries, but vite has a bug which causes them to be treated as boundaries
* (https://github.com/vitejs/vite/issues/9869).
*/
export function stripStoryHMRBoundary(): Plugin {
export async function stripStoryHMRBoundary(): Promise<Plugin> {
const { createFilter } = await import('vite');
const filter = createFilter(/\.stories\.([tj])sx?$/);
return {
name: 'storybook:strip-hmr-boundary-plugin',

View File

@ -1,5 +1,4 @@
import * as path from 'path';
import { loadConfigFromFile, mergeConfig } from 'vite';
import findCacheDirectory from 'find-cache-dir';
import type {
ConfigEnv,
@ -41,6 +40,8 @@ export async function commonConfig(
_type: PluginConfigType
): Promise<ViteInlineConfig> {
const configEnv = _type === 'development' ? configEnvServe : configEnvBuild;
const { loadConfigFromFile, mergeConfig } = await import('vite');
const { viteConfigPath } = await getBuilderOptions<BuilderOptions>(options);
const projectRoot = path.resolve(options.configDir, '..');
@ -84,8 +85,8 @@ export async function pluginConfig(options: Options) {
const plugins = [
codeGeneratorPlugin(options),
await csfPlugin(options),
injectExportOrderPlugin,
stripStoryHMRBoundary(),
await injectExportOrderPlugin(),
await stripStoryHMRBoundary(),
{
name: 'storybook:allow-storybook-dir',
enforce: 'post',

View File

@ -1,5 +1,4 @@
import type { Server } from 'http';
import { createServer } from 'vite';
import type { Options } from '@storybook/types';
import { commonConfig } from './vite-config';
import { getOptimizeDeps } from './optimizeDeps';
@ -29,5 +28,6 @@ export async function createViteServer(options: Options, devServer: Server) {
const finalConfig = await presets.apply('viteFinal', config, options);
const { createServer } = await import('vite');
return createServer(await sanitizeEnvVars(options, finalConfig));
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-webpack5",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
@ -66,7 +66,7 @@
"@storybook/preview": "workspace:*",
"@storybook/preview-api": "workspace:*",
"@swc/core": "^1.3.82",
"@types/node": "^16.0.0",
"@types/node": "^18.0.0",
"@types/semver": "^7.3.4",
"babel-loader": "^9.0.0",
"babel-plugin-named-exports-order": "^0.0.2",

View File

@ -201,7 +201,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({
const { warnings, errors } = getWebpackStats({ config, stats });
if (warnings.length > 0) {
warnings?.forEach((e) => logger.error(e.message));
warnings?.forEach((e) => logger.warn(e.message));
}
if (errors.length > 0) {
@ -270,10 +270,11 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime,
const { warnings, errors } = getWebpackStats({ config, stats });
if (warnings.length > 0) {
warnings?.forEach((e) => logger.error(e.message));
warnings?.forEach((e) => logger.warn(e.message));
}
if (errors.length > 0) {
errors.forEach((e) => logger.error(e.message));
compiler.close(() => fail(new WebpackCompilationError({ errors })));
return;
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addons",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook addons store",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/channel-postmessage",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/channel-websocket",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/client-api",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook Client API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-client",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/api",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook Manager API (facade)",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preview-web",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/store",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "",
"keywords": [
"storybook"
@ -55,6 +55,5 @@
],
"platform": "node",
"shim": "@storybook/preview-api/dist/store"
},
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae17"
}
}

View File

@ -70,4 +70,31 @@ test.describe('addon-controls', () => {
const label = await sbPage.panelContent().locator('textarea[name=label]').inputValue();
await expect(label).toEqual('Hello world');
});
test('should set select option when value contains double spaces', async ({ page }) => {
await page.goto(`${storybookUrl}?path=/story/addons-controls-basics--undefined`);
const sbPage = new SbPage(page);
await sbPage.waitUntilLoaded();
await sbPage.viewAddonPanel('Controls');
await sbPage.panelContent().locator('#control-select').selectOption('double space');
await expect(sbPage.panelContent().locator('#control-select')).toHaveValue('double space');
await expect(page).toHaveURL(/.*select:double\+\+space.*/);
});
test('should set multiselect option when value contains double spaces', async ({ page }) => {
await page.goto(`${storybookUrl}?path=/story/addons-controls-basics--undefined`);
const sbPage = new SbPage(page);
await sbPage.waitUntilLoaded();
await sbPage.viewAddonPanel('Controls');
await sbPage.panelContent().locator('#control-multiSelect').selectOption('double space');
await expect(sbPage.panelContent().locator('#control-multiSelect')).toHaveValue(
'double space'
);
await expect(page).toHaveURL(/.*multiSelect\[0]:double\+\+space.*/);
});
});

View File

@ -0,0 +1,40 @@
/* eslint-disable jest/no-disabled-tests */
import { test, expect } from '@playwright/test';
import process from 'process';
import { SbPage } from './util';
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:6006';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME;
test.describe('Svelte', () => {
test.skip(
// eslint-disable-next-line jest/valid-title
!templateName?.includes('svelte'),
'Only run this test on Svelte'
);
test.beforeEach(async ({ page }) => {
await page.goto(storybookUrl);
await new SbPage(page).waitUntilLoaded();
});
test('JS story has auto-generated args table', async ({ page }) => {
const sbPage = new SbPage(page);
await sbPage.navigateToStory('stories/renderers/svelte/js-docs', 'docs');
const root = sbPage.previewRoot();
const argsTable = root.locator('.docblock-argstable');
await expect(argsTable).toContainText('Rounds the button');
});
test('TS story has auto-generated args table', async ({ page }) => {
// eslint-disable-next-line jest/valid-title
test.skip(!templateName?.endsWith('ts') || false, 'Only test TS story in TS templates');
const sbPage = new SbPage(page);
await sbPage.navigateToStory('stories/renderers/svelte/ts-docs', 'docs');
const root = sbPage.previewRoot();
const argsTable = root.locator('.docblock-argstable');
await expect(argsTable).toContainText('Rounds the button');
});
});

View File

@ -0,0 +1,22 @@
import { test, expect } from '@playwright/test';
import process from 'process';
import { SbPage } from './util';
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
test.describe('navigating', () => {
test('a URL with a partial storyId will redirect to the first story', async ({ page }) => {
// this is purposefully not using the SbPage class, and the URL is a partial (it does not contain the full storyId)
await page.goto(`${storybookUrl}?path=/story/example-button`);
const sbPage = new SbPage(page);
await sbPage.waitUntilLoaded();
await page.waitForFunction(() =>
window.document.location.href.match('/docs/example-button--docs')
);
await expect(sbPage.page.url()).toContain('/docs/example-button--docs');
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
"version": "7.5.0-alpha.4",
"version": "7.6.0-alpha.1",
"description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
"keywords": [
"storybook",
@ -51,7 +51,7 @@
"@storybook/preview-api": "workspace:*",
"@storybook/telemetry": "workspace:*",
"@storybook/types": "workspace:*",
"@types/node": "^16.0.0",
"@types/node": "^18.0.0",
"@types/react": "^16.14.34",
"@types/react-dom": "^16.9.14",
"@types/semver": "^7.3.4",
@ -106,7 +106,7 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
"rxjs": "^6.0.0 || ^7.4.0",
"typescript": "^4.0.0 || ^5.0.0",
"zone.js": "^0.8.29 || >= 0.9.0 < 1.0.0"
"zone.js": ">= 0.11.1 < 1.0.0"
},
"peerDependenciesMeta": {
"@angular/cli": {

View File

@ -21,6 +21,7 @@ import { addToGlobalContext } from '@storybook/telemetry';
import { buildStaticStandalone, withTelemetry } from '@storybook/core-server';
import {
AssetPattern,
SourceMapUnion,
StyleElement,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
import { StandaloneOptions } from '../utils/standalone-options';
@ -39,10 +40,18 @@ export type StorybookBuilderOptions = JsonObject & {
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
} & Pick<
// makes sure the option exists
CLIOptions,
'outputDir' | 'configDir' | 'loglevel' | 'quiet' | 'webpackStatsJson' | 'disableTelemetry'
| 'outputDir'
| 'configDir'
| 'loglevel'
| 'quiet'
| 'webpackStatsJson'
| 'disableTelemetry'
| 'debugWebpack'
| 'previewUrl'
>;
export type StorybookBuilderOutput = JsonObject & BuilderOutput & { [key: string]: any };
@ -81,8 +90,11 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (
quiet,
enableProdMode = true,
webpackStatsJson,
debugWebpack,
disableTelemetry,
assets,
previewUrl,
sourceMap = false,
} = options;
const standaloneOptions: StandaloneBuildOptions = {
@ -100,9 +112,12 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (
...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}),
...(styles ? { styles } : {}),
...(assets ? { assets } : {}),
sourceMap,
},
tsConfig,
webpackStatsJson,
debugWebpack,
previewUrl,
};
return standaloneOptions;

View File

@ -29,6 +29,11 @@
"description": "Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent].",
"pattern": "(silly|verbose|info|warn|silent)"
},
"debugWebpack": {
"type": "boolean",
"description": "Debug the Webpack configuration",
"default": false
},
"enableProdMode": {
"type": "boolean",
"description": "Disable Angular's development mode, which turns off assertions and other checks within the framework.",
@ -62,6 +67,10 @@
"description": "Write Webpack Stats JSON to disk",
"default": false
},
"previewUrl": {
"type": "string",
"description": "Disables the default storybook preview and lets you use your own"
},
"styles": {
"type": "array",
"description": "Global styles to be included in the build.",
@ -92,6 +101,11 @@
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"sourceMap": {
"type": ["boolean", "object"],
"description": "Configure sourcemaps. See: https://angular.io/guide/workspace-config#source-map-configuration",
"default": false
}
},
"additionalProperties": false,

View File

@ -19,6 +19,7 @@ import { addToGlobalContext } from '@storybook/telemetry';
import { buildDevStandalone, withTelemetry } from '@storybook/core-server';
import {
AssetPattern,
SourceMapUnion,
StyleElement,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
import { StandaloneOptions } from '../utils/standalone-options';
@ -36,6 +37,7 @@ export type StorybookBuilderOptions = JsonObject & {
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
} & Pick<
// makes sure the option exists
CLIOptions,
@ -53,6 +55,10 @@ export type StorybookBuilderOptions = JsonObject & {
| 'initialPath'
| 'open'
| 'docs'
| 'debugWebpack'
| 'webpackStatsJson'
| 'loglevel'
| 'previewUrl'
>;
export type StorybookBuilderOutput = JsonObject & BuilderOutput & {};
@ -103,6 +109,11 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (options, cont
assets,
initialPath,
open,
debugWebpack,
loglevel,
webpackStatsJson,
previewUrl,
sourceMap = false,
} = options;
const standaloneOptions: StandaloneOptions = {
@ -126,10 +137,15 @@ const commandBuilder: BuilderHandlerFn<StorybookBuilderOptions> = (options, cont
...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}),
...(styles ? { styles } : {}),
...(assets ? { assets } : {}),
sourceMap,
},
tsConfig,
initialPath,
open,
debugWebpack,
loglevel,
webpackStatsJson,
previewUrl,
};
return standaloneOptions;

View File

@ -10,6 +10,11 @@
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
"default": null
},
"debugWebpack": {
"type": "boolean",
"description": "Debug the Webpack configuration",
"default": false
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
@ -123,6 +128,25 @@
"initialPath": {
"type": "string",
"description": "URL path to be appended when visiting Storybook for the first time"
},
"webpackStatsJson": {
"type": ["boolean", "string"],
"description": "Write Webpack Stats JSON to disk",
"default": false
},
"previewUrl": {
"type": "string",
"description": "Disables the default storybook preview and lets you use your own"
},
"loglevel": {
"type": "string",
"description": "Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent].",
"pattern": "(silly|verbose|info|warn|silent)"
},
"sourceMap": {
"type": ["boolean", "object"],
"description": "Configure sourcemaps. See: https://angular.io/guide/workspace-config#source-map-configuration",
"default": false
}
},
"additionalProperties": false,

View File

@ -12,7 +12,7 @@ export const printErrorDetails = (error: any): void => {
} else if ((error as any).stats && (error as any).stats.compilation.errors) {
(error as any).stats.compilation.errors.forEach((e: any) => logger.plain(e));
} else {
logger.error(error);
logger.error(error as any);
}
} else if (error.compilation?.errors) {
error.compilation.errors.forEach((e: any) => logger.plain(e));

View File

@ -1,6 +1,7 @@
import { BuilderContext } from '@angular-devkit/architect';
import {
AssetPattern,
SourceMapUnion,
StyleElement,
StylePreprocessorOptions,
} from '@angular-devkit/build-angular/src/builders/browser/schema';
@ -16,6 +17,7 @@ export type StandaloneOptions = CLIOptions &
styles?: StyleElement[];
stylePreprocessorOptions?: StylePreprocessorOptions;
assets?: AssetPattern[];
sourceMap?: SourceMapUnion;
};
angularBuilderContext?: BuilderContext | null;
tsConfig?: string;

View File

@ -18,6 +18,7 @@ const TestComponent1 = Component({})(class {});
const TestComponent2 = Component({})(class {});
const StandaloneTestComponent = Component({ standalone: true })(class {});
const TestDirective = Directive({})(class {});
const StandaloneTestDirective = Directive({ standalone: true })(class {});
const TestModuleWithDeclarations = NgModule({ declarations: [TestComponent1] })(class {});
const TestModuleWithImportsAndProviders = NgModule({
imports: [TestModuleWithDeclarations],
@ -118,6 +119,20 @@ describe('PropertyExtractor', () => {
StandaloneTestComponent,
]);
});
it('should return standalone directives', () => {
const imports = extractImports(
{
imports: [TestModuleWithImportsAndProviders],
},
StandaloneTestDirective
);
expect(imports).toEqual([
CommonModule,
TestModuleWithImportsAndProviders,
StandaloneTestDirective,
]);
});
});
describe('extractDeclarations', () => {

View File

@ -173,7 +173,7 @@ export class PropertyExtractor implements NgModuleMetadata {
const isPipe = decorators.some((d) => this.isDecoratorInstanceOf(d, 'Pipe'));
const isDeclarable = isComponent || isDirective || isPipe;
const isStandalone = isComponent && decorators.some((d) => d.standalone);
const isStandalone = (isComponent || isDirective) && decorators.some((d) => d.standalone);
return { isDeclarable, isStandalone };
};

View File

@ -0,0 +1,102 @@
import { argsToTemplate, ArgsToTemplateOptions } from './argsToTemplate'; // adjust path
describe('argsToTemplate', () => {
it('should correctly convert args to template string and exclude undefined values', () => {
const args: Record<string, any> = {
prop1: 'value1',
prop2: undefined,
prop3: 'value3',
};
const options: ArgsToTemplateOptions<keyof typeof args> = {};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1" [prop3]="prop3"');
});
it('should include properties from include option', () => {
const args = {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
};
const options: ArgsToTemplateOptions<keyof typeof args> = {
include: ['prop1', 'prop3'],
};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1" [prop3]="prop3"');
});
it('should include non-undefined properties from include option', () => {
const args: Record<string, any> = {
prop1: 'value1',
prop2: 'value2',
prop3: undefined,
};
const options: ArgsToTemplateOptions<keyof typeof args> = {
include: ['prop1', 'prop3'],
};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1"');
});
it('should exclude properties from exclude option', () => {
const args = {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
};
const options: ArgsToTemplateOptions<keyof typeof args> = {
exclude: ['prop2'],
};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1" [prop3]="prop3"');
});
it('should exclude properties from exclude option and undefined properties', () => {
const args: Record<string, any> = {
prop1: 'value1',
prop2: 'value2',
prop3: undefined,
};
const options: ArgsToTemplateOptions<keyof typeof args> = {
exclude: ['prop2'],
};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1"');
});
it('should prioritize include over exclude when both options are given', () => {
const args = {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
};
const options: ArgsToTemplateOptions<keyof typeof args> = {
include: ['prop1', 'prop2'],
exclude: ['prop2', 'prop3'],
};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1" [prop2]="prop2"');
});
it('should work when neither include nor exclude options are given', () => {
const args = {
prop1: 'value1',
prop2: 'value2',
};
const options: ArgsToTemplateOptions<keyof typeof args> = {};
const result = argsToTemplate(args, options);
expect(result).toBe('[prop1]="prop1" [prop2]="prop2"');
});
it('should bind events correctly when value is a function', () => {
const args = { event1: () => {}, event2: () => {} };
const result = argsToTemplate(args, {});
expect(result).toEqual('(event1)="event1($event)" (event2)="event2($event)"');
});
it('should mix properties and events correctly', () => {
const args = { input: 'Value1', event1: () => {} };
const result = argsToTemplate(args, {});
expect(result).toEqual('[input]="input" (event1)="event1($event)"');
});
});

View File

@ -0,0 +1,74 @@
/**
* Options for controlling the behavior of the argsToTemplate function.
*
* @template T The type of the keys in the target object.
*/
export interface ArgsToTemplateOptions<T> {
/**
* An array of keys to specifically include in the output.
* If provided, only the keys from this array will be included in the output,
* irrespective of the `exclude` option. Undefined values will still be excluded from the output.
*/
include?: Array<T>;
/**
* An array of keys to specifically exclude from the output.
* If provided, these keys will be omitted from the output. This option is
* ignored if the `include` option is also provided
*/
exclude?: Array<T>;
}
/**
* Converts an object of arguments to a string of property and event bindings and excludes undefined values.
* Why? Because Angular treats undefined values in property bindings as an actual value
* and does not apply the default value of the property as soon as the binding is set.
* This feels counter-intuitive and is a common source of bugs in stories.
* @example
* ```ts
* // component.ts
*@Component({ selector: 'example' })
* export class ExampleComponent {
* @Input() input1: string = 'Default Input1';
* @Input() input2: string = 'Default Input2';
* @Output() click = new EventEmitter();
* }
*
* // component.stories.ts
* import { argsToTemplate } from '@storybook/angular';
* export const Input1: Story = {
* render: (args) => ({
* props: args,
* // Problem1: <example [input1]="input1" [input2]="input2" (click)="click($event)"></example>
* // This will set input2 to undefined and the internal default value will not be used.
* // Problem2: <example [input1]="input1" (click)="click($event)"></example>
* // The default value of input2 will be used, but it is not overridable by the user via controls.
* // Solution: Now the controls will be applicable to both input1 and input2, and the default values will be used if the user does not override them.
* template: `<example ${argsToTemplate(args)}"></example>`,
* }),
* args: {
* // In this Story, we want to set the input1 property, and the internal default property of input2 should be used.
* input1: 'Input 1',
* click: { action: 'clicked' },
* },
*};
* ```
*/
export function argsToTemplate<A extends Record<string, any>>(
args: A,
options: ArgsToTemplateOptions<keyof A> = {}
) {
const includeSet = options.include ? new Set(options.include) : null;
const excludeSet = options.exclude ? new Set(options.exclude) : null;
return Object.entries(args)
.filter(([key]) => args[key] !== undefined)
.filter(([key]) => {
if (includeSet) return includeSet.has(key);
if (excludeSet) return !excludeSet.has(key);
return true;
})
.map(([key, value]) =>
typeof value === 'function' ? `(${key})="${key}($event)"` : `[${key}]="${key}"`
)
.join(' ');
}

View File

@ -25,7 +25,7 @@ import { global } from '@storybook/global';
/** *************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js'; // Included with Angular CLI.
/** *************************************************************************************************
* APPLICATION IMPORTS

View File

@ -10,6 +10,7 @@ export * from './public-types';
export type { StoryFnAngularReturnType as IStory } from './types';
export { moduleMetadata, componentWrapperDecorator, applicationConfig } from './decorators';
export { argsToTemplate } from './argsToTemplate';
// optimization: stop HMR propagation in webpack
if (typeof module !== 'undefined') module?.hot?.decline();

View File

@ -1,24 +1,29 @@
import { Args } from '@storybook/angular';
import { Meta, StoryObj, argsToTemplate } from '@storybook/angular';
import { DocButtonComponent } from './doc-button.component';
export default {
const meta: Meta<DocButtonComponent<any>> = {
component: DocButtonComponent,
};
export const Basic = (args: Args) => ({
props: args,
});
Basic.args = { label: 'Args test', isDisabled: false };
Basic.ArgTypes = {
theDefaultValue: {
table: {
defaultValue: { summary: 'Basic default value' },
export default meta;
type Story = StoryObj<DocButtonComponent<any>>;
export const Basic: Story = {
args: { label: 'Args test', isDisabled: false },
argTypes: {
theDefaultValue: {
table: {
defaultValue: { summary: 'Basic default value' },
},
},
},
};
export const WithTemplate = (args: Args) => ({
props: args,
template: '<my-button [label]="label" [appearance]="appearance"></my-button>',
});
WithTemplate.args = { label: 'Template test', appearance: 'primary' };
export const WithTemplate: Story = {
args: { label: 'Template test', appearance: 'primary' },
render: (args) => ({
props: args,
template: `<my-button ${argsToTemplate(args)}></my-button>`,
}),
};

View File

@ -1,14 +1,19 @@
import { Meta, StoryObj } from '@storybook/angular';
import { DocDirective } from './doc-directive.directive';
export default {
const meta: Meta<DocDirective> = {
component: DocDirective,
};
const modules = {
declarations: [DocDirective],
};
export default meta;
export const Basic = () => ({
moduleMetadata: modules,
template: '<div docDirective [hasGrayBackground]="true"><h1>DocDirective</h1></div>',
});
type Story = StoryObj<DocDirective>;
export const Basic: Story = {
render: () => ({
moduleMetadata: {
declarations: [DocDirective],
},
template: '<div docDirective [hasGrayBackground]="true"><h1>DocDirective</h1></div>',
}),
};

View File

@ -1,14 +1,19 @@
import { Meta, StoryObj } from '@storybook/angular';
import { DocInjectableService } from './doc-injectable.service';
export default {
const meta: Meta<DocInjectableService> = {
component: DocInjectableService,
};
const modules = {
provider: [DocInjectableService],
};
export default meta;
export const Basic = () => ({
moduleMetadata: modules,
template: '<div><h1>DocInjectable</h1></div>',
});
type Story = StoryObj<DocInjectableService>;
export const Basic: Story = {
render: () => ({
moduleMetadata: {
providers: [DocInjectableService],
},
template: '<div><h1>DocInjectable</h1></div>',
}),
};

View File

@ -1,14 +1,19 @@
import { Meta, StoryObj } from '@storybook/angular';
import { DocPipe } from './doc-pipe.pipe';
export default {
const meta: Meta<DocPipe> = {
component: DocPipe,
};
const modules = {
declarations: [DocPipe],
};
export default meta;
export const Basic = () => ({
moduleMetadata: modules,
template: `<div><h1>{{ 'DocPipe' | docPipe }}</h1></div>`,
});
type Story = StoryObj<DocPipe>;
export const Basic: Story = {
render: () => ({
moduleMetadata: {
declarations: [DocPipe],
},
template: `<div><h1>{{ 'DocPipe' | docPipe }}</h1></div>`,
}),
};

View File

@ -1,8 +1,8 @@
import { FormsModule } from '@angular/forms';
import { Meta, StoryFn, moduleMetadata } from '@storybook/angular';
import { Meta, StoryFn, StoryObj, moduleMetadata } from '@storybook/angular';
import { CustomCvaComponent } from './custom-cva.component';
export default {
const meta: Meta<CustomCvaComponent> = {
// title: 'Basics / Angular forms / ControlValueAccessor',
component: CustomCvaComponent,
decorators: [
@ -17,11 +17,16 @@ export default {
],
} as Meta;
export const SimpleInput: StoryFn = () => ({
props: {
ngModel: 'Type anything',
ngModelChange: () => {},
},
});
export default meta;
SimpleInput.storyName = 'Simple input';
type Story = StoryObj<CustomCvaComponent>;
export const SimpleInput: Story = {
name: 'Simple input',
render: () => ({
props: {
ngModel: 'Type anything',
ngModelChange: () => {},
},
}),
};

View File

@ -1,8 +1,13 @@
import { Meta, StoryObj } from '@storybook/angular';
import { AttributeSelectorComponent } from './attribute-selector.component';
export default {
const meta: Meta<AttributeSelectorComponent> = {
// title: 'Basics / Component / With Complex Selectors',
component: AttributeSelectorComponent,
};
export const AttributeSelectors = {};
export default meta;
type Story = StoryObj<AttributeSelectorComponent>;
export const AttributeSelectors: Story = {};

View File

@ -1,4 +1,4 @@
import { Meta, StoryFn } from '@storybook/angular';
import { Meta, StoryObj } from '@storybook/angular';
import {
EnumsComponent,
EnumNumeric,
@ -6,19 +6,22 @@ import {
EnumStringValues,
} from './enums.component';
export default {
const meta: Meta<EnumsComponent> = {
// title: 'Basics / Component / With Enum Types',
component: EnumsComponent,
} as Meta;
export const Basic: StoryFn = (args) => ({
props: args,
});
Basic.args = {
unionType: 'union a',
aliasedUnionType: 'Type Alias 1',
enumNumeric: EnumNumeric.FIRST,
enumNumericInitial: EnumNumericInitial.UNO,
enumStrings: EnumStringValues.PRIMARY,
enumAlias: EnumNumeric.FIRST,
};
export default meta;
type Story = StoryObj<EnumsComponent>;
export const Basic: Story = {
args: {
unionType: 'Union A',
aliasedUnionType: 'Type Alias 1',
enumNumeric: EnumNumeric.FIRST,
enumNumericInitial: EnumNumericInitial.UNO,
enumStrings: EnumStringValues.PRIMARY,
enumAlias: EnumNumeric.FIRST,
},
};

View File

@ -1,12 +1,15 @@
import { Meta, StoryObj } from '@storybook/angular';
import { BaseButtonComponent } from './base-button.component';
export default {
const meta: Meta<BaseButtonComponent> = {
// title: 'Basics / Component / With Inheritance',
component: BaseButtonComponent,
};
export const BaseButton = () => ({
props: {
export default meta;
export const BaseButton: StoryObj<BaseButtonComponent> = {
args: {
label: 'this is label',
},
});
};

View File

@ -1,13 +1,18 @@
import { Meta, StoryObj } from '@storybook/angular';
import { IconButtonComponent } from './icon-button.component';
export default {
const meta: Meta<IconButtonComponent> = {
// title: 'Basics / Component / With Inheritance',
component: IconButtonComponent,
};
export const IconButton = () => ({
props: {
export default meta;
type Story = StoryObj<IconButtonComponent>;
export const IconButton: Story = {
args: {
icon: 'this is icon',
label: 'this is label',
},
});
};

View File

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core';
import { componentWrapperDecorator, Meta, StoryFn } from '@storybook/angular';
import { componentWrapperDecorator, Meta, StoryObj } from '@storybook/angular';
@Component({
selector: 'sb-button',
@ -17,7 +17,7 @@ class SbButtonComponent {
color = '#5eadf5';
}
export default {
const meta: Meta<SbButtonComponent> = {
// title: 'Basics / Component / With ng-content / Button with different contents',
// Implicitly declares the component to Angular
// This will be the component described by the addon docs
@ -35,24 +35,24 @@ export default {
},
} as Meta;
export default meta;
type Story = StoryObj<SbButtonComponent>;
// By default storybook uses the default export component if no template or component is defined in the story
// So Storybook nests the component twice because it is first added by the componentWrapperDecorator.
export const AlwaysDefineTemplateOrComponent: StoryFn = () => ({});
export const AlwaysDefineTemplateOrComponent: Story = {};
export const EmptyButton: StoryFn = () => ({
template: '',
});
export const WithDynamicContentAndArgs: StoryFn = (args) => ({
template: `${args['content']}`,
});
WithDynamicContentAndArgs.argTypes = {
content: { control: 'text' },
export const EmptyButton: Story = {
render: () => ({
template: '',
}),
};
WithDynamicContentAndArgs.args = { content: 'My button text' };
export const InH1: StoryFn = () => ({
template: 'My button in h1',
});
InH1.decorators = [componentWrapperDecorator((story) => `<h1>${story}</h1>`)];
InH1.storyName = 'In <h1>';
export const InH1: Story = {
render: () => ({
template: 'My button in h1',
}),
decorators: [componentWrapperDecorator((story) => `<h1>${story}</h1>`)],
name: 'In <h1>',
};

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { Meta, StoryFn } from '@storybook/angular/';
import { Meta, StoryObj } from '@storybook/angular';
@Component({
selector: 'storybook-with-ng-content',
@ -9,21 +9,29 @@ import { Meta, StoryFn } from '@storybook/angular/';
})
class WithNgContentComponent {}
export default {
const meta: Meta<WithNgContentComponent> = {
// title: 'Basics / Component / With ng-content / Simple',
component: WithNgContentComponent,
} as Meta;
export const OnlyComponent: StoryFn = () => ({});
export default meta;
export const Default: StoryFn = () => ({
template: `<storybook-with-ng-content><h1>This is rendered in ng-content</h1></storybook-with-ng-content>`,
});
type Story = StoryObj<WithNgContentComponent & { content: string }>;
export const WithDynamicContentAndArgs: StoryFn = (args) => ({
template: `<storybook-with-ng-content><h1>${args['content']}</h1></storybook-with-ng-content>`,
});
WithDynamicContentAndArgs.argTypes = {
content: { control: 'text' },
export const OnlyComponent: Story = {};
export const Default: Story = {
render: () => ({
template: `<storybook-with-ng-content><h1>This is rendered in ng-content</h1></storybook-with-ng-content>`,
}),
};
export const WithDynamicContentAndArgs: Story = {
render: (args) => ({
template: `<storybook-with-ng-content><h1>${args['content']}</h1></storybook-with-ng-content>`,
}),
args: { content: 'Default content' },
argTypes: {
content: { control: 'text' },
},
};
WithDynamicContentAndArgs.args = { content: 'Default content' };

View File

@ -27,7 +27,7 @@ class OnDestroyComponent implements OnInit, OnDestroy {
}
}
export default {
const meta: Meta<OnDestroyComponent> = {
// title: 'Basics / Component / with ngOnDestroy',
component: OnDestroyComponent,
parameters: {
@ -37,4 +37,8 @@ export default {
},
} as Meta;
export const SimpleComponent: StoryObj = {};
export default meta;
type Story = StoryObj<OnDestroyComponent>;
export const SimpleComponent: Story = {};

View File

@ -1,7 +1,7 @@
import { Meta, StoryFn } from '@storybook/angular';
import { Meta, StoryObj } from '@storybook/angular';
import { OnPushBoxComponent } from './on-push-box.component';
export default {
const meta: Meta<OnPushBoxComponent> = {
// title: 'Basics / Component / With OnPush strategy',
component: OnPushBoxComponent,
argTypes: {
@ -12,10 +12,12 @@ export default {
word: 'The text',
bgColor: '#FFF000',
},
} as Meta;
};
export const ClassSpecifiedComponentWithOnPushAndArgs: StoryFn = (args) => ({
props: args,
});
ClassSpecifiedComponentWithOnPushAndArgs.storyName =
'Class-specified component with OnPush and Args';
export default meta;
type Story = StoryObj<OnPushBoxComponent>;
export const ClassSpecifiedComponentWithOnPushAndArgs: Story = {
name: 'Class-specified component with OnPush and Args',
};

View File

@ -1,9 +1,9 @@
import { Meta, StoryFn, moduleMetadata } from '@storybook/angular';
import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { CustomPipePipe } from './custom.pipe';
import { WithPipeComponent } from './with-pipe.component';
export default {
const meta: Meta<WithPipeComponent> = {
// title: 'Basics / Component / With Pipes',
component: WithPipeComponent,
decorators: [
@ -11,21 +11,26 @@ export default {
declarations: [CustomPipePipe],
}),
],
} as Meta;
};
export const Simple: StoryFn = () => ({
props: {
field: 'foobar',
export default meta;
type Story = StoryObj<WithPipeComponent>;
export const Simple: Story = {
render: () => ({
props: {
field: 'foobar',
},
}),
};
export const WithArgsStory: Story = {
name: 'With args',
argTypes: {
field: { control: 'text' },
},
args: {
field: 'Foo Bar',
},
});
export const WithArgsStory: StoryFn = (args) => ({
props: args,
});
WithArgsStory.storyName = 'With args';
WithArgsStory.argTypes = {
field: { control: 'text' },
};
WithArgsStory.args = {
field: 'Foo Bar',
};

View File

@ -1,26 +1,30 @@
import { Args } from '@storybook/angular';
import { Args, Meta, StoryObj } from '@storybook/angular';
import { DiComponent } from './di.component';
export default {
const meta: Meta<DiComponent> = {
// title: 'Basics / Component / With Provider',
component: DiComponent,
};
export const InputsAndInjectDependencies = () => ({
props: {
export default meta;
type Story = StoryObj<DiComponent>;
export const InputsAndInjectDependencies: Story = {
render: () => ({
props: {
title: 'Component dependencies',
},
}),
name: 'inputs and inject dependencies',
};
export const InputsAndInjectDependenciesWithArgs: Story = {
name: 'inputs and inject dependencies with args',
argTypes: {
title: { control: 'text' },
},
args: {
title: 'Component dependencies',
},
});
InputsAndInjectDependencies.storyName = 'inputs and inject dependencies';
export const InputsAndInjectDependenciesWithArgs = (args: Args) => ({
props: args,
});
InputsAndInjectDependenciesWithArgs.storyName = 'inputs and inject dependencies with args';
InputsAndInjectDependenciesWithArgs.argTypes = {
title: { control: 'text' },
};
InputsAndInjectDependenciesWithArgs.args = {
title: 'Component dependencies',
};

View File

@ -1,10 +1,15 @@
import { Meta, StoryObj } from '@storybook/angular';
import { StyledComponent } from './styled.component';
export default {
const meta: Meta = {
// title: 'Basics / Component / With StyleUrls',
component: StyledComponent,
};
export const ComponentWithStyles = () => ({});
export default meta;
ComponentWithStyles.storyName = 'Component with styles';
type Story = StoryObj<StyledComponent>;
export const ComponentWithStyles: Story = {
name: 'Component with styles',
};

View File

@ -0,0 +1,27 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-template',
imports: [CommonModule],
template: `<div (click)="event($event)">
Label: {{ label }}
<br />
Label2: {{ label2 }}
<br />
<button (click)="inc()">+</button>
</div>`,
styles: [],
standalone: true,
})
export class Template {
@Input() label = 'default label';
@Input() label2 = 'default label2';
@Output() changed = new EventEmitter<string>();
inc() {
this.changed.emit('Increase');
}
}

View File

@ -0,0 +1,24 @@
import { Meta, StoryObj, argsToTemplate } from '@storybook/angular';
import { Template } from './template.component';
const meta: Meta<Template> = {
component: Template,
};
export default meta;
type Story = StoryObj<Template>;
export const Default: Story = {
render: (args) => ({
props: args,
template: `<app-template ${argsToTemplate(args)}></app-template>`,
}),
};
export const SetOneInput: Story = {
...Default,
args: {
label: 'Label Example 1',
},
};

View File

@ -1,8 +1,8 @@
import { OnInit, Type, Component, Injector, Input } from '@angular/core';
import { StoryFn, Meta, componentWrapperDecorator, moduleMetadata } from '@storybook/angular';
import { Meta, componentWrapperDecorator, moduleMetadata, StoryObj } from '@storybook/angular';
import { WithoutSelectorComponent, WITHOUT_SELECTOR_DATA } from './without-selector.component';
export default {
const meta: Meta<WithoutSelectorComponent> = {
// title: 'Basics / Component / without selector / Custom wrapper *NgComponentOutlet',
component: WithoutSelectorComponent,
decorators: [
@ -12,6 +12,10 @@ export default {
],
} as Meta;
export default meta;
type Story = StoryObj<WithoutSelectorComponent>;
// Advanced example with custom *ngComponentOutlet
@Component({
@ -51,23 +55,22 @@ class NgComponentOutletWrapperComponent implements OnInit {
// Live changing of args by controls does not work at the moment. When changing args storybook does not fully
// reload and therefore does not take into account the change of provider.
export const WithCustomNgComponentOutletWrapper: StoryFn = (args) => ({
props: args,
});
WithCustomNgComponentOutletWrapper.storyName = 'Custom wrapper *NgComponentOutlet';
WithCustomNgComponentOutletWrapper.argTypes = {
name: { control: 'text' },
color: { control: 'color' },
};
WithCustomNgComponentOutletWrapper.args = { name: 'Color', color: 'green' };
WithCustomNgComponentOutletWrapper.decorators = [
moduleMetadata({
declarations: [NgComponentOutletWrapperComponent],
}),
componentWrapperDecorator(NgComponentOutletWrapperComponent, (args) => ({
name: args.name,
export const WithCustomNgComponentOutletWrapper: Story = {
name: 'Custom wrapper *NgComponentOutlet',
argTypes: {
name: { control: 'text' },
color: { control: 'color' },
},
args: { name: 'Color', color: 'green' },
decorators: [
moduleMetadata({
declarations: [NgComponentOutletWrapperComponent],
}),
componentWrapperDecorator(NgComponentOutletWrapperComponent, (args) => ({
name: args.name,
color: args['color'],
componentOutlet: WithoutSelectorComponent,
})),
];
color: args['color'],
componentOutlet: WithoutSelectorComponent,
})),
],
};

View File

@ -1,71 +0,0 @@
import {
AfterViewInit,
ComponentFactoryResolver,
Type,
Component,
Input,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { StoryFn, Meta, componentWrapperDecorator, moduleMetadata } from '@storybook/angular';
import { WithoutSelectorComponent } from './without-selector.component';
export default {
// title: 'Basics / Component / without selector / Custom wrapper ComponentFactoryResolver',
component: WithoutSelectorComponent,
decorators: [
moduleMetadata({
entryComponents: [WithoutSelectorComponent],
}),
],
} as Meta;
// Advanced example with custom ComponentFactoryResolver
@Component({ selector: 'component-factory-wrapper', template: '' })
class ComponentFactoryWrapperComponent implements AfterViewInit {
@ViewChild('dynamicInsert', { read: ViewContainerRef }) dynamicInsert: any;
@Input()
componentOutlet?: Type<unknown>;
@Input()
args: any;
constructor(
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngAfterViewInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
this.componentOutlet!
);
const containerRef = this.viewContainerRef;
containerRef.clear();
const dynamicComponent = containerRef.createComponent(componentFactory);
Object.assign(dynamicComponent.instance as any, this.args);
}
}
// Live changing of args by controls does not work at the moment. When changing args storybook does not fully
// reload and therefore does not take into account the change of provider.
export const WithComponentFactoryResolver: StoryFn = (args) => ({
props: args,
});
WithComponentFactoryResolver.storyName = 'Custom wrapper ComponentFactoryResolver';
WithComponentFactoryResolver.argTypes = {
name: { control: 'text' },
color: { control: 'color' },
};
WithComponentFactoryResolver.args = { name: 'Color', color: 'chartreuse' };
WithComponentFactoryResolver.decorators = [
moduleMetadata({
declarations: [ComponentFactoryWrapperComponent],
}),
componentWrapperDecorator(ComponentFactoryWrapperComponent, ({ args }) => ({
args,
componentOutlet: WithoutSelectorComponent,
})),
];

View File

@ -1,7 +1,7 @@
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { StoryObj, Meta, moduleMetadata } from '@storybook/angular';
import { WithoutSelectorComponent, WITHOUT_SELECTOR_DATA } from './without-selector.component';
export default {
const meta: Meta<WithoutSelectorComponent> = {
// title: 'Basics / Component / without selector',
component: WithoutSelectorComponent,
decorators: [
@ -11,20 +11,26 @@ export default {
],
} as Meta;
export const SimpleComponent: StoryFn = () => ({});
export default meta;
type Story = StoryObj<WithoutSelectorComponent>;
export const SimpleComponent: Story = {};
// Live changing of args by controls does not work for now. When changing args storybook does not fully
// reload and therefore does not take into account the change of provider.
export const WithInjectionTokenAndArgs: StoryFn = (args) => ({
props: args,
moduleMetadata: {
providers: [
{ provide: WITHOUT_SELECTOR_DATA, useValue: { color: args['color'], name: args['name'] } },
],
export const WithInjectionTokenAndArgs: StoryObj = {
render: (args) => ({
props: args,
moduleMetadata: {
providers: [
{ provide: WITHOUT_SELECTOR_DATA, useValue: { color: args['color'], name: args['name'] } },
],
},
}),
argTypes: {
name: { control: 'text' },
color: { control: 'color' },
},
});
WithInjectionTokenAndArgs.argTypes = {
name: { control: 'text' },
color: { control: 'color' },
args: { name: 'Color', color: 'red' },
};
WithInjectionTokenAndArgs.args = { name: 'Color', color: 'red' };

View File

@ -1,23 +1,25 @@
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { ChipsModule } from './angular-src/chips.module';
import { ChipComponent } from './angular-src/chip.component';
export default {
const meta: Meta<ChipComponent> = {
component: ChipComponent,
decorators: [
moduleMetadata({
imports: [ChipsModule],
}),
],
} as Meta;
export const Chip: StoryFn = (args) => ({
props: args,
});
Chip.args = {
displayText: 'Chip',
};
Chip.argTypes = {
removeClicked: { action: 'Remove icon clicked' },
export default meta;
type Story = StoryObj<ChipComponent>;
export const Chip: Story = {
args: {
displayText: 'Chip',
},
argTypes: {
removeClicked: { action: 'Remove icon clicked' },
},
};

View File

@ -1,9 +1,9 @@
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { ChipsModule } from './angular-src/chips.module';
import { ChipsGroupComponent } from './angular-src/chips-group.component';
import { CHIP_COLOR } from './angular-src/chip-color.token';
export default {
const meta: Meta<ChipsGroupComponent> = {
// title: 'Basics / NgModule / forRoot() pattern',
component: ChipsGroupComponent,
decorators: [
@ -27,24 +27,26 @@ export default {
removeChipClick: { action: 'Remove chip' },
removeAllChipsClick: { action: 'Remove all chips clicked' },
},
} as Meta;
};
const Template = (): StoryFn => (args) => ({
props: args,
});
export default meta;
export const Base = Template();
Base.storyName = 'Chips group';
type Story = StoryObj<ChipsGroupComponent>;
export const WithCustomProvider = Template();
WithCustomProvider.decorators = [
moduleMetadata({
providers: [
{
provide: CHIP_COLOR,
useValue: 'yellow',
},
],
}),
];
WithCustomProvider.storyName = 'Chips group with overridden provider';
export const Base: Story = {
name: 'Chips group',
};
export const WithCustomProvider: Story = {
decorators: [
moduleMetadata({
providers: [
{
provide: CHIP_COLOR,
useValue: 'yellow',
},
],
}),
],
name: 'Chips group with overridden provider',
};

View File

@ -1,8 +1,8 @@
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { StoryFn, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { ChipsModule } from './angular-src/chips.module';
import { ChipsGroupComponent } from './angular-src/chips-group.component';
export default {
const meta: Meta<ChipsGroupComponent> = {
// title: 'Basics / NgModule / Module with multiple component',
component: ChipsGroupComponent,
decorators: [
@ -10,26 +10,27 @@ export default {
imports: [ChipsModule],
}),
],
} as Meta;
export const ChipsGroup: StoryFn = (args) => ({
props: args,
});
ChipsGroup.args = {
chips: [
{
id: 1,
text: 'Chip 1',
},
{
id: 2,
text: 'Chip 2',
},
],
};
ChipsGroup.argTypes = {
removeChipClick: { action: 'Remove chip' },
removeAllChipsClick: { action: 'Remove all chips clicked' },
export default meta;
type Story = StoryObj<ChipsGroupComponent>;
export const ChipsGroup: Story = {
args: {
chips: [
{
id: 1,
text: 'Chip 1',
},
{
id: 2,
text: 'Chip 2',
},
],
},
argTypes: {
removeChipClick: { action: 'Remove chip' },
removeAllChipsClick: { action: 'Remove all chips clicked' },
},
};

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