diff --git a/.circleci/config.yml b/.circleci/config.yml index b34288f775c..1cd268cbbe1 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/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 871971bac23..90f09cc9f0d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -19,18 +19,19 @@ body: attributes: label: To Reproduce description: >- - We prioritize bug reports that have a reproduction. You can create a reproduction using [storybook.new](https://storybook.new), or by running `npx sb@next sandbox` and - following the instructions. Read our - [documentation](https://storybook.js.org/docs/react/contribute/how-to-reproduce) - to learn more about creating reproductions. + Due to the high volume of reports we receive, we can only prioritize bug reports that include a clear reproduction of the problem. Please use [storybook.new](https://storybook.new) to create one, and consult our [documentation](https://storybook.js.org/docs/react/contribute/how-to-reproduce) for guidance. Thank you for your understanding! placeholder: >- - Paste a link to your reproduction here. We prioritize issues with reproductions over those without. + Please provide a link to your reproduction here. If creating a reproduction really isn't feasible, let us know and be sure to include as much detail as you can to help us understand the issue. + validations: + required: true - type: textarea id: system attributes: label: System description: Please paste the results of `npx storybook@latest info` here. render: bash + validations: + required: true - type: textarea id: context attributes: diff --git a/.github/workflows/canary-release-pr.yml b/.github/workflows/canary-release-pr.yml index 659765318fe..557a0331fe2 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 898d10ace80..07026c97fb8 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 eddb5dee1fe..a9ef5d65aff 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 e1eb20e97ad..84cebf0aee5 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 0b4163251bd..3cbf8f8b1fc 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 91be3acf6f9..f66258c0d83 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 bd9c892e331..cc88ce6182f 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 1418c69695f..29afdd8ce19 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 dbb4f498ab6..103bb8196fa 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 6a1978abc0d..e731ddb2702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.0.2 + +- Addon Docs: Fix [Object object] displayName in some JSX components - [#26566](https://github.com/storybookjs/storybook/pull/26566), thanks @yannbf! +- CLI: Add yarn1 package manager fallback for init in empty directory - [#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! +- UI: Add key property to list children in Highlight component - [#26471](https://github.com/storybookjs/storybook/pull/26471), thanks @valentinpalkovic! +- 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.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 cae0fb53312..21d52c2627f 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,23 @@ +## 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! +- CLI: Improve `vite-config-file.ts` - [#26375](https://github.com/storybookjs/storybook/pull/26375), thanks @joevaugh4n! +- Controls: Fix number controls do not reset - [#26372](https://github.com/storybookjs/storybook/pull/26372), thanks @jiyiru! +- Core: Optimize clearNotification - [#26415](https://github.com/storybookjs/storybook/pull/26415), thanks @ndelangen! +- Portable stories: Make setProjectAnnotations accept multiple types of imports - [#26316](https://github.com/storybookjs/storybook/pull/26316), thanks @yannbf! +- UI: Add key property to list children in Highlight component - [#26471](https://github.com/storybookjs/storybook/pull/26471), thanks @valentinpalkovic! +- UI: Fix search result color contrast - [#26287](https://github.com/storybookjs/storybook/pull/26287), thanks @winchesHe! + ## 8.1.0-alpha.1 - Maintenance: Fix performance regressions - [#26411](https://github.com/storybookjs/storybook/pull/26411), thanks @kasperpeulen! diff --git a/MIGRATION.md b/MIGRATION.md index 561a9a61b89..2d6f1d834ec 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 00000000000..212dfcc7d0e --- /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 68c3ea37f99..898da685d06 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Test component compliance with web accessibility standards", "keywords": [ "a11y", @@ -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 d231cc4cf0e..83bcb1705d3 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 485f6d1b8d1..b950ef7541f 100644 --- a/code/addons/actions/package.json +++ b/code/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Get UI feedback when an action is performed on an interactive element", "keywords": [ "storybook", @@ -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/actions/src/runtime/action.ts b/code/addons/actions/src/runtime/action.ts index a647a8eb0d1..fab9e8aae1d 100644 --- a/code/addons/actions/src/runtime/action.ts +++ b/code/addons/actions/src/runtime/action.ts @@ -21,10 +21,9 @@ const isReactSyntheticEvent = (e: unknown): e is SyntheticEvent => findProto(e, (proto) => /^Synthetic(?:Base)?Event$/.test(proto.constructor.name)) && typeof (e as SyntheticEvent).persist === 'function' ); -const serializeArg = (a: T) => { +const serializeArg = (a: T) => { if (isReactSyntheticEvent(a)) { const e: SyntheticEvent = Object.create( - // @ts-expect-error (Converted from ts-ignore) a.constructor.prototype, Object.getOwnPropertyDescriptors(a) ); diff --git a/code/addons/backgrounds/package.json b/code/addons/backgrounds/package.json index 2caf3521463..360313cda8d 100644 --- a/code/addons/backgrounds/package.json +++ b/code/addons/backgrounds/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-backgrounds", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Switch backgrounds to view components in different settings", "keywords": [ "addon", @@ -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/controls/package.json b/code/addons/controls/package.json index 29d450db628..c2c1fb7bb5c 100644 --- a/code/addons/controls/package.json +++ b/code/addons/controls/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-controls", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Interact with component inputs dynamically in the Storybook UI", "keywords": [ "addon", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index 394e76fd8c1..f533ea16b4e 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Document component usage and properties in Markdown", "keywords": [ "addon", diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index afb37f3424a..6f4e8413437 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", @@ -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 647ef4345a6..c34063ac4ca 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/gfm/package.json b/code/addons/gfm/package.json index 62d9fc14d34..64e7e88642b 100644 --- a/code/addons/gfm/package.json +++ b/code/addons/gfm/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-mdx-gfm", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "GitHub Flavored Markdown in Storybook", "keywords": [ "addon", diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json index 3e86ac6520a..88fe0608483 100644 --- a/code/addons/highlight/package.json +++ b/code/addons/highlight/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-highlight", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Highlight DOM nodes within your stories", "keywords": [ "storybook-addons", @@ -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 1948f7b39d9..794417ef0d9 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 c8baa616626..97ee6a62d54 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-interactions", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Automate, test and debug user interactions", "keywords": [ "storybook-addons", @@ -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 d4fa62c144a..0cb5ecba69e 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/jest/package.json b/code/addons/jest/package.json index c878c2004dc..2556af6a499 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "React storybook addon that show component jest report", "keywords": [ "addon", diff --git a/code/addons/links/package.json b/code/addons/links/package.json index e23c945975d..781ed6d1af4 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Link stories together to build demos and prototypes with your UI components", "keywords": [ "addon", @@ -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/links/src/utils.ts b/code/addons/links/src/utils.ts index 51d8c09c5fc..651183a9df6 100644 --- a/code/addons/links/src/utils.ts +++ b/code/addons/links/src/utils.ts @@ -37,9 +37,7 @@ export const hrefTo = (title: ComponentTitle, name: StoryName): Promise return new Promise((resolve) => { const { location } = document; const query = parseQuery(location.search); - // @ts-expect-error (Converted from ts-ignore) - const existingId = [].concat(query.id)[0]; - // @ts-expect-error (Converted from ts-ignore) + const existingId = query.id; const titleToLink = title || existingId.split('--', 2)[0]; const id = toId(titleToLink, name); const path = `/story/${id}`; diff --git a/code/addons/measure/package.json b/code/addons/measure/package.json index 53824330a8d..ede9123dc5e 100644 --- a/code/addons/measure/package.json +++ b/code/addons/measure/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-measure", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Inspect layouts by visualizing the box model", "keywords": [ "storybook-addons", @@ -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/onboarding/package.json b/code/addons/onboarding/package.json index 540337fa698..622e609d8c0 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-onboarding", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook Addon Onboarding - Introduces a new onboarding experience", "keywords": [ "storybook-addons", diff --git a/code/addons/outline/package.json b/code/addons/outline/package.json index 346b5808b5d..ae51d10b615 100644 --- a/code/addons/outline/package.json +++ b/code/addons/outline/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-outline", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Outline all elements with CSS to help with layout placement and alignment", "keywords": [ "storybook-addons", @@ -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/storysource/package.json b/code/addons/storysource/package.json index d527dcafdad..f8b9176b2b8 100644 --- a/code/addons/storysource/package.json +++ b/code/addons/storysource/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storysource", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "View a story’s source code to see how it works and paste into your app", "keywords": [ "addon", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 4a795e2551b..6e7c0c579cb 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Switch between multiple themes for you components in Storybook", "keywords": [ "css", @@ -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/toolbars/package.json b/code/addons/toolbars/package.json index 77859b04527..4b389c1325d 100644 --- a/code/addons/toolbars/package.json +++ b/code/addons/toolbars/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-toolbars", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Create your own toolbar items that control story rendering", "keywords": [ "addon", diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json index e02f77d74a0..144b48d4ace 100644 --- a/code/addons/viewport/package.json +++ b/code/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Build responsive components by adjusting Storybook’s viewport size and orientation", "keywords": [ "addon", @@ -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/builders/builder-manager/package.json b/code/builders/builder-manager/package.json index d692ee77e17..33a03c89dc1 100644 --- a/code/builders/builder-manager/package.json +++ b/code/builders/builder-manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-manager", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook manager builder", "keywords": [ "storybook" diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index 0c871173a00..af7f64e7cba 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "A plugin to run and build Storybooks with Vite", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme", "bugs": { diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 6b19424af54..78a2202c05f 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/e2e-tests/tags.spec.ts b/code/e2e-tests/tags.spec.ts index 37fb76fb814..8302a9a6a33 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/angular/package.json b/code/frameworks/angular/package.json index 726c56bb92f..ead540f1273 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.", "keywords": [ "storybook", @@ -34,7 +34,7 @@ ], "scripts": { "check": "node ../../../scripts/node_modules/.bin/tsc", - "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/tsc.ts" + "prep": "rimraf dist && node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/tsc.ts" }, "dependencies": { "@storybook/builder-webpack5": "workspace:*", @@ -65,18 +65,18 @@ }, "devDependencies": { "@analogjs/vite-plugin-angular": "^0.2.24", - "@angular-devkit/architect": "^0.1700.5", - "@angular-devkit/build-angular": "^17.0.5", - "@angular-devkit/core": "^17.0.5", - "@angular/animations": "^17.0.5", - "@angular/cli": "^17.0.5", - "@angular/common": "^17.0.5", - "@angular/compiler": "^17.0.5", - "@angular/compiler-cli": "^17.0.5", - "@angular/core": "^17.0.5", - "@angular/forms": "^17.0.5", - "@angular/platform-browser": "^17.0.5", - "@angular/platform-browser-dynamic": "^17.0.5", + "@angular-devkit/architect": "^0.1703.0", + "@angular-devkit/build-angular": "^17.3.0", + "@angular-devkit/core": "^17.3.0", + "@angular/animations": "^17.3.0", + "@angular/cli": "^17.3.0", + "@angular/common": "^17.3.0", + "@angular/compiler": "^17.3.0", + "@angular/compiler-cli": "^17.3.0", + "@angular/core": "^17.3.0", + "@angular/forms": "^17.3.0", + "@angular/platform-browser": "^17.3.0", + "@angular/platform-browser-dynamic": "^17.3.0", "@types/cross-spawn": "^6.0.2", "@types/tmp": "^0.2.3", "cross-spawn": "^7.0.3", @@ -115,6 +115,7 @@ }, "builders": "dist/builders/builders.json", "bundler": { + "post": "./scripts/postbuild.js", "tsConfig": "tsconfig.build.json" }, "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae17" diff --git a/code/frameworks/angular/scripts/postbuild.js b/code/frameworks/angular/scripts/postbuild.js new file mode 100644 index 00000000000..68af6389ac3 --- /dev/null +++ b/code/frameworks/angular/scripts/postbuild.js @@ -0,0 +1,16 @@ +/** + * This postbuild fix is needed to add a ts-ignore to the generated public-types.d.ts file. + * The AngularCore.InputSignal and AngularCore.InputSignalWithTransform types do not exist in Angular + * versions < 17.2. In these versions, the unresolved types will error and prevent Storybook from starting/building. + * This postbuild script adds a ts-ignore statement above the unresolved types to prevent the errors. + */ + +const fs = require('fs'); +const path = require('path'); + +const filePath = path.join(__dirname, '../dist/client/public-types.d.ts'); +const fileContent = fs.readFileSync(filePath, 'utf8'); +const newContent = fileContent + .replaceAll(/(type AngularInputSignal)/g, '// @ts-ignore\n$1') + .replaceAll(/(type AngularOutputEmitterRef)/g, '// @ts-ignore\n$1'); +fs.writeFileSync(filePath, newContent, 'utf8'); diff --git a/code/frameworks/angular/src/client/public-types.ts b/code/frameworks/angular/src/client/public-types.ts index 02571ddf141..9469cfda489 100644 --- a/code/frameworks/angular/src/client/public-types.ts +++ b/code/frameworks/angular/src/client/public-types.ts @@ -1,3 +1,5 @@ +/* eslint-disable prettier/prettier */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { AnnotatedStoryFn, Args, @@ -9,7 +11,7 @@ import { StrictArgs, ProjectAnnotations, } from '@storybook/types'; -import { EventEmitter } from '@angular/core'; +import * as AngularCore from '@angular/core'; import { AngularRenderer } from './types'; export type { Args, ArgTypes, Parameters, StrictArgs } from '@storybook/types'; @@ -21,27 +23,63 @@ export type { AngularRenderer }; * * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) */ -export type Meta = ComponentAnnotations>; +export type Meta = ComponentAnnotations< + AngularRenderer, + TransformComponentType +>; /** * Story function that represents a CSFv2 component example. * * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) */ -export type StoryFn = AnnotatedStoryFn>; +export type StoryFn = AnnotatedStoryFn< + AngularRenderer, + TransformComponentType +>; /** * Story object that represents a CSFv3 component example. * * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) */ -export type StoryObj = StoryAnnotations>; +export type StoryObj = StoryAnnotations< + AngularRenderer, + TransformComponentType +>; export type Decorator = DecoratorFunction; export type Loader = LoaderFunction; export type StoryContext = GenericStoryContext; export type Preview = ProjectAnnotations; +/** + * Utility type that transforms InputSignal and EventEmitter types + */ +type TransformComponentType = TransformInputSignalType>> + +// @ts-ignore Angular < 17.2 doesn't export InputSignal +type AngularInputSignal = AngularCore.InputSignal +// @ts-ignore Angular < 17.2 doesn't export InputSignalWithTransform +type AngularInputSignalWithTransform = AngularCore.InputSignalWithTransform +// @ts-ignore Angular < 17.3 doesn't export AngularOutputEmitterRef +type AngularOutputEmitterRef = AngularCore.OutputEmitterRef + +type AngularHasInputSignal = typeof AngularCore extends { input: infer U } ? true : false; +type AngularHasOutputSignal = typeof AngularCore extends { output: infer U } ? true : false; + +type InputSignal = AngularHasInputSignal extends true ? AngularInputSignal : never; +type InputSignalWithTransform = AngularHasInputSignal extends true ? AngularInputSignalWithTransform : never; +type OutputEmitterRef = AngularHasOutputSignal extends true ? AngularOutputEmitterRef : never; + +type TransformInputSignalType = { + [K in keyof T]: T[K] extends InputSignal ? E : T[K] extends InputSignalWithTransform ? U : T[K]; +}; + +type TransformOutputSignalType = { + [K in keyof T]: T[K] extends OutputEmitterRef ? (e: E) => void : T[K]; +}; + type TransformEventType = { - [K in keyof T]: T[K] extends EventEmitter ? (e: E) => void : T[K]; + [K in keyof T]: T[K] extends AngularCore.EventEmitter ? (e: E) => void : T[K]; }; diff --git a/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts new file mode 100644 index 00000000000..4077bc044c9 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts @@ -0,0 +1,50 @@ +import { Component, Input, input, output } from '@angular/core'; + +@Component({ + // Needs to be a different name to the CLI template button + selector: 'storybook-signal-button', + template: ` `, + styleUrls: ['./button.css'], +}) +export default class SignalButtonComponent { + /** + * Is this the principal call to action on the page? + */ + primary = input(false); + + /** + * What background color to use + */ + @Input() + backgroundColor?: string; + + /** + * How large should the button be? + */ + size = input('medium', { + transform: (val: 'small' | 'medium') => val, + }); + + /** + * Button contents + */ + label = input.required({ transform: (val: string) => val.trim() }); + + /** + * Optional click handler + */ + onClick = output(); + + public get classes(): string[] { + const mode = this.primary() ? 'storybook-button--primary' : 'storybook-button--secondary'; + + return ['storybook-button', `storybook-button--${this.size()}`, mode]; + } +} diff --git a/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.css b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.css new file mode 100644 index 00000000000..dc91dc76370 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.css @@ -0,0 +1,30 @@ +.storybook-button { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; +} +.storybook-button--primary { + color: white; + background-color: #1ea7fd; +} +.storybook-button--secondary { + color: #333; + background-color: transparent; + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; +} +.storybook-button--small { + font-size: 12px; + padding: 10px 16px; +} +.storybook-button--medium { + font-size: 14px; + padding: 11px 20px; +} +.storybook-button--large { + font-size: 16px; + padding: 12px 24px; +} diff --git a/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.stories.ts b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.stories.ts new file mode 100644 index 00000000000..df634f19096 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.stories.ts @@ -0,0 +1,65 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { fn } from '@storybook/test'; +import SignalButtonComponent from './button.component'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories +const meta: Meta = { + component: SignalButtonComponent, + tags: ['autodocs'], + argTypes: { + backgroundColor: { + control: 'color', + }, + // The following argTypes are necessary, + // because Compodoc does not support Angular's new input and output signals yet + primary: { + type: 'boolean', + }, + size: { + control: { + type: 'radio', + }, + options: ['small', 'medium'], + }, + label: { + type: 'string', + }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { + onClick: fn(), + primary: false, + size: 'medium', + }, +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + primary: true, + label: 'Button', + }, +}; + +export const Secondary: Story = { + args: { + label: 'Button', + }, +}; + +export const Medium: Story = { + args: { + size: 'medium', + label: 'Button', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Button', + }, +}; diff --git a/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts new file mode 100644 index 00000000000..4077bc044c9 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts @@ -0,0 +1,50 @@ +import { Component, Input, input, output } from '@angular/core'; + +@Component({ + // Needs to be a different name to the CLI template button + selector: 'storybook-signal-button', + template: ` `, + styleUrls: ['./button.css'], +}) +export default class SignalButtonComponent { + /** + * Is this the principal call to action on the page? + */ + primary = input(false); + + /** + * What background color to use + */ + @Input() + backgroundColor?: string; + + /** + * How large should the button be? + */ + size = input('medium', { + transform: (val: 'small' | 'medium') => val, + }); + + /** + * Button contents + */ + label = input.required({ transform: (val: string) => val.trim() }); + + /** + * Optional click handler + */ + onClick = output(); + + public get classes(): string[] { + const mode = this.primary() ? 'storybook-button--primary' : 'storybook-button--secondary'; + + return ['storybook-button', `storybook-button--${this.size()}`, mode]; + } +} diff --git a/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.css b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.css new file mode 100644 index 00000000000..dc91dc76370 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.css @@ -0,0 +1,30 @@ +.storybook-button { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; +} +.storybook-button--primary { + color: white; + background-color: #1ea7fd; +} +.storybook-button--secondary { + color: #333; + background-color: transparent; + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; +} +.storybook-button--small { + font-size: 12px; + padding: 10px 16px; +} +.storybook-button--medium { + font-size: 14px; + padding: 11px 20px; +} +.storybook-button--large { + font-size: 16px; + padding: 12px 24px; +} diff --git a/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.stories.ts b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.stories.ts new file mode 100644 index 00000000000..df634f19096 --- /dev/null +++ b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.stories.ts @@ -0,0 +1,65 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { fn } from '@storybook/test'; +import SignalButtonComponent from './button.component'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories +const meta: Meta = { + component: SignalButtonComponent, + tags: ['autodocs'], + argTypes: { + backgroundColor: { + control: 'color', + }, + // The following argTypes are necessary, + // because Compodoc does not support Angular's new input and output signals yet + primary: { + type: 'boolean', + }, + size: { + control: { + type: 'radio', + }, + options: ['small', 'medium'], + }, + label: { + type: 'string', + }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { + onClick: fn(), + primary: false, + size: 'medium', + }, +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + primary: true, + label: 'Button', + }, +}; + +export const Secondary: Story = { + args: { + label: 'Button', + }, +}; + +export const Medium: Story = { + args: { + size: 'medium', + label: 'Button', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Button', + }, +}; diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index 8331d7d0b74..1a6d5da0bd4 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember", "bugs": { diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 1005843c6bd..707cc08a1af 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/html-webpack5/package.json b/code/frameworks/html-webpack5/package.json index 04976553bdf..f18571c8a56 100644 --- a/code/frameworks/html-webpack5/package.json +++ b/code/frameworks/html-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index ec578f0a318..89ba93015e3 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Next.js", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs/src/index.ts b/code/frameworks/nextjs/src/index.ts index fcb073fefcd..a904f93ec89 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 00000000000..01948d524c2 --- /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/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index b801e842543..bcc6f95be1f 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/preact-webpack5/package.json b/code/frameworks/preact-webpack5/package.json index d91973ee5f7..73d43b969e5 100644 --- a/code/frameworks/preact-webpack5/package.json +++ b/code/frameworks/preact-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index b6a2256d8f8..2847033aa95 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index f39f13c0031..c31a9f1a286 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index b9edea51d8e..fb1229ae4b1 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 28bec0d59fd..c93ad69ea83 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/svelte-webpack5/package.json b/code/frameworks/svelte-webpack5/package.json index 725b3bb7a13..1a16442c90f 100644 --- a/code/frameworks/svelte-webpack5/package.json +++ b/code/frameworks/svelte-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index 4e7f20ff77a..3d3d328927f 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for SvelteKit", "keywords": [ "storybook", diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index 60cc82ddd78..b772057a8f9 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Vue3 and Vite: Develop Vue3 components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/vue3-webpack5/package.json b/code/frameworks/vue3-webpack5/package.json index 7575b4b5ad7..89e5566e1c2 100644 --- a/code/frameworks/vue3-webpack5/package.json +++ b/code/frameworks/vue3-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index 0ad55a968ef..ba23356d955 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-vite", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for web-components and Vite: Develop Web Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/web-components-webpack5/package.json b/code/frameworks/web-components-webpack5/package.json index 58e8127374d..93eb833d8e5 100644 --- a/code/frameworks/web-components-webpack5/package.json +++ b/code/frameworks/web-components-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-webpack5", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for web-components: View web components snippets in isolation with Hot Reloading.", "keywords": [ "lit", diff --git a/code/lib/channels/package.json b/code/lib/channels/package.json index babfab81a6f..01d77227729 100644 --- a/code/lib/channels/package.json +++ b/code/lib/channels/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/channels", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index cf44c6af83c..22b785f2058 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 2c1ebe24b77..c74d465c830 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index a781b00c1a4..eacc3faa375 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook's CLI - install, dev, build, upgrade, and more", "keywords": [ "cli", diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index 730e4ce8dea..4d83666780b 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/client-logger/package.json b/code/lib/client-logger/package.json index 1b93dcdb7a9..b1c043af772 100644 --- a/code/lib/client-logger/package.json +++ b/code/lib/client-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/client-logger", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 2c48f148ba7..eb23890dc0f 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" diff --git a/code/lib/core-common/package.json b/code/lib/core-common/package.json index 6755631bf49..a5aeeb71197 100644 --- a/code/lib/core-common/package.json +++ b/code/lib/core-common/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-common", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/core-common/src/js-package-manager/JsPackageManager.ts b/code/lib/core-common/src/js-package-manager/JsPackageManager.ts index 42a31985047..8f306850b03 100644 --- a/code/lib/core-common/src/js-package-manager/JsPackageManager.ts +++ b/code/lib/core-common/src/js-package-manager/JsPackageManager.ts @@ -17,6 +17,8 @@ const logger = console; export type PackageManagerName = 'npm' | 'yarn1' | 'yarn2' | 'pnpm'; +type StorybookPackage = keyof typeof storybookPackagesVersions; + /** * Extract package name and version from input * @@ -381,9 +383,8 @@ export abstract class JsPackageManager { public async getVersion(packageName: string, constraint?: string): Promise { let current: string | undefined; - if (/(@storybook|^sb$|^storybook$)/.test(packageName)) { - // @ts-expect-error (Converted from ts-ignore) - current = storybookPackagesVersions[packageName]; + if (packageName in storybookPackagesVersions) { + current = storybookPackagesVersions[packageName as StorybookPackage]; } let latest; diff --git a/code/lib/core-common/src/versions.ts b/code/lib/core-common/src/versions.ts index 7ccc1ac3323..044c150b342 100644 --- a/code/lib/core-common/src/versions.ts +++ b/code/lib/core-common/src/versions.ts @@ -1,83 +1,83 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '8.1.0-alpha.1', - '@storybook/addon-actions': '8.1.0-alpha.1', - '@storybook/addon-backgrounds': '8.1.0-alpha.1', - '@storybook/addon-controls': '8.1.0-alpha.1', - '@storybook/addon-docs': '8.1.0-alpha.1', - '@storybook/addon-essentials': '8.1.0-alpha.1', - '@storybook/addon-highlight': '8.1.0-alpha.1', - '@storybook/addon-interactions': '8.1.0-alpha.1', - '@storybook/addon-jest': '8.1.0-alpha.1', - '@storybook/addon-links': '8.1.0-alpha.1', - '@storybook/addon-mdx-gfm': '8.1.0-alpha.1', - '@storybook/addon-measure': '8.1.0-alpha.1', - '@storybook/addon-onboarding': '8.1.0-alpha.1', - '@storybook/addon-outline': '8.1.0-alpha.1', - '@storybook/addon-storysource': '8.1.0-alpha.1', - '@storybook/addon-themes': '8.1.0-alpha.1', - '@storybook/addon-toolbars': '8.1.0-alpha.1', - '@storybook/addon-viewport': '8.1.0-alpha.1', - '@storybook/angular': '8.1.0-alpha.1', - '@storybook/blocks': '8.1.0-alpha.1', - '@storybook/builder-manager': '8.1.0-alpha.1', - '@storybook/builder-vite': '8.1.0-alpha.1', - '@storybook/builder-webpack5': '8.1.0-alpha.1', - '@storybook/channels': '8.1.0-alpha.1', - '@storybook/cli': '8.1.0-alpha.1', - '@storybook/client-logger': '8.1.0-alpha.1', - '@storybook/codemod': '8.1.0-alpha.1', - '@storybook/components': '8.1.0-alpha.1', - '@storybook/core-common': '8.1.0-alpha.1', - '@storybook/core-events': '8.1.0-alpha.1', - '@storybook/core-server': '8.1.0-alpha.1', - '@storybook/core-webpack': '8.1.0-alpha.1', - '@storybook/csf-plugin': '8.1.0-alpha.1', - '@storybook/csf-tools': '8.1.0-alpha.1', - '@storybook/docs-tools': '8.1.0-alpha.1', - '@storybook/ember': '8.1.0-alpha.1', - '@storybook/html': '8.1.0-alpha.1', - '@storybook/html-vite': '8.1.0-alpha.1', - '@storybook/html-webpack5': '8.1.0-alpha.1', - '@storybook/instrumenter': '8.1.0-alpha.1', - '@storybook/manager': '8.1.0-alpha.1', - '@storybook/manager-api': '8.1.0-alpha.1', - '@storybook/nextjs': '8.1.0-alpha.1', - '@storybook/node-logger': '8.1.0-alpha.1', - '@storybook/preact': '8.1.0-alpha.1', - '@storybook/preact-vite': '8.1.0-alpha.1', - '@storybook/preact-webpack5': '8.1.0-alpha.1', - '@storybook/preset-create-react-app': '8.1.0-alpha.1', - '@storybook/preset-html-webpack': '8.1.0-alpha.1', - '@storybook/preset-preact-webpack': '8.1.0-alpha.1', - '@storybook/preset-react-webpack': '8.1.0-alpha.1', - '@storybook/preset-server-webpack': '8.1.0-alpha.1', - '@storybook/preset-svelte-webpack': '8.1.0-alpha.1', - '@storybook/preset-vue3-webpack': '8.1.0-alpha.1', - '@storybook/preview': '8.1.0-alpha.1', - '@storybook/preview-api': '8.1.0-alpha.1', - '@storybook/react': '8.1.0-alpha.1', - '@storybook/react-dom-shim': '8.1.0-alpha.1', - '@storybook/react-vite': '8.1.0-alpha.1', - '@storybook/react-webpack5': '8.1.0-alpha.1', - '@storybook/router': '8.1.0-alpha.1', - '@storybook/server': '8.1.0-alpha.1', - '@storybook/server-webpack5': '8.1.0-alpha.1', - '@storybook/source-loader': '8.1.0-alpha.1', - '@storybook/svelte': '8.1.0-alpha.1', - '@storybook/svelte-vite': '8.1.0-alpha.1', - '@storybook/svelte-webpack5': '8.1.0-alpha.1', - '@storybook/sveltekit': '8.1.0-alpha.1', - '@storybook/telemetry': '8.1.0-alpha.1', - '@storybook/test': '8.1.0-alpha.1', - '@storybook/theming': '8.1.0-alpha.1', - '@storybook/types': '8.1.0-alpha.1', - '@storybook/vue3': '8.1.0-alpha.1', - '@storybook/vue3-vite': '8.1.0-alpha.1', - '@storybook/vue3-webpack5': '8.1.0-alpha.1', - '@storybook/web-components': '8.1.0-alpha.1', - '@storybook/web-components-vite': '8.1.0-alpha.1', - '@storybook/web-components-webpack5': '8.1.0-alpha.1', - sb: '8.1.0-alpha.1', - storybook: '8.1.0-alpha.1', + '@storybook/addon-a11y': '8.1.0-alpha.3', + '@storybook/addon-actions': '8.1.0-alpha.3', + '@storybook/addon-backgrounds': '8.1.0-alpha.3', + '@storybook/addon-controls': '8.1.0-alpha.3', + '@storybook/addon-docs': '8.1.0-alpha.3', + '@storybook/addon-essentials': '8.1.0-alpha.3', + '@storybook/addon-highlight': '8.1.0-alpha.3', + '@storybook/addon-interactions': '8.1.0-alpha.3', + '@storybook/addon-jest': '8.1.0-alpha.3', + '@storybook/addon-links': '8.1.0-alpha.3', + '@storybook/addon-mdx-gfm': '8.1.0-alpha.3', + '@storybook/addon-measure': '8.1.0-alpha.3', + '@storybook/addon-onboarding': '8.1.0-alpha.3', + '@storybook/addon-outline': '8.1.0-alpha.3', + '@storybook/addon-storysource': '8.1.0-alpha.3', + '@storybook/addon-themes': '8.1.0-alpha.3', + '@storybook/addon-toolbars': '8.1.0-alpha.3', + '@storybook/addon-viewport': '8.1.0-alpha.3', + '@storybook/angular': '8.1.0-alpha.3', + '@storybook/blocks': '8.1.0-alpha.3', + '@storybook/builder-manager': '8.1.0-alpha.3', + '@storybook/builder-vite': '8.1.0-alpha.3', + '@storybook/builder-webpack5': '8.1.0-alpha.3', + '@storybook/channels': '8.1.0-alpha.3', + '@storybook/cli': '8.1.0-alpha.3', + '@storybook/client-logger': '8.1.0-alpha.3', + '@storybook/codemod': '8.1.0-alpha.3', + '@storybook/components': '8.1.0-alpha.3', + '@storybook/core-common': '8.1.0-alpha.3', + '@storybook/core-events': '8.1.0-alpha.3', + '@storybook/core-server': '8.1.0-alpha.3', + '@storybook/core-webpack': '8.1.0-alpha.3', + '@storybook/csf-plugin': '8.1.0-alpha.3', + '@storybook/csf-tools': '8.1.0-alpha.3', + '@storybook/docs-tools': '8.1.0-alpha.3', + '@storybook/ember': '8.1.0-alpha.3', + '@storybook/html': '8.1.0-alpha.3', + '@storybook/html-vite': '8.1.0-alpha.3', + '@storybook/html-webpack5': '8.1.0-alpha.3', + '@storybook/instrumenter': '8.1.0-alpha.3', + '@storybook/manager': '8.1.0-alpha.3', + '@storybook/manager-api': '8.1.0-alpha.3', + '@storybook/nextjs': '8.1.0-alpha.3', + '@storybook/node-logger': '8.1.0-alpha.3', + '@storybook/preact': '8.1.0-alpha.3', + '@storybook/preact-vite': '8.1.0-alpha.3', + '@storybook/preact-webpack5': '8.1.0-alpha.3', + '@storybook/preset-create-react-app': '8.1.0-alpha.3', + '@storybook/preset-html-webpack': '8.1.0-alpha.3', + '@storybook/preset-preact-webpack': '8.1.0-alpha.3', + '@storybook/preset-react-webpack': '8.1.0-alpha.3', + '@storybook/preset-server-webpack': '8.1.0-alpha.3', + '@storybook/preset-svelte-webpack': '8.1.0-alpha.3', + '@storybook/preset-vue3-webpack': '8.1.0-alpha.3', + '@storybook/preview': '8.1.0-alpha.3', + '@storybook/preview-api': '8.1.0-alpha.3', + '@storybook/react': '8.1.0-alpha.3', + '@storybook/react-dom-shim': '8.1.0-alpha.3', + '@storybook/react-vite': '8.1.0-alpha.3', + '@storybook/react-webpack5': '8.1.0-alpha.3', + '@storybook/router': '8.1.0-alpha.3', + '@storybook/server': '8.1.0-alpha.3', + '@storybook/server-webpack5': '8.1.0-alpha.3', + '@storybook/source-loader': '8.1.0-alpha.3', + '@storybook/svelte': '8.1.0-alpha.3', + '@storybook/svelte-vite': '8.1.0-alpha.3', + '@storybook/svelte-webpack5': '8.1.0-alpha.3', + '@storybook/sveltekit': '8.1.0-alpha.3', + '@storybook/telemetry': '8.1.0-alpha.3', + '@storybook/test': '8.1.0-alpha.3', + '@storybook/theming': '8.1.0-alpha.3', + '@storybook/types': '8.1.0-alpha.3', + '@storybook/vue3': '8.1.0-alpha.3', + '@storybook/vue3-vite': '8.1.0-alpha.3', + '@storybook/vue3-webpack5': '8.1.0-alpha.3', + '@storybook/web-components': '8.1.0-alpha.3', + '@storybook/web-components-vite': '8.1.0-alpha.3', + '@storybook/web-components-webpack5': '8.1.0-alpha.3', + sb: '8.1.0-alpha.3', + storybook: '8.1.0-alpha.3', }; diff --git a/code/lib/core-events/package.json b/code/lib/core-events/package.json index aaa150c0cd9..173a01d7d7d 100644 --- a/code/lib/core-events/package.json +++ b/code/lib/core-events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-events", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Event names used in storybook core", "keywords": [ "storybook" diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 6a2428523cb..291f7c6ab32 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-server", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 22af5e8e240..04f107d789d 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index 258f868ba61..da1737a6aed 100644 --- a/code/lib/csf-plugin/package.json +++ b/code/lib/csf-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-plugin", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Enrich CSF files via static analysis", "keywords": [ "storybook" diff --git a/code/lib/csf-tools/package.json b/code/lib/csf-tools/package.json index d713f09c8ad..ea20c33f696 100644 --- a/code/lib/csf-tools/package.json +++ b/code/lib/csf-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-tools", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Parse and manipulate CSF and Storybook config files", "keywords": [ "storybook" diff --git a/code/lib/csf-tools/src/CsfFile.test.ts b/code/lib/csf-tools/src/CsfFile.test.ts index 7e0d223a23a..21e902cce52 100644 --- a/code/lib/csf-tools/src/CsfFile.test.ts +++ b/code/lib/csf-tools/src/CsfFile.test.ts @@ -619,7 +619,7 @@ describe('CsfFile', () => { }); it('no metadata', () => { - expect(() => + expect( parse( dedent` export default { foo: '5' }; @@ -627,7 +627,15 @@ describe('CsfFile', () => { export const B = () => {}; ` ) - ).toThrow('CSF: missing title/component'); + ).toMatchInlineSnapshot(` + meta: + title: Default Title + stories: + - id: default-title--a + name: A + - id: default-title--b + name: B + `); }); it('dynamic titles', () => { diff --git a/code/lib/csf-tools/src/CsfFile.ts b/code/lib/csf-tools/src/CsfFile.ts index f1cce083da9..4516a293b1c 100644 --- a/code/lib/csf-tools/src/CsfFile.ts +++ b/code/lib/csf-tools/src/CsfFile.ts @@ -478,14 +478,6 @@ export class CsfFile { throw new NoMetaError('missing default export', self._ast, self._fileName); } - if (!self._meta.title && !self._meta.component) { - throw new Error(dedent` - CSF: missing title/component ${formatLocation(self._ast, self._fileName)} - - More info: https://storybook.js.org/docs/react/writing-stories#default-export - `); - } - // default export can come at any point in the file, so we do this post processing last const entries = Object.entries(self._stories); self._meta.title = this._makeTitle(self._meta?.title as string); diff --git a/code/lib/docs-tools/package.json b/code/lib/docs-tools/package.json index e57e812eb30..bafce9663d5 100644 --- a/code/lib/docs-tools/package.json +++ b/code/lib/docs-tools/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/docs-tools", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Shared utility functions for frameworks to implement docs", "keywords": [ "storybook" diff --git a/code/lib/instrumenter/package.json b/code/lib/instrumenter/package.json index b7cbb75e41e..9e71a3b5430 100644 --- a/code/lib/instrumenter/package.json +++ b/code/lib/instrumenter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/instrumenter", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index 3eeb6ea86ed..57c812109e8 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/manager-api/package.json b/code/lib/manager-api/package.json index 8832ff4bd12..11ac1473f60 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager-api", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook Manager API & Context", "keywords": [ "storybook" @@ -49,6 +49,7 @@ "@storybook/core-events": "workspace:*", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", + "@storybook/icons": "^1.2.5", "@storybook/router": "workspace:*", "@storybook/theming": "workspace:*", "@storybook/types": "workspace:*", diff --git a/code/lib/manager-api/src/modules/whatsnew.ts b/code/lib/manager-api/src/modules/whatsnew.tsx similarity index 96% rename from code/lib/manager-api/src/modules/whatsnew.ts rename to code/lib/manager-api/src/modules/whatsnew.tsx index 8f465325d50..abdda88d6b1 100644 --- a/code/lib/manager-api/src/modules/whatsnew.ts +++ b/code/lib/manager-api/src/modules/whatsnew.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { global } from '@storybook/global'; import type { WhatsNewCache, WhatsNewData } from '@storybook/core-events'; import { @@ -7,6 +8,7 @@ import { TOGGLE_WHATS_NEW_NOTIFICATIONS, } from '@storybook/core-events'; import type { ModuleFn } from '../lib/types'; +import { StorybookIcon } from '@storybook/icons'; export type SubState = { whatsNewData?: WhatsNewData; @@ -95,7 +97,7 @@ export const init: ModuleFn = ({ fullAPI, store, provider }) => { headline: whatsNewData.title, subHeadline: "Learn what's new in Storybook", }, - icon: { name: 'storybook' }, + icon: , onClear({ dismissed }: any) { if (dismissed) { setWhatsNewCache({ lastDismissedPost: whatsNewData.url }); diff --git a/code/lib/manager-api/src/version.ts b/code/lib/manager-api/src/version.ts index a6d040b0eac..5a7155b190a 100644 --- a/code/lib/manager-api/src/version.ts +++ b/code/lib/manager-api/src/version.ts @@ -1 +1 @@ -export const version = '8.1.0-alpha.1'; +export const version = '8.1.0-alpha.3'; diff --git a/code/lib/node-logger/package.json b/code/lib/node-logger/package.json index 4bbefd1d2ba..a65b0ea17db 100644 --- a/code/lib/node-logger/package.json +++ b/code/lib/node-logger/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/node-logger", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 4fb9ccb2070..95e0fc8c551 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview-api", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/src/index.ts b/code/lib/preview-api/src/index.ts index e47cdaa0a0d..779cfeba155 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/preview-web/Preview.tsx b/code/lib/preview-api/src/modules/preview-web/Preview.tsx index af851edfc30..29ea7104594 100644 --- a/code/lib/preview-api/src/modules/preview-web/Preview.tsx +++ b/code/lib/preview-api/src/modules/preview-web/Preview.tsx @@ -100,9 +100,7 @@ export class Preview { get: (_, method) => { if (this.storyStoreValue) { deprecate('Accessing the Story Store is deprecated and will be removed in 9.0'); - - // @ts-expect-error I'm not sure if there's a way to keep TS happy here - return this.storyStoreValue[method]; + return this.storyStoreValue[method as keyof StoryStore]; } throw new StoryStoreAccessedBeforeInitializationError(); 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 238885f44ba..147038a5a8d 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 862f8cbcd50..e5785a6a3f0 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 47465eacc8e..57e8fcda9a2 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/preview/package.json b/code/lib/preview/package.json index c14feb4d127..fb46f24df7f 100644 --- a/code/lib/preview/package.json +++ b/code/lib/preview/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json index 302d1310b98..0b595f92df8 100644 --- a/code/lib/react-dom-shim/package.json +++ b/code/lib/react-dom-shim/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-dom-shim", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/router/package.json b/code/lib/router/package.json index 1467d74ab9d..5dcf468fbd9 100644 --- a/code/lib/router/package.json +++ b/code/lib/router/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/router", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook Router", "keywords": [ "storybook" diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index e3d0f73012c..4752e290e69 100644 --- a/code/lib/source-loader/package.json +++ b/code/lib/source-loader/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/source-loader", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Source loader", "keywords": [ "lib", diff --git a/code/lib/telemetry/package.json b/code/lib/telemetry/package.json index 71ebdf362fb..61a1dbb343b 100644 --- a/code/lib/telemetry/package.json +++ b/code/lib/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/telemetry", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Telemetry logging for crash reports and usage statistics", "keywords": [ "storybook" diff --git a/code/lib/test/package.json b/code/lib/test/package.json index 86caf7db8c6..845c1468b94 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/test", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "", "keywords": [ "storybook" diff --git a/code/lib/test/src/index.ts b/code/lib/test/src/index.ts index 600c68f1d3b..7bd72666f34 100644 --- a/code/lib/test/src/index.ts +++ b/code/lib/test/src/index.ts @@ -36,6 +36,6 @@ const resetAllMocksLoader: LoaderFunction = ({ parameters }) => { } }; -// @ts-expect-error We are using this as a default Storybook loader, when the test package is used. This avoids the need for optional peer dependency workarounds. +// We are using this as a default Storybook loader, when the test package is used. This avoids the need for optional peer dependency workarounds. // eslint-disable-next-line no-underscore-dangle -global.__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; +(global as any).__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; diff --git a/code/lib/theming/package.json b/code/lib/theming/package.json index b6592a4c6f9..ea92d73de72 100644 --- a/code/lib/theming/package.json +++ b/code/lib/theming/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/theming", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook Components", "keywords": [ "storybook" diff --git a/code/lib/theming/src/themes/dark.ts b/code/lib/theming/src/themes/dark.ts index 4cb8f19f29b..173e735ef36 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/lib/types/package.json b/code/lib/types/package.json index 7576e7349bb..fe8ab39911e 100644 --- a/code/lib/types/package.json +++ b/code/lib/types/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/types", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook TS Types", "keywords": [ "storybook" diff --git a/code/lib/types/src/modules/api.ts b/code/lib/types/src/modules/api.ts index cee23ca903e..1ccc98cc9e2 100644 --- a/code/lib/types/src/modules/api.ts +++ b/code/lib/types/src/modules/api.ts @@ -120,6 +120,14 @@ interface OnClearOptions { dismissed: boolean; } +/** + * @deprecated Use ReactNode for the icon instead. + * @see https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#icons-is-deprecated + */ +interface DeprecatedIconType { + name: string; + color?: string; +} export interface API_Notification { id: string; link: string; @@ -127,10 +135,8 @@ export interface API_Notification { headline: string; subHeadline?: string | any; }; - icon?: { - name: string; - color?: string; - }; + // TODO: Remove DeprecatedIconType in 9.0 + icon?: React.ReactNode | DeprecatedIconType; onClear?: (options: OnClearOptions) => void; } diff --git a/code/package.json b/code/package.json index 00fa9ec8061..dba59085cce 100644 --- a/code/package.json +++ b/code/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/root", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "private": true, "description": "Storybook root", "homepage": "https://storybook.js.org/", diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json index c765737ee71..50730f5188b 100644 --- a/code/presets/create-react-app/package.json +++ b/code/presets/create-react-app/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-create-react-app", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Create React App preset", "keywords": [ "storybook" diff --git a/code/presets/html-webpack/package.json b/code/presets/html-webpack/package.json index 1a0ee7ad581..a56d88e1d37 100644 --- a/code/presets/html-webpack/package.json +++ b/code/presets/html-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-html-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/preact-webpack/package.json b/code/presets/preact-webpack/package.json index ab154aea59b..1c58ec8cd98 100644 --- a/code/presets/preact-webpack/package.json +++ b/code/presets/preact-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-preact-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Preact: Develop Preact Component in isolation.", "keywords": [ "storybook" diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json index 305e7d84a0a..5505bf7a0ab 100644 --- a/code/presets/react-webpack/package.json +++ b/code/presets/react-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-react-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading", "keywords": [ "storybook" diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json index 0d53be53372..00fb9b82526 100644 --- a/code/presets/server-webpack/package.json +++ b/code/presets/server-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-server-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/svelte-webpack/package.json b/code/presets/svelte-webpack/package.json index 959ab344288..c839a3f66aa 100644 --- a/code/presets/svelte-webpack/package.json +++ b/code/presets/svelte-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-svelte-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/vue3-webpack/package.json b/code/presets/vue3-webpack/package.json index 4a15cdc0723..13245bfa956 100644 --- a/code/presets/vue3-webpack/package.json +++ b/code/presets/vue3-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-vue3-webpack", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook for Vue 3: Develop Vue 3 Components in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/renderers/html/package.json b/code/renderers/html/package.json index 6cfd2e08788..63c1eecdc6f 100644 --- a/code/renderers/html/package.json +++ b/code/renderers/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook HTML renderer", "keywords": [ "storybook" diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index ff6217b7121..5d63e345fbe 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook Preact renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index 08ace69c3cb..76da8b0c4b3 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook React renderer", "keywords": [ "storybook" @@ -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 277f92ddde1..fc78c1f27d6 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 ( + diff --git a/code/renderers/svelte/src/__test__/composeStories/CustomRenderComponent.svelte b/code/renderers/svelte/src/__test__/composeStories/CustomRenderComponent.svelte new file mode 100644 index 00000000000..2619c6cfc84 --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/CustomRenderComponent.svelte @@ -0,0 +1,11 @@ + + +
+

I am a custom render function

+
diff --git a/code/renderers/svelte/src/__test__/composeStories/InputFilledStoryComponent.svelte b/code/renderers/svelte/src/__test__/composeStories/InputFilledStoryComponent.svelte new file mode 100644 index 00000000000..e01457d58a1 --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/InputFilledStoryComponent.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/renderers/svelte/src/__test__/composeStories/LoaderStoryComponent.svelte b/code/renderers/svelte/src/__test__/composeStories/LoaderStoryComponent.svelte new file mode 100644 index 00000000000..5295f9ea991 --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/LoaderStoryComponent.svelte @@ -0,0 +1,15 @@ + + +
+
{loaded.value}
+
{String(data)}
+
\ No newline at end of file diff --git a/code/renderers/svelte/src/__test__/composeStories/StoryWithLocaleComponent.svelte b/code/renderers/svelte/src/__test__/composeStories/StoryWithLocaleComponent.svelte new file mode 100644 index 00000000000..fc71b19231e --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/StoryWithLocaleComponent.svelte @@ -0,0 +1,11 @@ + + +
+

locale: {locale}

+
\ No newline at end of file diff --git a/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap b/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap new file mode 100644 index 00000000000..3143b671b0c --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap @@ -0,0 +1,180 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Renders CSF2Secondary story 1`] = ` + +
+ + + + + +
+ +`; + +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` + +
+
+ + + +
+ + + + + + +
+ +`; + +exports[`Renders CSF3Button story 1`] = ` + +
+ + + + + +
+ +`; + +exports[`Renders CSF3ButtonWithRender story 1`] = ` + +
+
+

+ I am a custom render function +

+ + + +
+ + + + +
+ +`; + +exports[`Renders CSF3InputFieldFilled story 1`] = ` + +
+ + + + + +
+ +`; + +exports[`Renders CSF3Primary story 1`] = ` + +
+ + + + + +
+ +`; + +exports[`Renders LoaderStory story 1`] = ` + +
+
+
+ loaded data +
+ +
+ mockFn return value +
+
+ + + + +
+ +`; + +exports[`Renders NewStory story 1`] = ` + +
+
+ + + +
+ + + + + + +
+ +`; diff --git a/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts b/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts new file mode 100644 index 00000000000..f678ff52214 --- /dev/null +++ b/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts @@ -0,0 +1,187 @@ +/// ; +import { it, expect, vi, describe, afterEach } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/svelte'; +// import '@testing-library/svelte/vitest'; +import { expectTypeOf } from 'expect-type'; +import type { Meta } from '../..'; +import * as stories from './Button.stories'; +// import type Button from './Button.svelte'; +import type Button from './Button.svelte'; +import { composeStories, composeStory, setProjectAnnotations } from '../../portable-stories'; + +// example with composeStories, returns an object with all stories composed with args/decorators +const { CSF3Primary, LoaderStory } = composeStories(stories); + +// example with composeStory, returns a single story composed with args/decorators +const Secondary = composeStory(stories.CSF2Secondary, stories.default); +describe('renders', () => { + afterEach(() => { + cleanup(); + }); + + it('renders primary button with custom props via composeStory', () => { + // We unfortunately can't do the following: + // render(CSF3Primary.Component, { ...CSF3Primary.props, label: 'Hello world' }); + // Because the props will be passed to the first decorator of the story instead + // of the actual component of the story. This is because of our current PreviewRender structure + + const Composed = composeStory( + { + ...stories.CSF3Primary, + args: { ...stories.CSF3Primary.args, label: 'Hello world' }, + }, + stories.default + ); + + render(Composed.Component, Composed.props); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); + }); + + it('reuses args from composed story', () => { + render(Secondary.Component, Secondary.props); + const buttonElement = screen.getByRole('button'); + expect(buttonElement.textContent).toMatch(Secondary.args.label); + }); + + // TODO TypeError: component.$on is not a function - Potentially only works in Svelte 4 + it.skip('onclick handler is called', async () => { + const onClickSpy = vi.fn(); + const { component } = render(Secondary.Component, { ...Secondary.props, onClick: onClickSpy }); + component.$on('click', onClickSpy); + const buttonElement = screen.getByRole('button'); + buttonElement.click(); + expect(onClickSpy).toHaveBeenCalled(); + }); + + it('reuses args from composeStories', () => { + const { getByText } = render(CSF3Primary.Component, CSF3Primary.props); + const buttonElement = getByText(/foo/i); + expect(buttonElement).not.toBeNull(); + }); + + it('should call and compose loaders data', async () => { + await LoaderStory.load(); + const { getByTestId } = render(LoaderStory.Component, LoaderStory.props); + expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); + expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); + // spy assertions happen in the play function and should work + await LoaderStory.play!(); + }); +}); + +describe('projectAnnotations', () => { + afterEach(() => { + cleanup(); + }); + + it('renders with default projectAnnotations', () => { + setProjectAnnotations([ + { + parameters: { injected: true }, + globalTypes: { + locale: { defaultValue: 'en' }, + }, + }, + ]); + const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); + const { getByText } = render(WithEnglishText.Component, WithEnglishText.props); + const buttonElement = getByText('Hello!'); + expect(buttonElement).not.toBeNull(); + expect(WithEnglishText.parameters?.injected).toBe(true); + }); + + it('renders with custom projectAnnotations via composeStory params', () => { + const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { + globals: { locale: 'pt' }, + }); + const { getByText } = render(WithPortugueseText.Component, WithPortugueseText.props); + const buttonElement = getByText('Olá!'); + expect(buttonElement).not.toBeNull(); + }); +}); + +describe('CSF3', () => { + afterEach(() => { + cleanup(); + }); + + it('renders with inferred globalRender', () => { + const Primary = composeStory(stories.CSF3Button, stories.default); + render(Primary.Component, Primary.props); + const buttonElement = screen.getByText(/foo/i); + expect(buttonElement).not.toBeNull(); + }); + + it('renders with custom render function', () => { + const Primary = composeStory(stories.CSF3ButtonWithRender, stories.default); + + render(Primary.Component, Primary.props); + expect(screen.getByTestId('custom-render')).not.toBeNull(); + }); + + it('renders with play function without canvas element', async () => { + const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); + + render(CSF3InputFieldFilled.Component, CSF3InputFieldFilled.props); + + await CSF3InputFieldFilled.play!(); + + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + }); + + it('renders with play function with canvas element', async () => { + const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); + + const { container } = render(CSF3InputFieldFilled.Component, CSF3InputFieldFilled.props); + + await CSF3InputFieldFilled.play!({ canvasElement: container }); + + const input = screen.getByTestId('input') as HTMLInputElement; + expect(input.value).toEqual('Hello world!'); + }); +}); + +describe('ComposeStories types', () => { + // this file tests Typescript types that's why there are no assertions + it('Should support typescript operators', () => { + type ComposeStoriesParam = Parameters[0]; + + expectTypeOf({ + ...stories, + default: stories.default as Meta, + }).toMatchTypeOf(); + + expectTypeOf({ + ...stories, + + /** + * Types of property 'argTypes' are incompatible. + * Type '{ backgroundColor: { control: string; }; size: { control: { type: string; }; options: string[]; }; }' + * has no properties in common with type 'Partial>'. + */ + // @ts-expect-error fix this later + default: stories.default satisfies Meta, + }).toMatchTypeOf(); + }); +}); + +// Batch snapshot testing +const testCases = Object.values(composeStories(stories)).map( + (Story) => [Story.storyName, Story] as [string, typeof Story] +); +it.each(testCases)('Renders %s story', async (_storyName, Story) => { + cleanup(); + + if (_storyName === 'CSF2StoryWithLocale') { + return; + } + + await Story.load(); + + const { container } = await render(Story.Component, Story.props); + + await Story.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); +}); diff --git a/code/renderers/svelte/src/components/AddStorybookIdDecorator.svelte b/code/renderers/svelte/src/components/AddStorybookIdDecorator.svelte new file mode 100644 index 00000000000..3c59f352273 --- /dev/null +++ b/code/renderers/svelte/src/components/AddStorybookIdDecorator.svelte @@ -0,0 +1,7 @@ + + +
+ +
\ No newline at end of file diff --git a/code/renderers/svelte/src/globals.ts b/code/renderers/svelte/src/globals.ts index a9adc16b090..ce0d0501bab 100644 --- a/code/renderers/svelte/src/globals.ts +++ b/code/renderers/svelte/src/globals.ts @@ -1,5 +1 @@ -import { global } from '@storybook/global'; - -const { window: globalWindow } = global; - -globalWindow.STORYBOOK_ENV = 'svelte'; +globalThis.STORYBOOK_ENV = 'svelte'; diff --git a/code/renderers/svelte/src/index.ts b/code/renderers/svelte/src/index.ts index 145fbf2612e..25846610582 100644 --- a/code/renderers/svelte/src/index.ts +++ b/code/renderers/svelte/src/index.ts @@ -3,6 +3,7 @@ import './globals'; export * from './public-types'; +export * from './portable-stories'; // optimization: stop HMR propagation in webpack if (typeof module !== 'undefined') module?.hot?.decline(); diff --git a/code/renderers/svelte/src/playwright.ts b/code/renderers/svelte/src/playwright.ts new file mode 100644 index 00000000000..d1538f820e2 --- /dev/null +++ b/code/renderers/svelte/src/playwright.ts @@ -0,0 +1 @@ +export { createPlaywrightTest as createTest } from '@storybook/preview-api'; diff --git a/code/renderers/svelte/src/portable-stories.ts b/code/renderers/svelte/src/portable-stories.ts new file mode 100644 index 00000000000..abf9ca79b2e --- /dev/null +++ b/code/renderers/svelte/src/portable-stories.ts @@ -0,0 +1,175 @@ +import { + composeStory as originalComposeStory, + composeStories as originalComposeStories, + setProjectAnnotations as originalSetProjectAnnotations, +} from '@storybook/preview-api'; +import type { + Args, + ProjectAnnotations, + StoryAnnotationsOrFn, + Store_CSFExports, + StoriesWithPartialProps, + ComposedStoryFn, +} from '@storybook/types'; + +import * as svelteProjectAnnotations from './entry-preview'; +import type { Meta } from './public-types'; +import type { SvelteRenderer } from './types'; +import PreviewRender from '@storybook/svelte/internal/PreviewRender.svelte'; +// @ts-expect-error Don't know why TS doesn't pick up the types export here +import { createSvelte5Props } from '@storybook/svelte/internal/createSvelte5Props'; +import { IS_SVELTE_V4 } from './utils'; + +type ComposedStory = ComposedStoryFn & { + Component: typeof PreviewRender; + // these props current refer to the props of PReviewRender, not the user's component's + props: any; +}; + +type MapToComposed = { + [K in keyof TModule]: TModule[K] extends StoryAnnotationsOrFn< + SvelteRenderer, + infer TArgs extends Args + > + ? ComposedStory + : never; +}; + +/** 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/svelte'; + * 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 +export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = + svelteProjectAnnotations; + +/** + * 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/svelte'; + * import { composeStory } from '@storybook/svelte'; + * import Meta, { Primary as PrimaryStory } from './Button.stories'; + * + * const Primary = composeStory(PrimaryStory, Meta); + * + * test('renders primary button with Hello World', () => { + * const { getByText } = render(Primary, { label: '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 +) { + const composedStory = originalComposeStory( + story as StoryAnnotationsOrFn, + // @ts-expect-error Fix this later: Type 'Partial<{ [x: string]: any; }>' is not assignable to type 'Partial>' + componentAnnotations, + projectAnnotations, + INTERNAL_DEFAULT_PROJECT_ANNOTATIONS, + exportsName + ); + + let props = { + storyFn: composedStory, + storyContext: { ...composedStory }, + name: composedStory.storyName, + title: composedStory.id, + showError: () => {}, + }; + + // In Svelte >= 5, we make the props reactive + if (!IS_SVELTE_V4) { + props = createSvelte5Props(props); + } + /** TODO: figure out the situation here. + * Currently, we construct props to render the PreviewRender, a "story wrapper" that + * allows to render the story and its decorators correctly. However, the props + * from the user's component can't be overwritten in tests e.g. + * render(Primary.Component, { label: 'Hello world' }) + * + * In fact, the props that the user has access to are the props for PreviewRender, + * which should be an internal detail instead. + * + * Ideally, we should create a Svelte component with pre-configured props, so users + * can do something like: + * render(Primary) instead of render(Primary.Component, Primary.props) + * */ + const renderable = { + Component: PreviewRender, + props, + }; + Object.assign(renderable, composedStory); + + return renderable as ComposedStory; +} + +/** + * 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/svelte'; + * import { composeStories } from '@storybook/svelte'; + * import * as stories from './Button.stories'; + * + * const { Primary, Secondary } = composeStories(stories); + * + * test('renders primary button with Hello World', () => { + * const { getByText } = render(Primary, { label: '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< + MapToComposed>, + keyof Store_CSFExports + >; +} diff --git a/code/renderers/svelte/src/render.ts b/code/renderers/svelte/src/render.ts index 28552379bf9..2219e21b376 100644 --- a/code/renderers/svelte/src/render.ts +++ b/code/renderers/svelte/src/render.ts @@ -11,11 +11,8 @@ import { createSvelte5Props } from '@storybook/svelte/internal/createSvelte5Prop import { addons } from '@storybook/preview-api'; import * as svelte from 'svelte'; -import { VERSION as SVELTE_VERSION } from 'svelte/compiler'; - import type { SvelteRenderer } from './types'; - -const IS_SVELTE_V4 = Number(SVELTE_VERSION[0]) <= 4; +import { IS_SVELTE_V4 } from './utils'; export function renderToCanvas( renderContext: RenderContext, diff --git a/code/renderers/svelte/src/utils.ts b/code/renderers/svelte/src/utils.ts new file mode 100644 index 00000000000..09bf34cab42 --- /dev/null +++ b/code/renderers/svelte/src/utils.ts @@ -0,0 +1,3 @@ +import { VERSION as SVELTE_VERSION } from 'svelte/compiler'; + +export const IS_SVELTE_V4 = Number(SVELTE_VERSION[0]) <= 4; diff --git a/code/renderers/svelte/vitest.config.ts b/code/renderers/svelte/vitest.config.ts index edd280537ae..93b7542fd9f 100644 --- a/code/renderers/svelte/vitest.config.ts +++ b/code/renderers/svelte/vitest.config.ts @@ -8,6 +8,7 @@ export default defineConfig( test: { environment: 'jsdom', name: __dirname.split(sep).slice(-2).join(posix.sep), + // setupFiles: ['./vitest-setup.ts'], }, plugins: [ // eslint-disable-next-line import/no-unresolved diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index e97bd3fb8d1..05fb23e1c39 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook Vue 3 renderer", "keywords": [ "storybook" @@ -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", @@ -80,7 +86,8 @@ "./src/index.ts", "./src/preset.ts", "./src/entry-preview.ts", - "./src/entry-preview-docs.ts" + "./src/entry-preview-docs.ts", + "./src/playwright.ts" ], "platform": "browser" }, diff --git a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts index 7491a376e07..84e34a189f3 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts @@ -66,6 +66,7 @@ describe('projectAnnotations', () => { const { getByText } = render(WithEnglishText); const buttonElement = getByText('Hello!'); expect(buttonElement).toBeInTheDocument(); + expect(WithEnglishText.parameters?.injected).toBe(true); }); it('renders with custom projectAnnotations via composeStory params', () => { @@ -76,12 +77,6 @@ describe('projectAnnotations', () => { const buttonElement = getByText('Olá!'); expect(buttonElement).toBeInTheDocument(); }); - - it('renders with custom projectAnnotations via setProjectAnnotations', () => { - setProjectAnnotations([{ parameters: { injected: true } }]); - const Story = composeStory(stories.CSF2StoryWithLocale, stories.default); - expect(Story.parameters?.injected).toBe(true); - }); }); describe('CSF3', () => { diff --git a/code/renderers/vue3/src/playwright.ts b/code/renderers/vue3/src/playwright.ts new file mode 100644 index 00000000000..d1538f820e2 --- /dev/null +++ b/code/renderers/vue3/src/playwright.ts @@ -0,0 +1 @@ +export { createPlaywrightTest as createTest } from '@storybook/preview-api'; diff --git a/code/renderers/vue3/src/portable-stories.ts b/code/renderers/vue3/src/portable-stories.ts index 043f15ff46b..562228aa4d4 100644 --- a/code/renderers/vue3/src/portable-stories.ts +++ b/code/renderers/vue3/src/portable-stories.ts @@ -17,6 +17,14 @@ import * as defaultProjectAnnotations from './entry-preview'; import type { Meta } from './public-types'; import type { VueRenderer } from './types'; +type JSXAble = TElement & { + new (...args: any[]): any; + $props: any; +}; +type MapToJSXAble = { + [K in keyof T]: JSXAble; +}; + /** 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`. @@ -87,7 +95,7 @@ export function composeStory( // typing this as newable means TS allows it to be used as a JSX element // TODO: we should do the same for composeStories as well - return renderable as unknown as typeof composedStory & { new (...args: any[]): any }; + return renderable as unknown as JSXAble; } /** @@ -122,8 +130,7 @@ export function composeStories, - keyof Store_CSFExports + return composedStories as unknown as MapToJSXAble< + Omit, keyof Store_CSFExports> >; } diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index 64629f91a4b..2cc9ec4aabc 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook web-components renderer", "keywords": [ "lit", diff --git a/code/ui/.storybook/main.ts b/code/ui/.storybook/main.ts index dd01824a38e..4025715324d 100644 --- a/code/ui/.storybook/main.ts +++ b/code/ui/.storybook/main.ts @@ -52,6 +52,7 @@ const config: StorybookConfig = { '@storybook/addon-interactions', '@storybook/addon-storysource', '@storybook/addon-designs', + '@storybook/addon-a11y', '@chromatic-com/storybook', ], build: { diff --git a/code/ui/blocks/package.json b/code/ui/blocks/package.json index 5b912136af1..c4bcbfbc975 100644 --- a/code/ui/blocks/package.json +++ b/code/ui/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/blocks", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Storybook Doc Blocks", "keywords": [ "storybook" diff --git a/code/ui/blocks/src/components/ArgsTable/ArgControl.tsx b/code/ui/blocks/src/components/ArgsTable/ArgControl.tsx index 6d51fe88d31..ed8ee973d11 100644 --- a/code/ui/blocks/src/components/ArgsTable/ArgControl.tsx +++ b/code/ui/blocks/src/components/ArgsTable/ArgControl.tsx @@ -65,8 +65,8 @@ export const ArgControl: FC = ({ row, arg, updateArgs, isHovere const onBlur = useCallback(() => setFocused(false), []); const onFocus = useCallback(() => setFocused(true), []); - if (!control || control.disabled) { - const canBeSetup = control?.disabled !== true && row?.type?.name !== 'function'; + if (!control || control.disable) { + const canBeSetup = control?.disable !== true && row?.type?.name !== 'function'; return isHovered && canBeSetup ? ( ({ - color: theme.barTextColor, margin: '-4px -12px -4px 0', })); diff --git a/code/ui/blocks/src/components/ArgsTable/Empty.tsx b/code/ui/blocks/src/components/ArgsTable/Empty.tsx index c4269a605f9..2f2c1bb4044 100644 --- a/code/ui/blocks/src/components/ArgsTable/Empty.tsx +++ b/code/ui/blocks/src/components/ArgsTable/Empty.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; import { styled } from '@storybook/theming'; -import { Link } from '@storybook/components'; -import { DocumentIcon, SupportIcon, VideoIcon } from '@storybook/icons'; +import { Link, EmptyTabContent } from '@storybook/components'; +import { DocumentIcon, VideoIcon } from '@storybook/icons'; interface EmptyProps { inAddonPanel?: boolean; @@ -22,27 +22,6 @@ const Wrapper = styled.div<{ inAddonPanel?: boolean }>(({ inAddonPanel, theme }) boxShadow: 'rgba(0, 0, 0, 0.10) 0 1px 3px 0', })); -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,39 +52,47 @@ export const Empty: FC = ({ inAddonPanel }) => { return ( - - - {inAddonPanel + <EmptyTabContent + title={ + inAddonPanel ? 'Interactive story playground' - : "Args table with interactive controls couldn't be auto-generated"} - - - Controls give you an easy to use interface to test your components. Set your story args - and you'll see controls appearing here automatically. - - - - {inAddonPanel && ( + : "Args table with interactive controls couldn't be auto-generated" + } + description={ <> - - Watch 5m video - - - - Read docs - + Controls give you an easy to use interface to test your components. Set your story args + and you'll see controls appearing here automatically. - )} - {!inAddonPanel && ( - - Learn how to set that up - - )} - + } + footer={ + + {inAddonPanel && ( + <> + + Watch 5m video + + + + Read docs + + + )} + {!inAddonPanel && ( + + Learn how to set that up + + )} + + } + /> ); }; diff --git a/code/ui/components/package.json b/code/ui/components/package.json index 31987d45b79..222b8bd7efe 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/components", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook Components", "keywords": [ "storybook" diff --git a/code/ui/components/src/components/Button/Button.tsx b/code/ui/components/src/components/Button/Button.tsx index 37b64a78649..6e77f668b8d 100644 --- a/code/ui/components/src/components/Button/Button.tsx +++ b/code/ui/components/src/components/Button/Button.tsx @@ -170,6 +170,36 @@ const StyledButton = styled('button', { if (variant === 'ghost' && active) return theme.background.hoverable; return 'transparent'; })(), + ...(variant === 'ghost' + ? { + // This is a hack to apply bar styles to the button as soon as it is part of a bar + // It is a temporary solution until we have implemented Theming 2.0. + '.sb-bar &': { + background: (() => { + if (active) return transparentize(0.9, theme.barTextColor); + return 'transparent'; + })(), + color: (() => { + if (active) return theme.barSelectedColor; + return theme.barTextColor; + })(), + '&:hover': { + color: theme.barHoverColor, + background: transparentize(0.86, theme.barHoverColor), + }, + + '&:active': { + color: theme.barSelectedColor, + background: transparentize(0.9, theme.barSelectedColor), + }, + + '&:focus': { + boxShadow: `${rgba(theme.barHoverColor, 1)} 0 0 0 1px inset`, + outline: 'none', + }, + }, + } + : {}), color: (() => { if (variant === 'solid') return theme.color.lightest; if (variant === 'outline') return theme.input.color; diff --git a/code/ui/components/src/components/bar/bar.tsx b/code/ui/components/src/components/bar/bar.tsx index 27c595c65a8..9320e2a9704 100644 --- a/code/ui/components/src/components/bar/bar.tsx +++ b/code/ui/components/src/components/bar/bar.tsx @@ -93,10 +93,10 @@ export interface FlexBarProps extends ComponentProps { backgroundColor?: string; } -export const FlexBar = ({ children, backgroundColor, ...rest }: FlexBarProps) => { +export const FlexBar = ({ children, backgroundColor, className, ...rest }: FlexBarProps) => { const [left, right] = Children.toArray(children); return ( - + {left} diff --git a/code/ui/components/src/components/bar/button.tsx b/code/ui/components/src/components/bar/button.tsx index 68ce83036b5..db838d58133 100644 --- a/code/ui/components/src/components/bar/button.tsx +++ b/code/ui/components/src/components/bar/button.tsx @@ -108,7 +108,7 @@ export const TabButton = styled(ButtonOrLink, { shouldForwardProp: isPropValid } '&:focus': { outline: '0 none', - borderBottomColor: theme.color.secondary, + borderBottomColor: theme.barSelectedColor, }, }), ({ active, textColor, theme }) => @@ -120,6 +120,9 @@ export const TabButton = styled(ButtonOrLink, { shouldForwardProp: isPropValid } : { color: textColor || theme.barTextColor, borderBottomColor: 'transparent', + '&:hover': { + color: theme.barHoverColor, + }, } ); TabButton.displayName = 'TabButton'; diff --git a/code/ui/components/src/components/tabs/EmptyTabContent.stories.tsx b/code/ui/components/src/components/tabs/EmptyTabContent.stories.tsx new file mode 100644 index 00000000000..3ef2da755f3 --- /dev/null +++ b/code/ui/components/src/components/tabs/EmptyTabContent.stories.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { EmptyTabContent } from './EmptyTabContent'; +import { DocumentIcon } from '@storybook/icons'; +import { Link } from '@storybook/components'; +import type { Meta, StoryObj } from '@storybook/react'; + +export default { + component: EmptyTabContent, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +type Story = StoryObj; + +export const OnlyTitle: Story = { + args: { + title: 'Nothing found', + }, +}; + +export const TitleAndDescription: Story = { + args: { + title: 'Nothing found', + description: 'Sorry, there is nothing to display here.', + }, +}; + +export const TitleAndFooter: Story = { + args: { + title: 'Nothing found', + footer: ( + + See the docs + + ), + }, +}; + +export const TitleDescriptionAndFooter: Story = { + args: { + title: 'Nothing found', + description: 'Sorry, there is nothing to display here.', + footer: ( + + See the docs + + ), + }, +}; diff --git a/code/ui/components/src/components/tabs/EmptyTabContent.tsx b/code/ui/components/src/components/tabs/EmptyTabContent.tsx new file mode 100644 index 00000000000..eec65f6183d --- /dev/null +++ b/code/ui/components/src/components/tabs/EmptyTabContent.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { styled } from '@storybook/theming'; + +const Wrapper = styled.div(({ theme }) => ({ + height: '100%', + display: 'flex', + padding: 30, + 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, +})); + +interface Props { + title: React.ReactNode; + description?: React.ReactNode; + footer?: React.ReactNode; +} + +export const EmptyTabContent = ({ title, description, footer }: Props) => { + return ( + + + {title} + {description && {description}} + + {footer} + + ); +}; diff --git a/code/ui/components/src/components/tabs/tabs.hooks.tsx b/code/ui/components/src/components/tabs/tabs.hooks.tsx index f5fbd7272fe..d932b974b81 100644 --- a/code/ui/components/src/components/tabs/tabs.hooks.tsx +++ b/code/ui/components/src/components/tabs/tabs.hooks.tsx @@ -22,11 +22,14 @@ const CollapseIcon = styled.span<{ isActive: boolean }>(({ theme, isActive }) => const AddonButton = styled(TabButton)<{ preActive: boolean }>(({ active, theme, preActive }) => { return ` - color: ${preActive || active ? theme.color.secondary : theme.color.mediumdark}; + color: ${preActive || active ? theme.barSelectedColor : theme.barTextColor}; + .addon-collapsible-icon { + color: ${preActive || active ? theme.barSelectedColor : theme.barTextColor}; + } &:hover { - color: ${theme.color.secondary}; + color: ${theme.barHoverColor}; .addon-collapsible-icon { - color: ${theme.color.secondary}; + color: ${theme.barHoverColor}; } } `; diff --git a/code/ui/components/src/components/tabs/tabs.stories.tsx b/code/ui/components/src/components/tabs/tabs.stories.tsx index 46a332a87f1..a3c40fd8a9d 100644 --- a/code/ui/components/src/components/tabs/tabs.stories.tsx +++ b/code/ui/components/src/components/tabs/tabs.stories.tsx @@ -1,9 +1,9 @@ import { expect } from '@storybook/test'; -import React, { Fragment } from 'react'; +import React from 'react'; import { action } from '@storybook/addon-actions'; import type { Meta, StoryObj } from '@storybook/react'; import { within, fireEvent, waitFor, screen, userEvent, findByText } from '@storybook/test'; -import { CPUIcon, MemoryIcon } from '@storybook/icons'; +import { BottomBarIcon, CloseIcon } from '@storybook/icons'; import { Tabs, TabsState, TabWrapper } from './tabs'; import type { ChildrenList } from './tabs.helpers'; import { IconButton } from '../IconButton/IconButton'; @@ -260,7 +260,27 @@ export const StatelessBordered = { ), } satisfies Story; +const AddonTools = () => ( +
+ + + + + + +
+); + export const StatelessWithTools = { + args: { + tools: , + }, render: (args) => ( - - - - - - - - } {...args} > {content} ), -} satisfies Story; +} satisfies StoryObj; export const StatelessAbsolute = { parameters: { @@ -303,7 +313,7 @@ export const StatelessAbsolute = { {content} ), -} satisfies Story; +} satisfies StoryObj; export const StatelessAbsoluteBordered = { parameters: { @@ -323,9 +333,13 @@ export const StatelessAbsoluteBordered = { {content} ), -} satisfies Story; +} satisfies StoryObj; -export const StatelessEmpty = { +export const StatelessEmptyWithTools = { + args: { + ...StatelessWithTools.args, + showToolsWhenEmpty: true, + }, parameters: { layout: 'fullscreen', }, @@ -340,4 +354,25 @@ export const StatelessEmpty = { {...args} /> ), -} satisfies Story; +} satisfies StoryObj; + +export const StatelessWithCustomEmpty = { + args: { + ...StatelessEmptyWithTools.args, + emptyState:
I am custom!
, + }, + parameters: { + layout: 'fullscreen', + }, + render: (args) => ( + + ), +} satisfies StoryObj; diff --git a/code/ui/components/src/components/tabs/tabs.tsx b/code/ui/components/src/components/tabs/tabs.tsx index 0e3484eab4b..5b0cbb2b561 100644 --- a/code/ui/components/src/components/tabs/tabs.tsx +++ b/code/ui/components/src/components/tabs/tabs.tsx @@ -1,14 +1,14 @@ import type { FC, PropsWithChildren, ReactElement, ReactNode, SyntheticEvent } from 'react'; -import React, { useMemo, Component, Fragment, memo } from 'react'; +import React, { useMemo, Component, memo } from 'react'; import { styled } from '@storybook/theming'; import { sanitize } from '@storybook/csf'; import type { Addon_RenderOptions } from '@storybook/types'; -import { Placeholder } from '../placeholder/placeholder'; import { TabButton } from '../bar/button'; import { FlexBar } from '../bar/bar'; import { childrenToList, VisuallyHidden } from './tabs.helpers'; import { useList } from './tabs.hooks'; +import { EmptyTabContent } from './EmptyTabContent'; const ignoreSsrWarning = '/* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */'; @@ -119,6 +119,8 @@ export interface TabsProps { }>[]; id?: string; tools?: ReactNode; + showToolsWhenEmpty?: boolean; + emptyState?: ReactNode; selected?: string; actions?: { onSelect: (id: string) => void; @@ -140,6 +142,8 @@ export const Tabs: FC = memo( backgroundColor, id: htmlId, menuName, + emptyState, + showToolsWhenEmpty, }) => { const idList = childrenToList(children) .map((i) => i.id) @@ -157,7 +161,13 @@ export const Tabs: FC = memo( const { visibleList, tabBarRef, tabRefs, AddonTab } = useList(list); - return list.length ? ( + const EmptyContent = emptyState ?? ; + + if (!showToolsWhenEmpty && list.length === 0) { + return EmptyContent; + } + + return ( @@ -190,15 +200,13 @@ export const Tabs: FC = memo( {tools} - {list.map(({ id, active, render }) => { - return React.createElement(render, { key: id, active }, null); - })} + {list.length + ? list.map(({ id, active, render }) => { + return React.createElement(render, { key: id, active }, null); + }) + : EmptyContent} - ) : ( - - Nothing found - ); } ); diff --git a/code/ui/components/src/index.ts b/code/ui/components/src/index.ts index f43d08b5d8f..ffe4a08d699 100644 --- a/code/ui/components/src/index.ts +++ b/code/ui/components/src/index.ts @@ -66,6 +66,7 @@ export { default as ListItem } from './components/tooltip/ListItem'; // Toolbar and subcomponents export { Tabs, TabsState, TabBar, TabWrapper } from './components/tabs/tabs'; +export { EmptyTabContent } from './components/tabs/EmptyTabContent'; export { IconButtonSkeleton, TabButton } from './components/bar/button'; export { Separator, interleaveSeparators } from './components/bar/separator'; export { Bar, FlexBar } from './components/bar/bar'; diff --git a/code/ui/manager/package.json b/code/ui/manager/package.json index 6fb2e772f09..4dd2148860a 100644 --- a/code/ui/manager/package.json +++ b/code/ui/manager/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/manager", - "version": "8.1.0-alpha.1", + "version": "8.1.0-alpha.3", "description": "Core Storybook UI", "keywords": [ "storybook" diff --git a/code/ui/manager/src/components/mobile/navigation/MobileNavigation.tsx b/code/ui/manager/src/components/mobile/navigation/MobileNavigation.tsx index 47b7587019f..9ecee759fb0 100644 --- a/code/ui/manager/src/components/mobile/navigation/MobileNavigation.tsx +++ b/code/ui/manager/src/components/mobile/navigation/MobileNavigation.tsx @@ -45,7 +45,7 @@ export const MobileNavigation: FC = ({ menu, panel, showP {isMobilePanelOpen ? ( {panel} ) : ( -