diff --git a/.circleci/config.yml b/.circleci/config.yml
index b34288f775cb..1cd268cbbe1b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -47,6 +47,19 @@ executors:
environment:
NODE_OPTIONS: --max_old_space_size=6144
resource_class: <>
+ sb_playwright_component_testing:
+ parameters:
+ class:
+ description: The Resource class
+ type: enum
+ enum: ["small", "medium", "medium+", "large", "xlarge"]
+ default: "small"
+ working_directory: /tmp/storybook
+ docker:
+ - image: mcr.microsoft.com/playwright:v1.42.1-jammy
+ environment:
+ NODE_OPTIONS: --max_old_space_size=6144
+ resource_class: <>
orbs:
git-shallow-clone: guitarrapc/git-shallow-clone@2.5.0
@@ -565,7 +578,39 @@ jobs:
STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >>
STORYBOOK_DISABLE_TELEMETRY: true
- report-workflow-on-failure
-
+ test-portable-stories:
+ parameters:
+ directory:
+ type: string
+ executor:
+ name: sb_playwright_component_testing
+ class: medium
+ steps:
+ - git-shallow-clone/checkout_advanced:
+ clone_options: "--depth 1 --verbose"
+ - attach_workspace:
+ at: .
+ - run:
+ name: Install dependencies
+ command: yarn install
+ working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >>
+ - run:
+ name: Run Jest tests
+ command: yarn jest
+ working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >>
+ - run:
+ name: Run Vitest tests
+ command: yarn vitest
+ working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >>
+ - run:
+ name: Run Playwright CT tests
+ command: yarn playwright
+ working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >>
+ - run:
+ name: Run Cypress CT tests
+ command: yarn cypress
+ working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >>
+ - report-workflow-on-failure
workflows:
docs:
when:
@@ -624,6 +669,12 @@ workflows:
parallelism: 5
requires:
- build-sandboxes
+ - test-portable-stories:
+ requires:
+ - build
+ matrix:
+ parameters:
+ directory: ["react", "vue3", "nextjs", "svelte"]
# TODO: reenable once we find out the source of flakyness
# - test-runner-dev:
# requires:
@@ -676,6 +727,12 @@ workflows:
parallelism: 14
requires:
- build-sandboxes
+ - test-portable-stories:
+ requires:
+ - build
+ matrix:
+ parameters:
+ directory: ["react", "vue3", "nextjs", "svelte"]
- bench:
parallelism: 5
requires:
@@ -733,7 +790,12 @@ workflows:
parallelism: 30
requires:
- build-sandboxes
-
+ - test-portable-stories:
+ requires:
+ - build
+ matrix:
+ parameters:
+ directory: ["react", "vue3", "nextjs", "svelte"]
- test-empty-init:
requires:
- build
diff --git a/.github/workflows/canary-release-pr.yml b/.github/workflows/canary-release-pr.yml
index 659765318fe5..557a0331fe2d 100644
--- a/.github/workflows/canary-release-pr.yml
+++ b/.github/workflows/canary-release-pr.yml
@@ -49,18 +49,18 @@ jobs:
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: ${{ steps.info.outputs.isFork == 'true' && steps.info.outputs.repository || null }}
ref: ${{ steps.info.outputs.sha }}
token: ${{ secrets.GH_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Cache dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.yarn/berry/cache
diff --git a/.github/workflows/cron-weekly.yml b/.github/workflows/cron-weekly.yml
index 898d10ace803..07026c97fb8a 100644
--- a/.github/workflows/cron-weekly.yml
+++ b/.github/workflows/cron-weekly.yml
@@ -2,21 +2,21 @@ name: Markdown Links Check
# runs every monday at 9 am
on:
schedule:
- - cron: '0 9 * * 1'
+ - cron: "0 9 * * 1"
jobs:
check-links:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: gaurav-nelson/github-action-markdown-link-check@v1
# checks all markdown files from important folders including all subfolders
with:
# only show errors that occur instead of successful links + errors
- use-quiet-mode: 'yes'
+ use-quiet-mode: "yes"
# output full HTTP info for broken links
- use-verbose-mode: 'yes'
- config-file: '.github/workflows/markdown-link-check-config.json'
+ use-verbose-mode: "yes"
+ config-file: ".github/workflows/markdown-link-check-config.json"
# Notify to Discord channel on failure
- name: Send Discord Notification
if: failure() # Only run this step if previous steps failed
diff --git a/.github/workflows/danger-js.yml b/.github/workflows/danger-js.yml
index eddb5dee1fe7..a9ef5d65affe 100644
--- a/.github/workflows/danger-js.yml
+++ b/.github/workflows/danger-js.yml
@@ -21,10 +21,10 @@ jobs:
name: Danger JS
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
- node-version-file: '.nvmrc'
+ node-version-file: ".nvmrc"
- name: Danger JS
uses: danger/danger-js@11.2.6
env:
diff --git a/.github/workflows/handle-release-branches.yml b/.github/workflows/handle-release-branches.yml
index e1eb20e97adb..84cebf0aee54 100644
--- a/.github/workflows/handle-release-branches.yml
+++ b/.github/workflows/handle-release-branches.yml
@@ -23,7 +23,7 @@ jobs:
if: ${{ needs.branch-checks.outputs.is-latest-branch == 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- run: curl -X POST "https://api.netlify.com/build_hooks/${{ secrets.FRONTPAGE_HOOK }}"
@@ -32,7 +32,7 @@ jobs:
if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' || needs.branch-checks.outputs.is-release-branch == 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: next
path: next
@@ -54,7 +54,7 @@ jobs:
if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -88,7 +88,8 @@ jobs:
request-create-frontpage-branch:
if: ${{ always() }}
- needs: [branch-checks, next-release-branch-check, create-next-release-branch]
+ needs:
+ [branch-checks, next-release-branch-check, create-next-release-branch]
runs-on: ubuntu-latest
steps:
- if: ${{ needs.branch-checks.outputs.is-actionable-branch == 'true' && needs.branch-checks.outputs.is-latest-branch == 'false' && needs.next-release-branch-check.outputs.check == 'false' }}
diff --git a/.github/workflows/prepare-non-patch-release.yml b/.github/workflows/prepare-non-patch-release.yml
index 0b4163251bdd..3cbf8f8b1fc9 100644
--- a/.github/workflows/prepare-non-patch-release.yml
+++ b/.github/workflows/prepare-non-patch-release.yml
@@ -8,9 +8,9 @@ on:
workflow_dispatch:
inputs:
release-type:
- description: 'Which release type to use for bumping the version'
+ description: "Which release type to use for bumping the version"
required: true
- default: 'prerelease'
+ default: "prerelease"
type: choice
options:
- prerelease
@@ -43,7 +43,7 @@ jobs:
working-directory: scripts
steps:
- name: Checkout next
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: next
# this needs to be set to a high enough number that it will contain the last version tag
@@ -52,12 +52,12 @@ jobs:
token: ${{ secrets.GH_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version-file: '.nvmrc'
+ node-version-file: ".nvmrc"
- name: Cache dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.yarn/berry/cache
@@ -123,7 +123,7 @@ jobs:
run: |
yarn release:write-changelog ${{ steps.bump-version.outputs.next-version }} --verbose
- - name: 'Commit changes to branch: version-non-patch-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'
@@ -180,4 +180,4 @@ jobs:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
uses: Ilshidur/action-discord@master
with:
- args: 'The GitHub Action for preparing the release pull request bumping from v${{ steps.bump-version.outputs.current-version }} to v${{ steps.bump-version.outputs.next-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
+ args: "The GitHub Action for preparing the release pull request bumping from v${{ steps.bump-version.outputs.current-version }} to v${{ steps.bump-version.outputs.next-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml
index 91be3acf6f92..f66258c0d836 100644
--- a/.github/workflows/prepare-patch-release.yml
+++ b/.github/workflows/prepare-patch-release.yml
@@ -25,18 +25,18 @@ jobs:
working-directory: scripts
steps:
- name: Checkout main
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.GH_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Cache dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.yarn/berry/cache
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index bd9c892e3310..cc88ce6182fd 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -37,18 +37,18 @@ jobs:
gh run watch ${{ github.run_id }}
- name: Checkout ${{ github.ref_name }}
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 100
token: ${{ secrets.GH_TOKEN }}
- name: Setup Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version-file: '.nvmrc'
+ node-version-file: ".nvmrc"
- name: Cache dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
~/.yarn/berry/cache
@@ -197,4 +197,4 @@ jobs:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_MONITORING_URL }}
uses: Ilshidur/action-discord@master
with:
- args: 'The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
+ args: "The GitHub Action for publishing version ${{ steps.version.outputs.current-version }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 1418c69695f3..29afdd8ce196 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -1,18 +1,18 @@
-name: 'Close stale issues that need reproduction or more info from OP'
+name: "Close stale issues that need reproduction or more info from OP"
on:
schedule:
- - cron: '30 1 * * *'
+ - cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v8
+ - uses: actions/stale@v9
with:
stale-issue-message: "Hi there! Thank you for opening this issue, but it has been marked as `stale` because we need more information to move forward. Could you please provide us with the requested reproduction or additional information that could help us better understand the problem? We'd love to resolve this issue, but we can't do it without your help!"
close-issue-message: "I'm afraid we need to close this issue for now, since we can't take any action without the requested reproduction or additional information. But please don't hesitate to open a new issue if the problem persists – we're always happy to help. Thanks so much for your understanding."
- any-of-labels: 'needs reproduction,needs more info'
- exempt-issue-labels: 'needs triage'
- labels-to-add-when-unstale: 'needs triage'
+ any-of-labels: "needs reproduction,needs more info"
+ exempt-issue-labels: "needs triage"
+ labels-to-add-when-unstale: "needs triage"
days-before-stale: 21
days-before-pr-stale: -1
diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml
index dbb4f498ab6e..103bb8196fa4 100644
--- a/.github/workflows/tests-unit.yml
+++ b/.github/workflows/tests-unit.yml
@@ -16,11 +16,11 @@ jobs:
os: [windows-latest]
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set node version
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version-file: '.nvmrc'
+ node-version-file: ".nvmrc"
- name: install and compile
run: yarn task --task compile --start-from=auto --no-link
- name: test
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a1978abc0d9..9826390c44b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 8.0.1
+
+- Controls: Fix type summary when table.type unset - [#26283](https://github.com/storybookjs/storybook/pull/26283), thanks @shilman!
+- Core: Fix addon bundling script - [#26145](https://github.com/storybookjs/storybook/pull/26145), thanks @ndelangen!
+- Core: Fix fail to load `main.ts` error message - [#26035](https://github.com/storybookjs/storybook/pull/26035), thanks @ndelangen!
+- Maintenance: Fix performance regressions - [#26411](https://github.com/storybookjs/storybook/pull/26411), thanks @kasperpeulen!
+
## 8.0.0
#### Storybook 8.0 is here
diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md
index 3474586b1d3f..21d52c2627ff 100644
--- a/CHANGELOG.prerelease.md
+++ b/CHANGELOG.prerelease.md
@@ -1,3 +1,13 @@
+## 8.1.0-alpha.3
+
+- Addon Docs: Fix [Object object] displayName in some JSX components - [#26566](https://github.com/storybookjs/storybook/pull/26566), thanks @yannbf!
+- CLI: Introduce package manager fallback for initializing Storybook in an empty directory with yarn1 - [#26500](https://github.com/storybookjs/storybook/pull/26500), thanks @valentinpalkovic!
+- CSF: Make sure loaders/decorators can be used as array - [#26514](https://github.com/storybookjs/storybook/pull/26514), thanks @kasperpeulen!
+- Controls: Fix disable condition in ArgControl component - [#26567](https://github.com/storybookjs/storybook/pull/26567), thanks @valentinpalkovic!
+- Portable stories: Introduce experimental Playwright CT API and Support for more renderers - [#26063](https://github.com/storybookjs/storybook/pull/26063), thanks @yannbf!
+- UI: Fix theming of elements inside bars - [#26527](https://github.com/storybookjs/storybook/pull/26527), thanks @valentinpalkovic!
+- UI: Improve empty state of addon panel - [#26481](https://github.com/storybookjs/storybook/pull/26481), thanks @yannbf!
+
## 8.1.0-alpha.2
- CLI: Automigrate improve upgrade storybook related packages - [#26497](https://github.com/storybookjs/storybook/pull/26497), thanks @ndelangen!
diff --git a/MIGRATION.md b/MIGRATION.md
index 561a9a61b899..2d6f1d834ecf 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -510,8 +510,8 @@ For migrating to CSF, see: [`storyStoreV6` and `storiesOf` is deprecated](#story
In Storybook 7, these packages existed for backwards compatibility, but were marked as deprecated:
- `@storybook/addons` - this package has been split into 2 packages: `@storybook/preview-api` and `@storybook/manager-api`, see more here: [New Addons API](#new-addons-api).
-- `@storybook/channel-postmessage` - this package has been merged into `@storybook/channel`.
-- `@storybook/channel-websocket` - this package has been merged into `@storybook/channel`.
+- `@storybook/channel-postmessage` - this package has been merged into `@storybook/channels`.
+- `@storybook/channel-websocket` - this package has been merged into `@storybook/channels`.
- `@storybook/client-api` - this package has been merged into `@storybook/preview-api`.
- `@storybook/core-client` - this package has been merged into `@storybook/preview-api`.
- `@storybook/preview-web` - this package has been merged into `@storybook/preview-api`.
@@ -554,7 +554,7 @@ export default defineConfig({
```ts
import { defineConfig } from "vite";
-import svelte from "@sveltejs/vite-plugin-svelte";
+import { svelte } from "@sveltejs/vite-plugin-svelte";
export default defineConfig({
plugins: [svelte()],
diff --git a/code/.yarn/patches/@testing-library-svelte-npm-4.1.0-34b7037bc0.patch b/code/.yarn/patches/@testing-library-svelte-npm-4.1.0-34b7037bc0.patch
new file mode 100644
index 000000000000..212dfcc7d0ea
--- /dev/null
+++ b/code/.yarn/patches/@testing-library-svelte-npm-4.1.0-34b7037bc0.patch
@@ -0,0 +1,97 @@
+diff --git a/package.json b/package.json
+index 195dac9ee7d42fdb76bb22dc37580fa0bffd4680..980ad42f41a06023f9f7e370fd382c9217c24be5 100644
+--- a/package.json
++++ b/package.json
+@@ -55,7 +55,7 @@
+ "contributors:generate": "all-contributors generate"
+ },
+ "peerDependencies": {
+- "svelte": "^3 || ^4"
++ "svelte": "^3 || ^4 || ^5"
+ },
+ "dependencies": {
+ "@testing-library/dom": "^9.3.1"
+diff --git a/src/pure.js b/src/pure.js
+index 6d4943412448c9f310f007ca7dab9d04cef90d0d..d62f4aebeb1b23ccc3c3d82aadd67075c6507c0e 100644
+--- a/src/pure.js
++++ b/src/pure.js
+@@ -3,7 +3,7 @@ import {
+ getQueriesForElement,
+ prettyDOM
+ } from '@testing-library/dom'
+-import { tick } from 'svelte'
++import { tick, mount, unmount } from 'svelte'
+
+ const containerCache = new Set()
+ const componentCache = new Set()
+@@ -54,40 +54,34 @@ const render = (
+ return { props: options }
+ }
+
+- let component = new ComponentConstructor({
++ let component = mount(ComponentConstructor, {
+ target,
+- ...checkProps(options)
++ ...checkProps(options),
++ ondestroy: () => componentCache.delete(component)
+ })
+
+ containerCache.add({ container, target, component })
+ componentCache.add(component)
+
+- component.$$.on_destroy.push(() => {
+- componentCache.delete(component)
+- })
+-
+ return {
+ container,
+ component,
+ debug: (el = container) => console.log(prettyDOM(el)),
+ rerender: (options) => {
+- if (componentCache.has(component)) component.$destroy()
++ if (componentCache.has(component)) unmount(component)
+
+ // eslint-disable-next-line no-new
+ component = new ComponentConstructor({
+ target,
+- ...checkProps(options)
++ ...checkProps(options),
++ ondestroy: () => componentCache.delete(component)
+ })
+
+ containerCache.add({ container, target, component })
+ componentCache.add(component)
+-
+- component.$$.on_destroy.push(() => {
+- componentCache.delete(component)
+- })
+ },
+ unmount: () => {
+- if (componentCache.has(component)) component.$destroy()
++ if (componentCache.has(component)) unmount(component)
+ },
+ ...getQueriesForElement(container, queries)
+ }
+@@ -96,7 +90,7 @@ const render = (
+ const cleanupAtContainer = (cached) => {
+ const { target, component } = cached
+
+- if (componentCache.has(component)) component.$destroy()
++ if (componentCache.has(component)) unmount(component)
+
+ if (target.parentNode === document.body) {
+ document.body.removeChild(target)
+@@ -109,9 +103,10 @@ const cleanup = () => {
+ Array.from(containerCache.keys()).forEach(cleanupAtContainer)
+ }
+
+-const act = async (fn) => {
+- if (fn) {
+- await fn()
++const act = (fn) => {
++ const value = fn && fn()
++ if (value !== undefined && typeof value.then === 'function') {
++ return value.then(() => tick())
+ }
+ return tick()
+ }
diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json
index 2dc509c9a535..e1148efa3066 100644
--- a/code/addons/a11y/package.json
+++ b/code/addons/a11y/package.json
@@ -32,8 +32,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/a11y/src/components/Report/index.tsx b/code/addons/a11y/src/components/Report/index.tsx
index d231cc4cf0e2..83bcb1705d32 100644
--- a/code/addons/a11y/src/components/Report/index.tsx
+++ b/code/addons/a11y/src/components/Report/index.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react';
import React, { Fragment } from 'react';
-import { Placeholder } from '@storybook/components';
+import { EmptyTabContent } from '@storybook/components';
import type { Result } from 'axe-core';
import { Item } from './Item';
@@ -18,7 +18,7 @@ export const Report: FC = ({ items, empty, type }) => (
{items && items.length ? (
items.map((item) => )
) : (
- {empty}
+
)}
);
diff --git a/code/addons/actions/package.json b/code/addons/actions/package.json
index 3daab0e6523b..2e3772547dfd 100644
--- a/code/addons/actions/package.json
+++ b/code/addons/actions/package.json
@@ -33,8 +33,12 @@
"require": "./dist/decorator.js",
"import": "./dist/decorator.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register.js": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json
index e1628ec59e9c..ab4c5ad3051c 100644
--- a/code/addons/backgrounds/package.json
+++ b/code/addons/backgrounds/package.json
@@ -32,8 +32,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json
index bd5e7c1d3042..f2122af1de0d 100644
--- a/code/addons/essentials/package.json
+++ b/code/addons/essentials/package.json
@@ -28,22 +28,50 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
- "./actions/preview": "./dist/actions/preview.js",
+ "./actions/preview": {
+ "types": "./dist/actions/preview.d.ts",
+ "import": "./dist/actions/preview.mjs",
+ "require": "./dist/actions/preview.js"
+ },
"./actions/manager": "./dist/actions/manager.js",
- "./backgrounds/preview": "./dist/backgrounds/preview.js",
+ "./backgrounds/preview": {
+ "types": "./dist/backgrounds/preview.d.ts",
+ "import": "./dist/backgrounds/preview.mjs",
+ "require": "./dist/backgrounds/preview.js"
+ },
"./backgrounds/manager": "./dist/backgrounds/manager.js",
"./controls/manager": "./dist/controls/manager.js",
- "./docs/preview": "./dist/docs/preview.js",
+ "./docs/preview": {
+ "types": "./dist/docs/preview.d.ts",
+ "import": "./dist/docs/preview.mjs",
+ "require": "./dist/docs/preview.js"
+ },
"./docs/preset": "./dist/docs/preset.js",
"./docs/mdx-react-shim": "./dist/docs/mdx-react-shim.js",
- "./highlight/preview": "./dist/highlight/preview.js",
- "./measure/preview": "./dist/measure/preview.js",
+ "./highlight/preview": {
+ "types": "./dist/highlight/preview.d.ts",
+ "import": "./dist/highlight/preview.mjs",
+ "require": "./dist/highlight/preview.js"
+ },
+ "./measure/preview": {
+ "types": "./dist/measure/preview.d.ts",
+ "import": "./dist/measure/preview.mjs",
+ "require": "./dist/measure/preview.js"
+ },
"./measure/manager": "./dist/measure/manager.js",
- "./outline/preview": "./dist/outline/preview.js",
+ "./outline/preview": {
+ "types": "./dist/outline/preview.d.ts",
+ "import": "./dist/outline/preview.mjs",
+ "require": "./dist/outline/preview.js"
+ },
"./outline/manager": "./dist/outline/manager.js",
"./toolbars/manager": "./dist/toolbars/manager.js",
"./viewport/manager": "./dist/viewport/manager.js",
- "./viewport/preview": "./dist/viewport/preview.js",
+ "./viewport/preview": {
+ "types": "./dist/viewport/preview.d.ts",
+ "import": "./dist/viewport/preview.mjs",
+ "require": "./dist/viewport/preview.js"
+ },
"./package.json": "./package.json"
},
"main": "dist/index.js",
diff --git a/code/addons/essentials/src/measure/preview.ts b/code/addons/essentials/src/measure/preview.ts
index 647ef4345a6d..c34063ac4ca4 100644
--- a/code/addons/essentials/src/measure/preview.ts
+++ b/code/addons/essentials/src/measure/preview.ts
@@ -1,2 +1 @@
-// @ts-expect-error (no types needed for this)
export * from '@storybook/addon-measure/preview';
diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json
index 4a5d8848e411..6e0383f755a3 100644
--- a/code/addons/highlight/package.json
+++ b/code/addons/highlight/package.json
@@ -30,7 +30,11 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
- "./preview": "./dist/preview.js",
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./package.json": "./package.json"
},
"main": "dist/index.js",
diff --git a/code/addons/highlight/src/preview.ts b/code/addons/highlight/src/preview.ts
index 1948f7b39d97..794417ef0d9d 100644
--- a/code/addons/highlight/src/preview.ts
+++ b/code/addons/highlight/src/preview.ts
@@ -8,18 +8,12 @@ const { document } = global;
type OutlineStyle = 'dotted' | 'dashed' | 'solid' | 'double';
-export const highlightStyle = (color = '#FF4785', style: OutlineStyle = 'dashed') => `
+const highlightStyle = (color = '#FF4785', style: OutlineStyle = 'dashed') => `
outline: 2px ${style} ${color};
outline-offset: 2px;
box-shadow: 0 0 0 6px rgba(255,255,255,0.6);
`;
-export const highlightObject = (color: string) => ({
- outline: `2px dashed ${color}`,
- outlineOffset: 2,
- boxShadow: '0 0 0 6px rgba(255,255,255,0.6)',
-});
-
interface HighlightInfo {
/** html selector of the element */
elements: string[];
diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json
index f39a3065e103..9b6c22d96f90 100644
--- a/code/addons/interactions/package.json
+++ b/code/addons/interactions/package.json
@@ -28,8 +28,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./preset": "./dist/preset.js",
"./register.js": "./dist/manager.js",
"./package.json": "./package.json"
diff --git a/code/addons/interactions/src/components/EmptyState.tsx b/code/addons/interactions/src/components/EmptyState.tsx
index d4fa62c144a4..0cb5ecba69e2 100644
--- a/code/addons/interactions/src/components/EmptyState.tsx
+++ b/code/addons/interactions/src/components/EmptyState.tsx
@@ -1,43 +1,11 @@
import React, { useEffect, useState } from 'react';
-import { Link } from '@storybook/components';
+import { Link, EmptyTabContent } from '@storybook/components';
import { DocumentIcon, VideoIcon } from '@storybook/icons';
-import { Consumer, useStorybookApi } from '@storybook/manager-api';
+import { useStorybookApi } from '@storybook/manager-api';
import { styled } from '@storybook/theming';
import { DOCUMENTATION_LINK, TUTORIAL_VIDEO_LINK } from '../constants';
-const Wrapper = styled.div(({ theme }) => ({
- height: '100%',
- display: 'flex',
- padding: 0,
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'column',
- gap: 15,
- background: theme.background.content,
-}));
-
-const Content = styled.div({
- display: 'flex',
- flexDirection: 'column',
- gap: 4,
- maxWidth: 415,
-});
-
-const Title = styled.div(({ theme }) => ({
- fontWeight: theme.typography.weight.bold,
- fontSize: theme.typography.size.s2 - 1,
- textAlign: 'center',
- color: theme.textColor,
-}));
-
-const Description = styled.div(({ theme }) => ({
- fontWeight: theme.typography.weight.regular,
- fontSize: theme.typography.size.s2 - 1,
- textAlign: 'center',
- color: theme.textMutedColor,
-}));
-
const Links = styled.div(({ theme }) => ({
display: 'flex',
fontSize: theme.typography.size.s2 - 1,
@@ -73,27 +41,25 @@ export const Empty = () => {
if (isLoading) return null;
return (
-
-
- Interaction testing
-
+
Interaction tests allow you to verify the functional aspects of UIs. Write a play function
for your story and you'll see it run here.
-
-
-
-
- Watch 8m video
-
-
-
- {({ state }) => (
-
- Read docs
-
- )}
-
-
-
+ >
+ }
+ footer={
+
+
+ Watch 8m video
+
+
+
+ Read docs
+
+
+ }
+ />
);
};
diff --git a/code/addons/links/package.json b/code/addons/links/package.json
index 4effdb357a7c..d5269011dc6b 100644
--- a/code/addons/links/package.json
+++ b/code/addons/links/package.json
@@ -33,8 +33,12 @@
"require": "./dist/react/index.js",
"import": "./dist/react/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json
index 71616e30727c..2e5c905ba64b 100644
--- a/code/addons/measure/package.json
+++ b/code/addons/measure/package.json
@@ -31,8 +31,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json
index 96f83f0b4b15..07fb23746bce 100644
--- a/code/addons/outline/package.json
+++ b/code/addons/outline/package.json
@@ -34,8 +34,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./register": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json
index 2724c1b9450b..a55582e17c84 100644
--- a/code/addons/themes/package.json
+++ b/code/addons/themes/package.json
@@ -33,8 +33,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
- "./preview": "./dist/preview.js",
"./package.json": "./package.json",
"./postinstall": "./postinstall.js"
},
diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json
index eecdb09fb296..45bf6471994f 100644
--- a/code/addons/viewport/package.json
+++ b/code/addons/viewport/package.json
@@ -29,7 +29,11 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
- "./preview": "./dist/preview.js",
+ "./preview": {
+ "types": "./dist/preview.d.ts",
+ "require": "./dist/preview.js",
+ "import": "./dist/preview.mjs"
+ },
"./manager": "./dist/manager.js",
"./package.json": "./package.json"
},
diff --git a/code/e2e-tests/tags.spec.ts b/code/e2e-tests/tags.spec.ts
index 37fb76fb814c..8302a9a6a338 100644
--- a/code/e2e-tests/tags.spec.ts
+++ b/code/e2e-tests/tags.spec.ts
@@ -9,7 +9,9 @@ test.describe('tags', () => {
await new SbPage(page).waitUntilLoaded();
});
- test('should correctly filter dev-only, docs-only, test-only stories', async ({ page }) => {
+ test('@flaky: should correctly filter dev-only, docs-only, test-only stories', async ({
+ page,
+ }) => {
const sbPage = new SbPage(page);
await sbPage.navigateToStory('lib/preview-api/tags', 'docs');
diff --git a/code/frameworks/nextjs/src/index.ts b/code/frameworks/nextjs/src/index.ts
index fcb073fefcd6..a904f93ec89d 100644
--- a/code/frameworks/nextjs/src/index.ts
+++ b/code/frameworks/nextjs/src/index.ts
@@ -1 +1,2 @@
export * from './types';
+export * from './portable-stories';
diff --git a/code/frameworks/nextjs/src/portable-stories.ts b/code/frameworks/nextjs/src/portable-stories.ts
new file mode 100644
index 000000000000..01948d524c2c
--- /dev/null
+++ b/code/frameworks/nextjs/src/portable-stories.ts
@@ -0,0 +1,126 @@
+import {
+ composeStory as originalComposeStory,
+ composeStories as originalComposeStories,
+ setProjectAnnotations as originalSetProjectAnnotations,
+ composeConfigs,
+} from '@storybook/preview-api';
+import type {
+ Args,
+ ProjectAnnotations,
+ StoryAnnotationsOrFn,
+ Store_CSFExports,
+ StoriesWithPartialProps,
+} from '@storybook/types';
+
+// ! ATTENTION: This needs to be a relative import so it gets prebundled. This is to avoid ESM issues in Nextjs + Jest setups
+import { INTERNAL_DEFAULT_PROJECT_ANNOTATIONS as reactAnnotations } from '../../../renderers/react/src/portable-stories';
+import * as nextJsAnnotations from './preview';
+
+import type { ReactRenderer, Meta } from '@storybook/react';
+
+/** Function that sets the globalConfig of your storybook. The global config is the preview module of your .storybook folder.
+ *
+ * It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`.
+ *
+ * Example:
+ *```jsx
+ * // setup.js (for jest)
+ * import { setProjectAnnotations } from '@storybook/nextjs';
+ * import projectAnnotations from './.storybook/preview';
+ *
+ * setProjectAnnotations(projectAnnotations);
+ *```
+ *
+ * @param projectAnnotations - e.g. (import projectAnnotations from '../.storybook/preview')
+ */
+export function setProjectAnnotations(
+ projectAnnotations: ProjectAnnotations | ProjectAnnotations[]
+) {
+ originalSetProjectAnnotations(projectAnnotations);
+}
+
+// This will not be necessary once we have auto preset loading
+const defaultProjectAnnotations: ProjectAnnotations = composeConfigs([
+ reactAnnotations,
+ nextJsAnnotations,
+]);
+
+/**
+ * Function that will receive a story along with meta (e.g. a default export from a .stories file)
+ * and optionally projectAnnotations e.g. (import * from '../.storybook/preview)
+ * and will return a composed component that has all args/parameters/decorators/etc combined and applied to it.
+ *
+ *
+ * It's very useful for reusing a story in scenarios outside of Storybook like unit testing.
+ *
+ * Example:
+ *```jsx
+ * import { render } from '@testing-library/react';
+ * import { composeStory } from '@storybook/nextjs';
+ * import Meta, { Primary as PrimaryStory } from './Button.stories';
+ *
+ * const Primary = composeStory(PrimaryStory, Meta);
+ *
+ * test('renders primary button with Hello World', () => {
+ * const { getByText } = render(Hello world);
+ * expect(getByText(/Hello world/i)).not.toBeNull();
+ * });
+ *```
+ *
+ * @param story
+ * @param componentAnnotations - e.g. (import Meta from './Button.stories')
+ * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files.
+ * @param [exportsName] - in case your story does not contain a name and you want it to have a name.
+ */
+export function composeStory(
+ story: StoryAnnotationsOrFn,
+ componentAnnotations: Meta,
+ projectAnnotations?: ProjectAnnotations,
+ exportsName?: string
+) {
+ return originalComposeStory(
+ story as StoryAnnotationsOrFn,
+ componentAnnotations,
+ projectAnnotations,
+ defaultProjectAnnotations,
+ exportsName
+ );
+}
+
+/**
+ * Function that will receive a stories import (e.g. `import * as stories from './Button.stories'`)
+ * and optionally projectAnnotations (e.g. `import * from '../.storybook/preview`)
+ * and will return an object containing all the stories passed, but now as a composed component that has all args/parameters/decorators/etc combined and applied to it.
+ *
+ *
+ * It's very useful for reusing stories in scenarios outside of Storybook like unit testing.
+ *
+ * Example:
+ *```jsx
+ * import { render } from '@testing-library/react';
+ * import { composeStories } from '@storybook/nextjs';
+ * import * as stories from './Button.stories';
+ *
+ * const { Primary, Secondary } = composeStories(stories);
+ *
+ * test('renders primary button with Hello World', () => {
+ * const { getByText } = render(Hello world);
+ * expect(getByText(/Hello world/i)).not.toBeNull();
+ * });
+ *```
+ *
+ * @param csfExports - e.g. (import * as stories from './Button.stories')
+ * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files.
+ */
+export function composeStories>(
+ csfExports: TModule,
+ projectAnnotations?: ProjectAnnotations
+) {
+ // @ts-expect-error (Converted from ts-ignore)
+ const composedStories = originalComposeStories(csfExports, projectAnnotations, composeStory);
+
+ return composedStories as unknown as Omit<
+ StoriesWithPartialProps,
+ keyof Store_CSFExports
+ >;
+}
diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts
index 730e4ce8dead..4d83666780b2 100644
--- a/code/lib/cli/src/initiate.ts
+++ b/code/lib/cli/src/initiate.ts
@@ -238,7 +238,7 @@ export async function doInitiate(options: CommandOptions): Promise<
> {
const { packageManager: pkgMgr } = options;
- const packageManager = JsPackageManagerFactory.getPackageManager({
+ let packageManager = JsPackageManagerFactory.getPackageManager({
force: pkgMgr,
});
@@ -272,6 +272,13 @@ export async function doInitiate(options: CommandOptions): Promise<
// Check if the current directory is empty.
if (options.force !== true && currentDirectoryIsEmpty(packageManager.type)) {
+ // Initializing Storybook in an empty directory with yarn1
+ // will very likely fail due to different kind of hoisting issues
+ // which doesn't get fixed anymore in yarn1.
+ // We will fallback to npm in this case.
+ if (packageManager.type === 'yarn1') {
+ packageManager = JsPackageManagerFactory.getPackageManager({ force: 'npm' });
+ }
// Prompt the user to create a new project from our list.
await scaffoldNewProject(packageManager.type, options);
diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts
index 3eeb6ea86ed8..57c812109e89 100644
--- a/code/lib/instrumenter/src/instrumenter.ts
+++ b/code/lib/instrumenter/src/instrumenter.ts
@@ -102,7 +102,7 @@ export class Instrumenter {
// Restore state from the parent window in case the iframe was reloaded.
// @ts-expect-error (TS doesn't know about this global variable)
- this.state = global.window.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ || {};
+ this.state = global.window?.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ || {};
// When called from `start`, isDebugging will be true.
const resetState = ({
@@ -242,8 +242,10 @@ export class Instrumenter {
const patch = typeof update === 'function' ? update(state) : update;
this.state = { ...this.state, [storyId]: { ...state, ...patch } };
// Track state on the parent window so we can reload the iframe without losing state.
- // @ts-expect-error (TS doesn't know about this global variable)
- global.window.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ = this.state;
+ if (global.window?.parent) {
+ // @ts-expect-error fix this later in d.ts file
+ global.window.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ = this.state;
+ }
}
cleanup() {
@@ -259,8 +261,10 @@ export class Instrumenter {
);
const payload: SyncPayload = { controlStates: controlsDisabled, logItems: [] };
this.channel.emit(EVENTS.SYNC, payload);
- // @ts-expect-error (TS doesn't know about this global variable)
- global.window.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ = this.state;
+ if (global.window?.parent) {
+ // @ts-expect-error fix this later in d.ts file
+ global.window.parent.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__ = this.state;
+ }
}
getLog(storyId: string): LogItem[] {
@@ -426,7 +430,7 @@ export class Instrumenter {
const { flags, source } = value;
return { __regexp__: { flags, source } };
}
- if (value instanceof global.window.HTMLElement) {
+ if (value instanceof global.window?.HTMLElement) {
const { prefix, localName, id, classList, innerText } = value;
const classNames = Array.from(classList);
return { __element__: { prefix, localName, id, classNames, innerText } };
@@ -640,23 +644,23 @@ export function instrument>(
let forceInstrument = false;
let skipInstrument = false;
- if (global.window.location?.search?.includes('instrument=true')) {
+ if (global.window?.location?.search?.includes('instrument=true')) {
forceInstrument = true;
- } else if (global.window.location?.search?.includes('instrument=false')) {
+ } else if (global.window?.location?.search?.includes('instrument=false')) {
skipInstrument = true;
}
// Don't do any instrumentation if not loaded in an iframe unless it's forced - instrumentation can also be skipped.
- if ((global.window.parent === global.window && !forceInstrument) || skipInstrument) {
+ if ((global.window?.parent === global.window && !forceInstrument) || skipInstrument) {
return obj;
}
// Only create an instance if we don't have one (singleton) yet.
- if (!global.window.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__) {
+ if (global.window && !global.window.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__) {
global.window.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__ = new Instrumenter();
}
- const instrumenter: Instrumenter = global.window.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__;
+ const instrumenter: Instrumenter = global.window?.__STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__;
return instrumenter.instrument(obj, options);
} catch (e) {
// Access to the parent window might fail due to CORS restrictions.
diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts
index e47cdaa0a0dd..779cfeba1557 100644
--- a/code/lib/preview-api/src/index.ts
+++ b/code/lib/preview-api/src/index.ts
@@ -62,6 +62,8 @@ export {
sortStoriesV7,
} from './store';
+export { createPlaywrightTest } from './modules/store/csf/portable-stories';
+
export type { PropDescriptor } from './store';
/**
diff --git a/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts b/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts
index 238885f44ba3..147038a5a8d2 100644
--- a/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts
+++ b/code/lib/preview-api/src/modules/store/csf/composeConfigs.test.ts
@@ -176,6 +176,34 @@ describe('composeConfigs', () => {
});
});
+ it('allows single array to be written without array', () => {
+ expect(
+ composeConfigs([
+ {
+ argsEnhancers: ['1', '2'],
+ argTypesEnhancers: ['1', '2'],
+ loaders: '1',
+ },
+ {
+ argsEnhancers: '3',
+ argTypesEnhancers: '3',
+ loaders: ['2', '3'],
+ },
+ ])
+ ).toEqual({
+ parameters: {},
+ decorators: [],
+ args: {},
+ argsEnhancers: ['1', '2', '3'],
+ argTypes: {},
+ argTypesEnhancers: ['1', '2', '3'],
+ globals: {},
+ globalTypes: {},
+ loaders: ['1', '2', '3'],
+ runStep: expect.any(Function),
+ });
+ });
+
it('combines decorators in reverse file order', () => {
expect(
composeConfigs([
diff --git a/code/lib/preview-api/src/modules/store/csf/composeConfigs.ts b/code/lib/preview-api/src/modules/store/csf/composeConfigs.ts
index 862f8cbcd501..e5785a6a3f01 100644
--- a/code/lib/preview-api/src/modules/store/csf/composeConfigs.ts
+++ b/code/lib/preview-api/src/modules/store/csf/composeConfigs.ts
@@ -3,6 +3,7 @@ import { global } from '@storybook/global';
import { combineParameters } from '../parameters';
import { composeStepRunners } from './stepRunners';
+import { normalizeArrays } from './normalizeArrays';
export function getField(
moduleExportList: ModuleExports[],
@@ -16,10 +17,10 @@ export function getArrayField(
field: string,
options: { reverseFileOrder?: boolean } = {}
): TFieldType[] {
- return getField(moduleExportList, field).reduce(
- (a: any, b: any) => (options.reverseFileOrder ? [...b, ...a] : [...a, ...b]),
- []
- );
+ return getField(moduleExportList, field).reduce((prev: any, cur: any) => {
+ const normalized = normalizeArrays(cur);
+ return options.reverseFileOrder ? [...normalized, ...prev] : [...prev, ...normalized];
+ }, []);
}
export function getObjectField>(
diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts
index 47465eacc8e5..57e8fcda9a2b 100644
--- a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts
+++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts
@@ -1,5 +1,7 @@
+/* eslint-disable no-underscore-dangle */
/* eslint-disable @typescript-eslint/naming-convention */
import { isExportStory } from '@storybook/csf';
+import dedent from 'ts-dedent';
import type {
Renderer,
Args,
@@ -161,3 +163,68 @@ export function composeStories(
return composedStories;
}
+
+type WrappedStoryRef = { __pw_type: 'jsx' | 'importRef' };
+type UnwrappedJSXStoryRef = {
+ __pw_type: 'jsx';
+ type: ComposedStoryFn;
+};
+type UnwrappedImportStoryRef = ComposedStoryFn;
+
+declare global {
+ function __pwUnwrapObject(
+ storyRef: WrappedStoryRef
+ ): Promise;
+}
+
+export function createPlaywrightTest(
+ baseTest: TFixture
+): TFixture {
+ return baseTest.extend({
+ mount: async ({ mount, page }: any, use: any) => {
+ await use(async (storyRef: WrappedStoryRef, ...restArgs: any) => {
+ // Playwright CT deals with JSX import references differently than normal imports
+ // and we can currently only handle JSX import references
+ if (
+ !('__pw_type' in storyRef) ||
+ ('__pw_type' in storyRef && storyRef.__pw_type !== 'jsx')
+ ) {
+ // eslint-disable-next-line local-rules/no-uncategorized-errors
+ throw new Error(dedent`
+ Portable stories in Playwright CT only work when referencing JSX elements.
+ Please use JSX format for your components such as:
+
+ instead of:
+ await mount(MyComponent, { props: { foo: 'bar' } })
+
+ do:
+ await mount()
+
+ More info: https://storybook.js.org/docs/api/portable-stories-playwright
+ `);
+ }
+
+ await page.evaluate(async (wrappedStoryRef: WrappedStoryRef) => {
+ const unwrappedStoryRef = await globalThis.__pwUnwrapObject?.(wrappedStoryRef);
+ const story =
+ '__pw_type' in unwrappedStoryRef ? unwrappedStoryRef.type : unwrappedStoryRef;
+ return story?.load?.();
+ }, storyRef);
+
+ // mount the story
+ const mountResult = await mount(storyRef, ...restArgs);
+
+ // play the story in the browser
+ await page.evaluate(async (wrappedStoryRef: WrappedStoryRef) => {
+ const unwrappedStoryRef = await globalThis.__pwUnwrapObject?.(wrappedStoryRef);
+ const story =
+ '__pw_type' in unwrappedStoryRef ? unwrappedStoryRef.type : unwrappedStoryRef;
+ const canvasElement = document.querySelector('#root');
+ return story?.play?.({ canvasElement });
+ }, storyRef);
+
+ return mountResult;
+ });
+ },
+ });
+}
diff --git a/code/lib/theming/src/themes/dark.ts b/code/lib/theming/src/themes/dark.ts
index 4cb8f19f29bd..173e735ef366 100644
--- a/code/lib/theming/src/themes/dark.ts
+++ b/code/lib/theming/src/themes/dark.ts
@@ -25,7 +25,7 @@ const theme: ThemeVars = {
textMutedColor: '#798186',
// Toolbar default and active colors
- barTextColor: '#798186',
+ barTextColor: color.mediumdark,
barHoverColor: color.secondary,
barSelectedColor: color.secondary,
barBg: '#292C2E',
diff --git a/code/package.json b/code/package.json
index b7f7f7d2bf3d..b4361f2f68cc 100644
--- a/code/package.json
+++ b/code/package.json
@@ -291,5 +291,6 @@
"Dependency Upgrades"
]
]
- }
+ },
+ "deferredNextVersion": "8.1.0-alpha.3"
}
diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json
index a73eb90118ce..9a46313c53ff 100644
--- a/code/renderers/react/package.json
+++ b/code/renderers/react/package.json
@@ -26,6 +26,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
+ "./experimental-playwright": {
+ "types": "./dist/playwright.d.ts",
+ "node": "./dist/playwright.js",
+ "require": "./dist/playwright.js",
+ "import": "./dist/playwright.mjs"
+ },
"./preset": "./preset.js",
"./dist/entry-preview.mjs": "./dist/entry-preview.mjs",
"./dist/entry-preview-docs.mjs": "./dist/entry-preview-docs.mjs",
@@ -101,7 +107,8 @@
"./src/preset.ts",
"./src/entry-preview.ts",
"./src/entry-preview-docs.ts",
- "./src/entry-preview-rsc.tsx"
+ "./src/entry-preview-rsc.tsx",
+ "./src/playwright.ts"
],
"platform": "browser"
},
diff --git a/code/renderers/react/src/__test__/Button.stories.tsx b/code/renderers/react/src/__test__/Button.stories.tsx
index 277f92ddde1f..fc78c1f27d63 100644
--- a/code/renderers/react/src/__test__/Button.stories.tsx
+++ b/code/renderers/react/src/__test__/Button.stories.tsx
@@ -4,6 +4,8 @@ import type { StoryFn as CSF2Story, StoryObj as CSF3Story, Meta } from '..';
import type { ButtonProps } from './Button';
import { Button } from './Button';
+import type { HandlerFunction } from '@storybook/addon-actions';
+import { action } from '@storybook/addon-actions';
const meta = {
title: 'Example/Button',
@@ -124,3 +126,35 @@ export const LoaderStory: CSF3Story<{ mockFn: (val: string) => string }> = {
expect(mockFn).toHaveBeenCalledWith('render');
},
};
+
+export const WithActionArg: CSF3Story<{ someActionArg: HandlerFunction }> = {
+ args: {
+ someActionArg: action('some-action-arg'),
+ },
+ render: (args) => {
+ args.someActionArg('in render');
+ return (
+
+
+
+
+
+
+