diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index 5cdc1ba44fcf..91be3acf6f92 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version-file: '.nvmrc' + node-version-file: ".nvmrc" - name: Cache dependencies uses: actions/cache@v3 @@ -89,7 +89,7 @@ jobs: yarn release:pick-patches - name: Cancel when no patches to pick - if: steps.pick-patches.outputs.pr-count == '0' && steps.pick-patches.outputs.pr-count != null + if: steps.pick-patches.outputs.no-patch-prs == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # From https://stackoverflow.com/a/75809743 @@ -123,7 +123,7 @@ jobs: run: | yarn release:write-changelog ${{ steps.versions.outputs.next }} --unpicked-patches --verbose - - name: 'Commit changes to branch: version-patch-from-${{ steps.versions.outputs.current }}' + - name: "Commit changes to branch: version-patch-from-${{ steps.versions.outputs.current }}" working-directory: . run: | git config --global user.name 'storybook-bot' @@ -185,4 +185,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.versions.outputs.current }} to v${{ steps.versions.outputs.next }} (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.versions.outputs.current }} to v${{ steps.versions.outputs.next }} (triggered by ${{ github.triggering_actor }}) failed! See run at: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a25029e92c..5c0d554b6714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 7.6.15 + +- Interaction: Make sure that adding spies doesn't cause infinite loops with self referencing args [#26019](https://github.com/storybookjs/storybook/pull/26019), thanks @kasperpeulen! + +## 7.6.14 + +- Core: Fix boolean `true` args in URL getting ignored - [#25950](https://github.com/storybookjs/storybook/pull/25950), thanks [@JReinhold](https://github.com/JReinhold)! + +## 7.6.13 + +- Next.js: Fix frameworkOptions resolution - [#25907](https://github.com/storybookjs/storybook/pull/25907), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- React Native: Fix init fails when package is already installed - [#25908](https://github.com/storybookjs/storybook/pull/25908), thanks [@dannyhw](https://github.com/dannyhw)! +- React Native: Remove watcher from init - [#25895](https://github.com/storybookjs/storybook/pull/25895), thanks [@dannyhw](https://github.com/dannyhw)! +- Webpack: Update StorybookConfig import in core-webpack types.ts - [#25740](https://github.com/storybookjs/storybook/pull/25740), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! + ## 7.6.12 - CLI: Fix `upgrade` detecting the wrong version of existing Storybooks - [#25752](https://github.com/storybookjs/storybook/pull/25752), thanks [@JReinhold](https://github.com/JReinhold)! diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 840c5ee3edb8..620e9307a8a7 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,31 @@ +## 8.0.0-beta.2 + +- Core: Fix boolean `true` args in URL getting ignored - [#25950](https://github.com/storybookjs/storybook/pull/25950), thanks [@JReinhold](https://github.com/JReinhold)! +- Core: Move @types packages to dev deps in core-common - [#25387](https://github.com/storybookjs/storybook/pull/25387), thanks [@kyletsang](https://github.com/kyletsang)! +- Maintenance: Rename testing-utils paths to portable-stories - [#25888](https://github.com/storybookjs/storybook/pull/25888), thanks [@yannbf](https://github.com/yannbf)! +- Portable stories: Pass story context to the play function of a composed story - [#25943](https://github.com/storybookjs/storybook/pull/25943), thanks [@yannbf](https://github.com/yannbf)! +- UI: Fix `display=true` warning in console - [#25951](https://github.com/storybookjs/storybook/pull/25951), thanks [@JReinhold](https://github.com/JReinhold)! +- UI: Update deprecated Icons with the new @storybook/icons in addons - [#25822](https://github.com/storybookjs/storybook/pull/25822), thanks [@cdedreuille](https://github.com/cdedreuille)! +- Vite: Add a `rollup-plugin-webpack-stats` to allow stats from preview builds - [#25923](https://github.com/storybookjs/storybook/pull/25923), thanks [@tmeasday](https://github.com/tmeasday)! + +## 8.0.0-beta.1 + +- Addon-docs: Fix MDX components not applied in Vite and theme loading twice - [#25925](https://github.com/storybookjs/storybook/pull/25925), thanks [@JReinhold](https://github.com/JReinhold)! +- Core: Fix React peer dependency warnings - [#25926](https://github.com/storybookjs/storybook/pull/25926), thanks [@JReinhold](https://github.com/JReinhold)! +- Core: Remove CSF batching, as it isn't required any more - [#25872](https://github.com/storybookjs/storybook/pull/25872), thanks [@tmeasday](https://github.com/tmeasday)! +- Next.js: Fix frameworkOptions resolution - [#25907](https://github.com/storybookjs/storybook/pull/25907), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- React Native: Fix init fails when package is already installed - [#25908](https://github.com/storybookjs/storybook/pull/25908), thanks [@dannyhw](https://github.com/dannyhw)! +- React Native: Remove watcher from init - [#25895](https://github.com/storybookjs/storybook/pull/25895), thanks [@dannyhw](https://github.com/dannyhw)! +- Test: Fix jest-dom matcher type imports - [#25869](https://github.com/storybookjs/storybook/pull/25869), thanks [@alitas](https://github.com/alitas)! +- UI: Fix overlapping on the ref button in the sidebar - [#25914](https://github.com/storybookjs/storybook/pull/25914), thanks [@cdedreuille](https://github.com/cdedreuille)! +- UI: Remove keyboard shortcut hint in search bar on mobile viewports - [#25866](https://github.com/storybookjs/storybook/pull/25866), thanks [@tusharwebd](https://github.com/tusharwebd)! + +## 8.0.0-beta.0 + +- CLI: Add Visual Tests addon to `init` - [#25850](https://github.com/storybookjs/storybook/pull/25850), thanks [@shilman](https://github.com/shilman)! +- CLI: Upgrade boxen library - [#25713](https://github.com/storybookjs/storybook/pull/25713), thanks [@yannbf](https://github.com/yannbf)! +- UI: Fix custom tabs not showing in the manager - [#25792](https://github.com/storybookjs/storybook/pull/25792), thanks [@ndelangen](https://github.com/ndelangen)! + ## 8.0.0-alpha.17 - CLI: Fix add command for non monorepo deps - [#25791](https://github.com/storybookjs/storybook/pull/25791), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! diff --git a/MIGRATION.md b/MIGRATION.md index bc3727aa4528..391c868cfa5b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,12 +1,20 @@

Migration

- [From version 7.x to 8.0.0](#from-version-7x-to-800) + - [Type change in `composeStories` API](#type-change-in-composestories-api) - [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter) - [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed) - [Manager addons are now rendered with React 18](#manager-addons-are-now-rendered-with-react-18) - [Removal of `storiesOf`-API](#removal-of-storiesof-api) - [Removed deprecated shim packages](#removed-deprecated-shim-packages) - [Framework-specific Vite plugins have to be explicitly added](#framework-specific-vite-plugins-have-to-be-explicitly-added) + - [For React:](#for-react) + - [For Vue:](#for-vue) + - [For Svelte (without Sveltekit):](#for-svelte-without-sveltekit) + - [For Preact:](#for-preact) + - [For Solid:](#for-solid) + - [For Qwik:](#for-qwik) + - [TurboSnap Vite plugin is no longer needed](#turbosnap-vite-plugin-is-no-longer-needed) - [Implicit actions can not be used during rendering (for example in the play function)](#implicit-actions-can-not-be-used-during-rendering-for-example-in-the-play-function) - [MDX related changes](#mdx-related-changes) - [MDX is upgraded to v3](#mdx-is-upgraded-to-v3) @@ -28,6 +36,7 @@ - [Removed stories.json](#removed-storiesjson) - [Removed `sb babelrc` command](#removed-sb-babelrc-command) - [Changed interfaces for `@storybook/router` components](#changed-interfaces-for-storybookrouter-components) + - [Extract no longer batches](#extract-no-longer-batches) - [Framework-specific changes](#framework-specific-changes) - [React](#react) - [`react-docgen` component analysis by default](#react-docgen-component-analysis-by-default) @@ -35,6 +44,8 @@ - [Require Next.js 13.5 and up](#require-nextjs-135-and-up) - [Automatic SWC mode detection](#automatic-swc-mode-detection) - [RSC config moved to React renderer](#rsc-config-moved-to-react-renderer) + - [Vue](#vue) + - [Require Vue 3 and up](#require-vue-3-and-up) - [Angular](#angular) - [Require Angular 15 and up](#require-angular-15-and-up) - [Svelte](#svelte) @@ -388,6 +399,23 @@ ## From version 7.x to 8.0.0 +### Type change in `composeStories` API + +There is a TypeScript type change in the `play` function returned from `composeStories` or `composeStory` in `@storybook/react` or `@storybook/vue3`, where before it was always defined, now it is potentially undefined. This means that you might have to make a small change in your code, such as: + +```ts +const { Primary } = composeStories(stories) + +// before +await Primary.play(...) + +// after +await Primary.play?.(...) // if you don't care whether the play function exists +await Primary.play!(...) // if you want a runtime error when the play function does not exist +``` + +There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate. + ### Tab addons are now routed to a query parameter The URL of a tab used to be: `http://localhost:6006/?path=/my-addon-tab/my-story`. @@ -447,16 +475,78 @@ This section explains the rationale, and the required changed you might have to In Storybook 7, we would automatically add frameworks-specific Vite plugins, e.g. `@vitejs/plugin-react` if not installed. In Storybook 8 those plugins have to be added explicitly in the user's `vite.config.ts`: +#### For React: + ```ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -// https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], }); ``` +#### For Vue: + +```ts +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; + +export default defineConfig({ + plugins: [vue()], +}); +``` + +#### For Svelte (without Sveltekit): + +```ts +import { defineConfig } from "vite"; +import svelte from "@sveltejs/vite-plugin-svelte"; + +export default defineConfig({ + plugins: [svelte()], +}); +``` + +#### For Preact: + +```ts +import { defineConfig } from "vite"; +import preact from "@preact/preset-vite"; + +export default defineConfig({ + plugins: [preact()], +}); +``` + +#### For Solid: + +```ts +import { defineConfig } from "vite"; +import solid from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [solid()], +}); +``` + +#### For Qwik: + +```ts +import { defineConfig } from "vite"; +import qwik from "vite-plugin-qwik"; + +export default defineConfig({ + plugins: [qwik()], +}); +``` + +### TurboSnap Vite plugin is no longer needed + +At least in build mode, `builder-vite` now supports the `--webpack-stats-json` flag and will output `preview-stats.json`. + +This means https://github.com/IanVS/vite-plugin-turbosnap is no longer necessary, and duplicative, and the plugin will automatically be removed if found. + ### Implicit actions can not be used during rendering (for example in the play function) In Storybook 7, we inferred if the component accepts any action props, @@ -710,6 +800,10 @@ The reasoning behind is to condense and provide some clarity to what's happened The `hideOnly` prop has been removed from the `` component in `@storybook/router`. If needed this can be implemented manually with the `` component. +#### Extract no longer batches + +`Preview.extract()` no longer loads CSF files in batches. This was a workaround for resource limitations that slowed down extract. This shouldn't affect behaviour. + ### Framework-specific changes #### React @@ -749,6 +843,12 @@ Storybook 7.6 introduced a new feature flag, `experimentalNextRSC`, to enable Re These flags have been renamed to `experimentalRSC` and `react.rsc`, respectively. This is a breaking change to accommodate RSC support in other, non-Next.js frameworks. For now, `@storybook/nextjs` is the only framework that supports it, and does so experimentally. +#### Vue + +##### Require Vue 3 and up + +Starting in 8.0, Storybook requires Vue 3 and up. + #### Angular ##### Require Angular 15 and up @@ -1480,7 +1580,7 @@ Storybook uses `react` in a variety of docs-related packages. In the past, we've To upgrade manually, add any version of `react` and `react-dom` as devDependencies using your package manager of choice, e.g. ``` -npm add react react-dom --dev +npm add react react-dom --save-dev ``` #### start-storybook / build-storybook binaries removed diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index d415ec8e1740..ae09048b7f43 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Test component compliance with web accessibility standards", "keywords": [ "a11y", @@ -60,7 +60,7 @@ "@storybook/client-logger": "workspace:*", "@storybook/components": "workspace:*", "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.2.3", + "@storybook/icons": "^1.2.5", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", "@storybook/theming": "workspace:*", diff --git a/code/addons/a11y/src/components/Report/Item.tsx b/code/addons/a11y/src/components/Report/Item.tsx index 2d34ee58d745..1830a10771fc 100644 --- a/code/addons/a11y/src/components/Report/Item.tsx +++ b/code/addons/a11y/src/components/Report/Item.tsx @@ -1,7 +1,6 @@ import React, { Fragment, useState } from 'react'; import { styled } from '@storybook/theming'; -import { Icons } from '@storybook/components'; import type { Result } from 'axe-core'; import { Info } from './Info'; @@ -11,6 +10,7 @@ import { Tags } from './Tags'; import type { RuleType } from '../A11YPanel'; import HighlightToggle from './HighlightToggle'; +import { ChevronSmallDownIcon } from '@storybook/icons'; const Wrapper = styled.div(({ theme }) => ({ display: 'flex', @@ -21,10 +21,7 @@ const Wrapper = styled.div(({ theme }) => ({ }, })); -const Icon = styled(Icons)({ - height: 10, - width: 10, - minWidth: 10, +const Icon = styled(ChevronSmallDownIcon)({ marginRight: 10, transition: 'transform 0.1s ease-in-out', verticalAlign: 'inherit', @@ -75,7 +72,6 @@ export const Item = (props: ItemProps) => { onToggle(!open)} role="button"> { const { mdxPlugin } = await import('./plugins/mdx-plugin'); // Use the resolvedReact preset to alias react and react-dom to either the users version or the version shipped with addon-docs - const { react, reactDom } = await getResolvedReact(options); + const { react, reactDom, mdx } = await getResolvedReact(options); - const reactAliasPlugin = { - name: 'storybook:react-alias', + const packageDeduplicationPlugin = { + name: 'storybook:package-deduplication', enforce: 'pre', config: () => ({ resolve: { alias: { react, 'react-dom': reactDom, + '@mdx-js/react': mdx, + /** + * The following aliases are used to ensure a single instance of these packages are used in situations where they are duplicated + * The packages will be duplicated by the package manager when the user has react installed with another version than 18.2.0 + */ + '@storybook/theming': dirname(require.resolve('@storybook/theming')), + '@storybook/components': dirname(require.resolve('@storybook/components')), + '@storybook/blocks': dirname(require.resolve('@storybook/blocks')), }, }, }), @@ -155,7 +163,7 @@ export const viteFinal = async (config: any, options: Options) => { // add alias plugin early to ensure any other plugins that also add the aliases will override this // eg. the preact vite plugin adds its own aliases - plugins.unshift(reactAliasPlugin); + plugins.unshift(packageDeduplicationPlugin); plugins.push(mdxPlugin(options)); return config; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index b4cbc9f7ca5f..6ff40fb86d80 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-essentials", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Curated addons to bring out the best of Storybook", "keywords": [ "addon", diff --git a/code/addons/gfm/package.json b/code/addons/gfm/package.json index 7a8b2ddbc899..b80b72c7fc93 100644 --- a/code/addons/gfm/package.json +++ b/code/addons/gfm/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-mdx-gfm", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "GitHub Flavored Markdown in Storybook", "keywords": [ "addon", diff --git a/code/addons/highlight/package.json b/code/addons/highlight/package.json index 0948688d48b8..00aaed7b475f 100644 --- a/code/addons/highlight/package.json +++ b/code/addons/highlight/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-highlight", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Highlight DOM nodes within your stories", "keywords": [ "storybook-addons", diff --git a/code/addons/interactions/package.json b/code/addons/interactions/package.json index 978de9bf4c24..0cf9b95103fd 100644 --- a/code/addons/interactions/package.json +++ b/code/addons/interactions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-interactions", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Automate, test and debug user interactions", "keywords": [ "storybook-addons", @@ -50,7 +50,6 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.2.3", "@storybook/types": "workspace:*", "jest-mock": "^27.0.6", "polished": "^4.2.2", @@ -62,6 +61,7 @@ "@storybook/components": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/core-events": "workspace:*", + "@storybook/icons": "^1.2.5", "@storybook/instrumenter": "workspace:*", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", diff --git a/code/addons/interactions/src/components/List.tsx b/code/addons/interactions/src/components/List.tsx index 8c420d37102a..75db1d21d99e 100644 --- a/code/addons/interactions/src/components/List.tsx +++ b/code/addons/interactions/src/components/List.tsx @@ -1,6 +1,6 @@ import React, { Fragment, useState } from 'react'; import { styled, themes, convert } from '@storybook/theming'; -import { Icons, type IconsProps } from '@storybook/components'; +import { ChevronSmallDownIcon } from '@storybook/icons'; const ListWrapper = styled.ul({ listStyle: 'none', @@ -18,10 +18,7 @@ const Wrapper = styled.div({ }, }); -const Icon = styled(Icons)({ - height: 10, - width: 10, - minWidth: 10, +const Icon = styled(ChevronSmallDownIcon)({ color: convert(themes.light).textMutedColor, marginRight: 10, transition: 'transform 0.1s ease-in-out', @@ -68,7 +65,6 @@ export const ListItem: React.FC = ({ item }) => { onToggle(!open)} role="button"> (({ theme, status }) => { - const color = { - [CallStates.DONE]: theme.color.positive, - [CallStates.ERROR]: theme.color.negative, - [CallStates.ACTIVE]: theme.color.secondary, - [CallStates.WAITING]: transparentize(0.5, gray[500]), - }[status]; - return { - width: status === CallStates.WAITING ? 6 : 12, - height: status === CallStates.WAITING ? 6 : 12, - color, - justifySelf: 'center', - }; +const WarningContainer = styled.div({ + width: 14, + height: 14, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }); -export const StatusIcon: React.FC = ({ status, className }) => { - const icon = { - [CallStates.DONE]: 'check', - [CallStates.ERROR]: 'stopalt', - [CallStates.ACTIVE]: 'play', - [CallStates.WAITING]: 'circle', - }[status] as IconsProps['icon']; - return ( - - ); +export const StatusIcon: React.FC = ({ status }) => { + const theme = useTheme(); + + switch (status) { + case CallStates.DONE: { + return ; + } + case CallStates.ERROR: { + return ; + } + case CallStates.ACTIVE: { + return ; + } + case CallStates.WAITING: { + return ( + + + + ); + } + default: { + return null; + } + } }; diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index f59367de098d..dc65c436571a 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-jest", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "React storybook addon that show component jest report", "keywords": [ "addon", @@ -62,6 +62,7 @@ "@storybook/client-logger": "workspace:*", "@storybook/components": "workspace:*", "@storybook/core-events": "workspace:*", + "@storybook/icons": "^1.2.5", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", "@storybook/theming": "workspace:*", diff --git a/code/addons/jest/src/components/Result.tsx b/code/addons/jest/src/components/Result.tsx index d50fa6f34a1a..61bacd9e6af7 100644 --- a/code/addons/jest/src/components/Result.tsx +++ b/code/addons/jest/src/components/Result.tsx @@ -1,8 +1,8 @@ import React, { Fragment, useState } from 'react'; import { styled, themes, convert } from '@storybook/theming'; -import { Icons } from '@storybook/components'; // eslint-disable-next-line import/no-named-as-default import Message from './Message'; +import { ChevronSmallDownIcon } from '@storybook/icons'; const Wrapper = styled.div<{ status: string }>(({ theme, status }) => ({ display: 'flex', @@ -30,10 +30,7 @@ const HeaderBar = styled.div<{ status: string }>(({ theme, status }) => ({ }, })); -const Icon = styled(Icons)(({ theme }) => ({ - height: 10, - width: 10, - minWidth: 10, +const Icon = styled(ChevronSmallDownIcon)(({ theme }) => ({ color: theme.textMutedColor, marginRight: 10, transition: 'transform 0.1s ease-in-out', @@ -66,7 +63,6 @@ export function Result(props: ResultProps) { {status === `failed` ? ( void; } +// We can't remove the Icons component just yet because there's no way for now to import icons +// in the preview directly. Before having a better solution, we are going to keep the Icons component +// for now and remove the deprecated warning. + export const ToolbarMenuButton: FC = ({ active, title, @@ -19,7 +23,7 @@ export const ToolbarMenuButton: FC = ({ }) => { return ( - {icon && } + {icon && } {title ? `\xa0${title}` : null} ); diff --git a/code/addons/viewport/package.json b/code/addons/viewport/package.json index 03315ce7d262..3f8ed938ff27 100644 --- a/code/addons/viewport/package.json +++ b/code/addons/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-viewport", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Build responsive components by adjusting Storybook’s viewport size and orientation", "keywords": [ "addon", @@ -55,7 +55,7 @@ "@storybook/components": "workspace:*", "@storybook/core-events": "workspace:*", "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.2.3", + "@storybook/icons": "^1.2.5", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", "@storybook/theming": "workspace:*", diff --git a/code/builders/builder-manager/package.json b/code/builders/builder-manager/package.json index d085f2c6e6ed..bac171608295 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook manager builder", "keywords": [ "storybook" diff --git a/code/builders/builder-vite/input/iframe.html b/code/builders/builder-vite/input/iframe.html index dd976d6c4ab4..7720ef6b9482 100644 --- a/code/builders/builder-vite/input/iframe.html +++ b/code/builders/builder-vite/input/iframe.html @@ -1,44 +1,66 @@ - + + + + Storybook + - - - Storybook - + + + + + - - - - - + + + - // We do this so that "module && module.hot" etc. in Storybook source code - // doesn't fail (it will simply be disabled) - window.module = undefined; - window.global = window; - - - - - - -
-
- - - - - \ No newline at end of file + + +
+
+ + + + diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index a9f7f1744fc3..5e5a9277d64b 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "A plugin to run and build Storybooks with Vite", "homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme", "bugs": { @@ -57,7 +57,8 @@ "express": "^4.17.3", "find-cache-dir": "^3.0.0", "fs-extra": "^11.1.0", - "magic-string": "^0.30.0" + "magic-string": "^0.30.0", + "ts-dedent": "^2.0.0" }, "devDependencies": { "@types/express": "^4.17.13", diff --git a/code/builders/builder-vite/src/build.ts b/code/builders/builder-vite/src/build.ts index ccf9f9476a5f..ebf4f030e8b8 100644 --- a/code/builders/builder-vite/src/build.ts +++ b/code/builders/builder-vite/src/build.ts @@ -1,7 +1,17 @@ import type { Options } from '@storybook/types'; -import { commonConfig } from './vite-config'; +import { logger } from '@storybook/node-logger'; +import dedent from 'ts-dedent'; +import { commonConfig } from './vite-config'; import { sanitizeEnvVars } from './envs'; +import type { WebpackStatsPlugin } from './plugins'; +import type { InlineConfig } from 'vite'; +import { hasVitePlugins } from './utils/has-vite-plugins'; +import { withoutVitePlugins } from './utils/without-vite-plugins'; + +function findPlugin(config: InlineConfig, name: string) { + return config.plugins?.find((p) => p && 'name' in p && p.name === name); +} export async function build(options: Options) { const { build: viteBuild, mergeConfig } = await import('vite'); @@ -28,5 +38,21 @@ export async function build(options: Options) { }).build; const finalConfig = await presets.apply('viteFinal', config, options); + + const turbosnapPluginName = 'rollup-plugin-turbosnap'; + const hasTurbosnapPlugin = + finalConfig.plugins && hasVitePlugins(finalConfig.plugins, [turbosnapPluginName]); + if (hasTurbosnapPlugin) { + logger.warn(dedent`Found '${turbosnapPluginName}' which is now included by default in Storybook 8. + Removing from your plugins list. Ensure you pass \`--webpack-stats-json\` to generate stats. + + For more information, see https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#turbosnap-vite-plugin-is-no-longer-needed`); + + finalConfig.plugins = await withoutVitePlugins(finalConfig.plugins, [turbosnapPluginName]); + } + await viteBuild(await sanitizeEnvVars(options, finalConfig)); + + const statsPlugin = findPlugin(finalConfig, 'rollup-plugin-webpack-stats') as WebpackStatsPlugin; + return statsPlugin?.storybookGetStats(); } diff --git a/code/builders/builder-vite/src/plugins/index.ts b/code/builders/builder-vite/src/plugins/index.ts index 68e540908dc6..bc72dc8755d5 100644 --- a/code/builders/builder-vite/src/plugins/index.ts +++ b/code/builders/builder-vite/src/plugins/index.ts @@ -3,3 +3,4 @@ export * from './strip-story-hmr-boundaries'; export * from './code-generator-plugin'; export * from './csf-plugin'; export * from './external-globals-plugin'; +export * from './webpack-stats-plugin'; diff --git a/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts new file mode 100644 index 000000000000..affb130c07e1 --- /dev/null +++ b/code/builders/builder-vite/src/plugins/webpack-stats-plugin.ts @@ -0,0 +1,116 @@ +// This plugin is a direct port of https://github.com/IanVS/vite-plugin-turbosnap + +import type { BuilderStats } from '@storybook/types'; +import path from 'path'; +import type { Plugin } from 'vite'; + +/* + * Reason, Module are copied from chromatic types + * https://github.com/chromaui/chromatic-cli/blob/145a5e295dde21042e96396c7e004f250d842182/bin-src/types.ts#L265-L276 + */ +interface Reason { + moduleName: string; +} +interface Module { + id: string | number; + name: string; + modules?: Array>; + reasons?: Reason[]; +} + +type WebpackStatsPluginOptions = { + workingDir: string; +}; + +/** + * Strips off query params added by rollup/vite to ids, to make paths compatible for comparison with git. + */ +function stripQueryParams(filePath: string): string { + return filePath.split('?')[0]; +} + +/** + * We only care about user code, not node_modules, vite files, or (most) virtual files. + */ +function isUserCode(moduleName: string) { + return Boolean( + moduleName && + !moduleName.startsWith('vite/') && + !moduleName.startsWith('\x00') && + !moduleName.startsWith('\u0000') && + moduleName !== 'react/jsx-runtime' && + !moduleName.match(/node_modules\//) + ); +} + +export type WebpackStatsPlugin = Plugin & { storybookGetStats: () => BuilderStats }; + +export function pluginWebpackStats({ workingDir }: WebpackStatsPluginOptions): WebpackStatsPlugin { + /** + * Convert an absolute path name to a path relative to the vite root, with a starting `./` + */ + function normalize(filename: string) { + // Do not try to resolve virtual files + if (filename.startsWith('/virtual:')) { + return filename; + } + // Otherwise, we need them in the format `./path/to/file.js`. + else { + const relativePath = path.relative(workingDir, stripQueryParams(filename)); + // This seems hacky, got to be a better way to add a `./` to the start of a path. + return `./${relativePath}`; + } + } + + /** + * Helper to create Reason objects out of a list of string paths + */ + function createReasons(importers?: readonly string[]): Reason[] { + return (importers || []).map((i) => ({ moduleName: normalize(i) })); + } + + /** + * Helper function to build a `Module` given a filename and list of files that import it + */ + function createStatsMapModule(filename: string, importers?: readonly string[]): Module { + return { + id: filename, + name: filename, + reasons: createReasons(importers), + }; + } + + const statsMap = new Map(); + + return { + name: 'storybook:rollup-plugin-webpack-stats', + // We want this to run after the vite build plugins (https://vitejs.dev/guide/api-plugin.html#plugin-ordering) + enforce: 'post', + moduleParsed: function (mod) { + if (isUserCode(mod.id)) { + mod.importedIds + .concat(mod.dynamicallyImportedIds) + .filter((name) => isUserCode(name)) + .forEach((depIdUnsafe) => { + const depId = normalize(depIdUnsafe); + if (statsMap.has(depId)) { + const m = statsMap.get(depId); + if (m) { + m.reasons = (m.reasons ?? []) + .concat(createReasons([mod.id])) + .filter((r) => r.moduleName !== depId); + statsMap.set(depId, m); + } + } else { + statsMap.set(depId, createStatsMapModule(depId, [mod.id])); + } + }); + } + }, + + storybookGetStats() { + const stats = { modules: Array.from(statsMap.values()) }; + return { ...stats, toJson: () => stats }; + }, + }; +} diff --git a/code/builders/builder-vite/src/vite-config.ts b/code/builders/builder-vite/src/vite-config.ts index 2383ebd6e5e5..4a1c2fb3ee44 100644 --- a/code/builders/builder-vite/src/vite-config.ts +++ b/code/builders/builder-vite/src/vite-config.ts @@ -20,6 +20,7 @@ import { injectExportOrderPlugin, stripStoryHMRBoundary, externalGlobalsPlugin, + pluginWebpackStats, } from './plugins'; import type { BuilderOptions } from './types'; @@ -112,6 +113,7 @@ export async function pluginConfig(options: Options) { }, }, await externalGlobalsPlugin(externals), + pluginWebpackStats({ workingDir: process.cwd() }), ] as PluginOption[]; // TODO: framework doesn't exist, should move into framework when/if built diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 65d8c4953cc9..ebcccddcdeb6 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 305acd84966c..a5cd680014a7 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.", "keywords": [ "storybook", diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index 73945a69d1c5..8b1bbcbc7208 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 1f0c2a5ce246..78ae2ded1c53 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 f40c13c4accc..d800e8bc0bc0 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index 94158fce5b02..87019027ca9a 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -1,10 +1,10 @@ # Storybook for Next.js -See [documentation](https://storybook.js.org/docs/get-started/nextjs) for installation instructions, usage examples, api, and more. +See [documentation](https://storybook.js.org/docs/8.0/get-started/nextjs) for installation instructions, usage examples, APIs, and more. ## Acknowledgements This framework borrows heavily from these Storybook addons: - [storybook-addon-next](https://github.com/RyanClementsHax/storybook-addon-next) by [RyanClementsHax](https://github.com/RyanClementsHax/) -- [storybook-addon-next-router](https://github.com/lifeiscontent/storybook-addon-next-router) by [lifeiscontent](https://github.com/lifeiscontent) \ No newline at end of file +- [storybook-addon-next-router](https://github.com/lifeiscontent/storybook-addon-next-router) by [lifeiscontent](https://github.com/lifeiscontent) diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index cb36fe3a4092..d4e3346a826b 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for Next.js", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index 027d5bad0b2b..bb7ac0ddf153 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -110,10 +110,7 @@ export const babel: PresetProperty<'babel'> = async (baseConfig: TransformOption }; export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, options) => { - const frameworkOptions = await options.presets.apply<{ options: FrameworkOptions }>( - 'frameworkOptions' - ); - const { options: { nextConfigPath } = {} } = frameworkOptions; + const { nextConfigPath } = await options.presets.apply('frameworkOptions'); const nextConfig = await configureConfig({ baseConfig, nextConfigPath, diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index 2de75b7ed871..c34b21e89ad7 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 7314b2f46c27..b33e68211d80 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 fec32de42c07..053aa9b35282 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 7e7213d7b2d8..32215f431682 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 5ae4a23014fd..8855bfd2e442 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 be342cbdd806..961969e507f7 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 06cd41e6499d..ff1f2b245f63 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 96443bbf95b2..83f70b36b126 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for SvelteKit", "keywords": [ "storybook", diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index fd53a1fb71b9..eed6922b71bc 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 a03850a05c9b..c05c303d988f 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 92fecfe4fbfd..224a0ee3b6ed 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 34df2542ab04..e9974bfdf16d 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 4ffc454688ae..0be834c503cc 100644 --- a/code/lib/channels/package.json +++ b/code/lib/channels/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/channels", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index ff1994708083..27b64dae50cd 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 9c6b59f1457b..7a26a80642cb 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook CLI", "keywords": [ "storybook" diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index 59487f009765..2ea8e20d58b0 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook's CLI - install, dev, build, upgrade, and more", "keywords": [ "cli", diff --git a/code/lib/cli/src/autoblock/block-dependencies-versions.ts b/code/lib/cli/src/autoblock/block-dependencies-versions.ts new file mode 100644 index 000000000000..284562aa9f6d --- /dev/null +++ b/code/lib/cli/src/autoblock/block-dependencies-versions.ts @@ -0,0 +1,92 @@ +import { createBlocker } from './types'; +import { dedent } from 'ts-dedent'; +import { lt } from 'semver'; + +const minimalVersionsMap = { + '@angular/core': '15.0.0', + 'react-scripts': '5.0.0', + next: '13.5.0', + preact: '10.0.0', + svelte: '4.0.0', + vue: '3.0.0', +}; + +type Result = { + installedVersion: string | undefined; + packageName: keyof typeof minimalVersionsMap; + minimumVersion: string; +}; +const typedKeys = (obj: Record) => Object.keys(obj) as TKey[]; + +export const blocker = createBlocker({ + id: 'dependenciesVersions', + async check({ packageManager }) { + const list = await Promise.all( + typedKeys(minimalVersionsMap).map(async (packageName) => ({ + packageName, + installedVersion: await packageManager.getPackageVersion(packageName), + minimumVersion: minimalVersionsMap[packageName], + })) + ); + + return list.reduce((acc, { installedVersion, minimumVersion, packageName }) => { + if (acc) { + return acc; + } + if (packageName && installedVersion && lt(installedVersion, minimumVersion)) { + return { + installedVersion, + packageName, + minimumVersion, + }; + } + return acc; + }, false); + }, + message(options, data) { + return `Found ${data.packageName} version: ${data.installedVersion}, please upgrade to ${data.minimumVersion} or higher.`; + }, + log(options, data) { + switch (data.packageName) { + case 'react-scripts': + return dedent` + Support react-script < 5.0.0 has been removed. + Please see the migration guide for more information: + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#create-react-app-dropped-cra4-support + + Upgrade to the latest version of react-scripts. + `; + case 'vue': + return dedent` + Support for Vue 2 has been removed. + Please see the migration guide for more information: + https://v3-migration.vuejs.org/ + + Please upgrade to the latest version of Vue. + `; + case '@angular/core': + return dedent` + Support for Angular < 15 has been removed. + Please see the migration guide for more information: + https://angular.io/guide/update-to-version-15 + + Please upgrade to the latest version of Angular. + `; + case 'next': + return dedent` + Support for Next.js < 13.5 has been removed. + Please see the migration guide for more information: + https://nextjs.org/docs/pages/building-your-application/upgrading/version-13 + + Please upgrade to the latest version of Next.js. + `; + default: + return dedent` + Support for ${data.packageName} version < ${data.minimumVersion} has been removed. + Storybook 8 needs minimum version of ${data.minimumVersion}, but you had version ${data.installedVersion}. + + Please update this dependency. + `; + } + }, +}); diff --git a/code/lib/cli/src/autoblock/block-node-version.ts b/code/lib/cli/src/autoblock/block-node-version.ts new file mode 100644 index 000000000000..220b29823e4e --- /dev/null +++ b/code/lib/cli/src/autoblock/block-node-version.ts @@ -0,0 +1,25 @@ +import { createBlocker } from './types'; +import { dedent } from 'ts-dedent'; +import { lt } from 'semver'; + +export const blocker = createBlocker({ + id: 'minimumNode16', + async check() { + const nodeVersion = process.versions.node; + if (nodeVersion && lt(nodeVersion, '18.0.0')) { + return { nodeVersion }; + } + return false; + }, + message(options, data) { + return `Please use Node.js v18 or higher.`; + }, + log(options, data) { + return dedent` + We've detected you're using Node.js v${data.nodeVersion}. + Storybook needs Node.js 18 or higher. + + https://nodejs.org/en/download + `; + }, +}); diff --git a/code/lib/cli/src/autoblock/block-stories-mdx.ts b/code/lib/cli/src/autoblock/block-stories-mdx.ts new file mode 100644 index 000000000000..b868d913ecd0 --- /dev/null +++ b/code/lib/cli/src/autoblock/block-stories-mdx.ts @@ -0,0 +1,33 @@ +import { createBlocker } from './types'; +import { dedent } from 'ts-dedent'; +import { glob } from 'glob'; + +export const blocker = createBlocker({ + id: 'storiesMdxUsage', + async check() { + const files = await glob('**/*.stories.mdx', { cwd: process.cwd() }); + if (files.length === 0) { + return false; + } + return { files }; + }, + message(options, data) { + return `Found ${data.files.length} stories.mdx ${ + data.files.length === 1 ? 'file' : 'files' + }, these must be migrated.`; + }, + log() { + return dedent` + Support for *.stories.mdx files has been removed. + Please see the migration guide for more information: + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#dropping-support-for-storiesmdx-csf-in-mdx-format-and-mdx1-support + + Storybook will also require you to use MDX 3.0.0 or later. + Check the migration guide for more information: + https://mdxjs.com/blog/v3/ + + Manually run the migration script to convert your stories.mdx files to CSF format documented here: + https://storybook.js.org/docs/migration-guide#storiesmdx-to-mdxcsf + `; + }, +}); diff --git a/code/lib/cli/src/autoblock/block-storystorev6.ts b/code/lib/cli/src/autoblock/block-storystorev6.ts new file mode 100644 index 000000000000..40a9f8822ac9 --- /dev/null +++ b/code/lib/cli/src/autoblock/block-storystorev6.ts @@ -0,0 +1,40 @@ +import { relative } from 'path'; +import { createBlocker } from './types'; +import { dedent } from 'ts-dedent'; +import type { StorybookConfigRaw } from '@storybook/types'; + +export const blocker = createBlocker({ + id: 'storyStoreV7removal', + async check({ mainConfig }) { + const features = (mainConfig as any as StorybookConfigRaw)?.features; + if (features === undefined) { + return false; + } + if (Object.hasOwn(features, 'storyStoreV7')) { + return true; + } + return false; + }, + message(options, data) { + const mainConfigPath = relative(process.cwd(), options.mainConfigPath); + return `StoryStoreV7 feature must be removed from ${mainConfigPath}`; + }, + log() { + return dedent` + StoryStoreV7 feature must be removed from your Storybook configuration. + This feature was removed in Storybook 8.0.0. + Please see the migration guide for more information: + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev6-and-storiesof-is-deprecated + + In your Storybook configuration file you have this code: + + export default = { + features: { + storyStoreV7: false, <--- remove this line + }, + }; + + You need to remove the storyStoreV7 property. + `; + }, +}); diff --git a/code/lib/cli/src/autoblock/index.test.ts b/code/lib/cli/src/autoblock/index.test.ts new file mode 100644 index 000000000000..ce5fa3170411 --- /dev/null +++ b/code/lib/cli/src/autoblock/index.test.ts @@ -0,0 +1,109 @@ +import { expect, test, vi } from 'vitest'; +import { autoblock } from './index'; +import { JsPackageManagerFactory } from '@storybook/core-common'; +import { createBlocker } from './types'; +import { writeFile as writeFileRaw } from 'node:fs/promises'; +import { logger } from '@storybook/node-logger'; + +vi.mock('node:fs/promises', () => ({ + writeFile: vi.fn(), +})); +vi.mock('boxen', () => ({ + default: vi.fn((x) => x), +})); +vi.mock('@storybook/node-logger', () => ({ + logger: { + info: vi.fn(), + line: vi.fn(), + plain: vi.fn(), + }, +})); + +const writeFile = vi.mocked(writeFileRaw); + +const blockers = { + alwaysPass: createBlocker({ + id: 'alwaysPass', + check: async () => false, + message: () => 'Always pass', + log: () => 'Always pass', + }), + alwaysFail: createBlocker({ + id: 'alwaysFail', + check: async () => ({ bad: true }), + message: () => 'Always fail', + log: () => '...', + }), + alwaysFail2: createBlocker({ + id: 'alwaysFail2', + check: async () => ({ disaster: true }), + message: () => 'Always fail 2', + log: () => '...', + }), +} as const; + +const baseOptions: Parameters[0] = { + configDir: '.storybook', + mainConfig: { + stories: [], + }, + mainConfigPath: '.storybook/main.ts', + packageJson: { + dependencies: {}, + devDependencies: {}, + }, + packageManager: JsPackageManagerFactory.getPackageManager({ force: 'npm' }), +}; + +test('with empty list', async () => { + const result = await autoblock({ ...baseOptions }, []); + expect(result).toBe(null); + expect(logger.plain).not.toHaveBeenCalledWith(expect.stringContaining('No blockers found')); +}); + +test('all passing', async () => { + const result = await autoblock({ ...baseOptions }, [ + Promise.resolve({ blocker: blockers.alwaysPass }), + Promise.resolve({ blocker: blockers.alwaysPass }), + ]); + expect(result).toBe(null); + expect(logger.plain).toHaveBeenCalledWith(expect.stringContaining('No blockers found')); +}); + +test('1 fail', async () => { + const result = await autoblock({ ...baseOptions }, [ + Promise.resolve({ blocker: blockers.alwaysPass }), + Promise.resolve({ blocker: blockers.alwaysFail }), + ]); + expect(writeFile).toHaveBeenCalledWith( + expect.any(String), + expect.stringContaining('alwaysFail'), + expect.any(Object) + ); + expect(result).toBe('alwaysFail'); + expect(logger.plain).toHaveBeenCalledWith(expect.stringContaining('Oh no..')); + + expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` + "(alwaysFail): + ..." + `); +}); + +test('multiple fails', async () => { + const result = await autoblock({ ...baseOptions }, [ + Promise.resolve({ blocker: blockers.alwaysPass }), + Promise.resolve({ blocker: blockers.alwaysFail }), + Promise.resolve({ blocker: blockers.alwaysFail2 }), + ]); + expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` + "(alwaysFail): + ... + + ---- + + (alwaysFail2): + ..." + `); + + expect(result).toBe('alwaysFail'); +}); diff --git a/code/lib/cli/src/autoblock/index.ts b/code/lib/cli/src/autoblock/index.ts new file mode 100644 index 000000000000..ca8116d890cb --- /dev/null +++ b/code/lib/cli/src/autoblock/index.ts @@ -0,0 +1,85 @@ +import type { AutoblockOptions, Blocker } from './types'; +import { logger } from '@storybook/node-logger'; +import chalk from 'chalk'; +import boxen from 'boxen'; +import { writeFile } from 'node:fs/promises'; + +const excludesFalse = (x: T | false): x is T => x !== false; + +const blockers: () => BlockerModule[] = () => [ + // add/remove blockers here + import('./block-storystorev6'), + import('./block-stories-mdx'), + import('./block-dependencies-versions'), + import('./block-node-version'), +]; + +type BlockerModule = Promise<{ blocker: Blocker }>; + +export const autoblock = async ( + options: AutoblockOptions, + list: BlockerModule[] = blockers() +) => { + if (list.length === 0) { + return null; + } + + logger.info('Checking for upgrade blockers...'); + + const out = await Promise.all( + list.map(async (i) => { + const { blocker } = await i; + const result = await blocker.check(options); + if (result) { + return { + id: blocker.id, + value: true, + message: blocker.message(options, result), + log: blocker.log(options, result), + }; + } else { + return false; + } + }) + ); + + const faults = out.filter(excludesFalse); + + if (faults.length > 0) { + const LOG_FILE_NAME = 'migration-storybook.log'; + + const messages = { + welcome: `Blocking your upgrade because of the following issues:`, + reminder: chalk.yellow('Fix the above issues and try running the upgrade command again.'), + logfile: chalk.yellow(`You can find more details in ./${LOG_FILE_NAME}.`), + }; + const borderColor = '#FC521F'; + + logger.plain('Oh no..'); + logger.plain( + boxen( + [messages.welcome] + .concat(faults.map((i) => i.message)) + .concat([messages.reminder]) + .concat([messages.logfile]) + .join('\n\n'), + { borderStyle: 'round', padding: 1, borderColor } + ) + ); + + await writeFile( + LOG_FILE_NAME, + faults.map((i) => '(' + i.id + '):\n' + i.log).join('\n\n----\n\n'), + { + encoding: 'utf-8', + } + ); + + return faults[0].id; + } + + logger.plain('No blockers found.'); + logger.line(); + + return null; +}; diff --git a/code/lib/cli/src/autoblock/types.ts b/code/lib/cli/src/autoblock/types.ts new file mode 100644 index 000000000000..62be9625c76e --- /dev/null +++ b/code/lib/cli/src/autoblock/types.ts @@ -0,0 +1,42 @@ +import type { JsPackageManager, PackageJson } from '@storybook/core-common'; +import type { StorybookConfig } from '@storybook/types'; + +export interface AutoblockOptions { + packageManager: JsPackageManager; + packageJson: PackageJson; + mainConfig: StorybookConfig; + mainConfigPath: string; + configDir: string; +} + +export interface Blocker { + /** + * A unique string to identify the blocker with. + */ + id: string; + /** + * Check if the blocker should block. + * + * @param context + * @returns A truthy value to activate the block, return false to proceed. + */ + check: (options: AutoblockOptions) => Promise; + /** + * Format a message to be printed to the log-file. + * @param context + * @param data returned from the check method. + * @returns The string to print to the terminal. + */ + message: (options: AutoblockOptions, data: T) => string; + /** + * Format a message to be printed to the log-file. + * @param context + * @param data returned from the check method. + * @returns The string to print to the log-file. + */ + log: (options: AutoblockOptions, data: T) => string; +} + +export function createBlocker(block: Blocker) { + return block; +} diff --git a/code/lib/cli/src/automigrate/fixes/index.ts b/code/lib/cli/src/automigrate/fixes/index.ts index 27f8a80b3140..030e31baa6dc 100644 --- a/code/lib/cli/src/automigrate/fixes/index.ts +++ b/code/lib/cli/src/automigrate/fixes/index.ts @@ -2,17 +2,18 @@ import type { Fix } from '../types'; import { cra5 } from './cra5'; import { webpack5 } from './webpack5'; +import { vite4 } from './vite4'; import { vue3 } from './vue3'; import { mdxgfm } from './mdx-gfm'; import { eslintPlugin } from './eslint-plugin'; import { builderVite } from './builder-vite'; +import { viteConfigFile } from './vite-config-file'; import { sbScripts } from './sb-scripts'; import { sbBinary } from './sb-binary'; import { newFrameworks } from './new-frameworks'; import { removedGlobalClientAPIs } from './remove-global-client-apis'; import { mdx1to2 } from './mdx-1-to-2'; import { autodocsTrue } from './autodocs-true'; -import { nodeJsRequirement } from './nodejs-requirement'; import { angularBuilders } from './angular-builders'; import { incompatibleAddons } from './incompatible-addons'; import { angularBuildersMultiproject } from './angular-builders-multiproject'; @@ -20,20 +21,23 @@ import { wrapRequire } from './wrap-require'; import { reactDocgen } from './react-docgen'; import { removeReactDependency } from './prompt-remove-react'; import { storyshotsMigration } from './storyshots-migration'; +import { removeJestTestingLibrary } from './remove-jest-testing-library'; export * from '../types'; export const allFixes: Fix[] = [ - nodeJsRequirement, newFrameworks, cra5, webpack5, vue3, + vite4, + viteConfigFile, eslintPlugin, builderVite, sbBinary, sbScripts, incompatibleAddons, + removeJestTestingLibrary, removedGlobalClientAPIs, mdx1to2, mdxgfm, diff --git a/code/lib/cli/src/automigrate/fixes/nodejs-requirement.test.ts b/code/lib/cli/src/automigrate/fixes/nodejs-requirement.test.ts deleted file mode 100644 index b3c1f8b311d6..000000000000 --- a/code/lib/cli/src/automigrate/fixes/nodejs-requirement.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, afterAll, it, expect, vi } from 'vitest'; - -import { nodeJsRequirement } from './nodejs-requirement'; - -vi.mock('fs-extra', async () => import('../../../../../__mocks__/fs-extra')); - -const check = async ({ storybookVersion = '7.0.0' }) => { - return nodeJsRequirement.check({ - storybookVersion, - packageManager: {} as any, - mainConfig: {} as any, - }); -}; - -const originalNodeVersion = process.version; -const mockNodeVersion = (version: string) => { - Object.defineProperties(process, { - version: { - value: version, - }, - }); -}; - -describe('nodejs-requirement fix', () => { - afterAll(() => { - mockNodeVersion(originalNodeVersion); - vi.restoreAllMocks(); - }); - - it('skips when sb <= 7.0.0', async () => { - mockNodeVersion('14.0.0'); - await expect(check({ storybookVersion: '6.3.2' })).resolves.toBeNull(); - }); - - it('skips when node >= 16.0.0', async () => { - mockNodeVersion('16.0.0'); - await expect(check({})).resolves.toBeNull(); - }); - - it('skips when node >= 18.0.0', async () => { - mockNodeVersion('18.0.0'); - await expect(check({})).resolves.toBeNull(); - }); - - it('prompts when node <= 16.0.0', async () => { - mockNodeVersion('14.0.0'); - await expect(check({})).resolves.toEqual({ nodeVersion: '14.0.0' }); - }); -}); diff --git a/code/lib/cli/src/automigrate/fixes/nodejs-requirement.ts b/code/lib/cli/src/automigrate/fixes/nodejs-requirement.ts deleted file mode 100644 index cf82bceb9bac..000000000000 --- a/code/lib/cli/src/automigrate/fixes/nodejs-requirement.ts +++ /dev/null @@ -1,44 +0,0 @@ -import chalk from 'chalk'; -import dedent from 'ts-dedent'; -import semver from 'semver'; -import type { Fix } from '../types'; - -interface NodeJsRequirementOptions { - nodeVersion: string; -} - -export const nodeJsRequirement: Fix = { - id: 'nodejs-requirement', - promptOnly: true, - - async check({ storybookVersion }) { - if (!semver.gte(storybookVersion, '7.0.0')) { - return null; - } - - const nodeVersion = process.version; - if (semver.lt(nodeVersion, '16.0.0')) { - return { nodeVersion }; - } - - return null; - }, - prompt({ nodeVersion }) { - return dedent` - ${chalk.bold( - chalk.red('Attention') - )}: We could not automatically make this change. You'll need to do it manually. - - We've detected that you're using Node ${chalk.bold( - nodeVersion - )} but Storybook 7 only supports Node ${chalk.bold( - 'v16.0.0' - )} and higher. You will either need to upgrade your Node version or keep using an older version of Storybook. - - Please see the migration guide for more information: - ${chalk.yellow( - 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#dropped-support-for-node-15-and-below' - )} - `; - }, -}; diff --git a/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.test.ts b/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.test.ts new file mode 100644 index 000000000000..60a2c2a97a35 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.test.ts @@ -0,0 +1,64 @@ +import { expect, it } from 'vitest'; + +import type { StorybookConfig } from '@storybook/types'; +import type { JsPackageManager } from '@storybook/core-common'; +import { removeJestTestingLibrary } from './remove-jest-testing-library'; +import ansiRegex from 'ansi-regex'; + +const check = async ({ + packageManager, + main: mainConfig = {}, + storybookVersion = '8.0.0', +}: { + packageManager: Partial; + main?: Partial & Record; + storybookVersion?: string; +}) => { + return removeJestTestingLibrary.check({ + packageManager: packageManager as any, + configDir: '', + mainConfig: mainConfig as any, + storybookVersion, + }); +}; + +it('should prompt to install the test package and run the codemod', async () => { + const options = await check({ + packageManager: { + getAllDependencies: async () => ({ + '@storybook/jest': '1.0.0', + '@storybook/testing-library': '1.0.0', + }), + }, + main: { addons: ['@storybook/essentials', '@storybook/addon-info'] }, + }); + + await expect(options).toMatchInlineSnapshot(` + { + "incompatiblePackages": [ + "@storybook/jest", + "@storybook/testing-library", + ], + } + `); + + expect.addSnapshotSerializer({ + serialize: (value) => { + const stringVal = typeof value === 'string' ? value : value.toString(); + return stringVal.replace(ansiRegex(), ''); + }, + test: () => true, + }); + + expect(await removeJestTestingLibrary.prompt(options!)).toMatchInlineSnapshot(` + Attention: We've detected that you're using the following packages which are known to be incompatible with Storybook 8: + + - @storybook/jest + - @storybook/testing-library + + Install the replacement for those packages: @storybook/test + + And run the following codemod: + npx storybook migrate migrate-to-test-package --glob="**/*.stories.@(js|jsx|ts|tsx)" + `); +}); diff --git a/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts b/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts new file mode 100644 index 000000000000..87cf964468b3 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts @@ -0,0 +1,32 @@ +import chalk from 'chalk'; +import dedent from 'ts-dedent'; +import type { Fix } from '../types'; + +export const removeJestTestingLibrary: Fix<{ incompatiblePackages: string[] }> = { + id: 'remove-jest-testing-library', + promptOnly: true, + async check({ mainConfig, packageManager }) { + const deps = await packageManager.getAllDependencies(); + + const incompatiblePackages = Object.keys(deps).filter( + (it) => it === '@storybook/jest' || it === '@storybook/testing-library' + ); + return incompatiblePackages.length ? { incompatiblePackages } : null; + }, + prompt({ incompatiblePackages }) { + return dedent` + ${chalk.bold( + 'Attention' + )}: We've detected that you're using the following packages which are known to be incompatible with Storybook 8: + + ${incompatiblePackages.map((name) => `- ${chalk.cyan(`${name}`)}`).join('\n')} + + Install the replacement for those packages: ${chalk.cyan('@storybook/test')} + + And run the following codemod: + ${chalk.cyan( + 'npx storybook migrate migrate-to-test-package --glob="**/*.stories.@(js|jsx|ts|tsx)"' + )} + `; + }, +}; diff --git a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts new file mode 100644 index 000000000000..f8047a839af9 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts @@ -0,0 +1,111 @@ +import { dedent } from 'ts-dedent'; +import type { Fix } from '../types'; +import findUp from 'find-up'; +import { getFrameworkPackageName } from '../helpers/mainConfigFile'; +import { frameworkToRenderer } from '../../helpers'; +import { frameworkPackages } from '@storybook/core-common'; + +interface Webpack5RunOptions { + plugins: string[]; + existed: boolean; +} + +export const viteConfigFile = { + id: 'viteConfigFile', + + async check({ mainConfig, packageManager }) { + const viteConfigPath = await findUp([ + 'vite.config.js', + 'vite.config.mjs', + 'vite.config.cjs', + 'vite.config.ts', + ]); + + const rendererToVitePluginMap: Record = { + preact: '@preact/preset-vite', + qwik: 'vite-plugin-qwik', + react: '@vitejs/plugin-react', + solid: 'vite-plugin-solid', + svelte: '@sveltejs/vite-plugin-svelte', + sveltekit: '@sveltejs/kit/vite', // might be pointless? + vue: '@vitejs/plugin-vue', + }; + + const frameworkPackageName = getFrameworkPackageName(mainConfig); + if (!frameworkPackageName) { + return null; + } + const frameworkName = frameworkPackages[frameworkPackageName]; + const isUsingViteBuilder = + mainConfig.core?.builder === 'vite' || + frameworkPackageName?.includes('vite') || + frameworkPackageName === 'qwik' || + frameworkPackageName === 'solid' || + frameworkPackageName === 'sveltekit'; + + const rendererName = frameworkToRenderer[frameworkName as keyof typeof frameworkToRenderer]; + + if (!viteConfigPath && isUsingViteBuilder) { + const plugins = []; + + if (rendererToVitePluginMap[rendererName]) { + plugins.push(rendererToVitePluginMap[rendererName]); + } + + return { + plugins, + existed: !!viteConfigPath, + }; + } + + const plugin = rendererToVitePluginMap[rendererName]; + + if (!plugin) { + return null; + } + + const pluginVersion = await packageManager.getPackageVersion(plugin); + + if (viteConfigPath && isUsingViteBuilder && !pluginVersion) { + const plugins = []; + + if (plugin) { + plugins.push(plugin); + } + + return { + plugins, + existed: !viteConfigPath, + }; + } + + return null; + }, + + prompt({ existed, plugins }) { + if (existed) { + return dedent` + Storybook 8.0.0 no longer ships with a Vite config build-in. + We've detected you do have a Vite config, but you may be missing the following plugins in it. + + ${plugins.map((plugin) => ` - ${plugin}`).join('\n')} + + If you do already have these plugins, you can ignore this message. + + You can find more information on how to do this here: + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added + + This change was necessary to support newer versions of Vite. + `; + } + return dedent` + Storybook 8.0.0 no longer ships with a Vite config build-in. + Please add a vite.config.js file to your project root. + + You can find more information on how to do this here: + https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-specific-vite-plugins-have-to-be-explicitly-added + + This change was necessary to support newer versions of Vite. + `; + }, +} satisfies Fix; diff --git a/code/lib/cli/src/automigrate/fixes/vite4.ts b/code/lib/cli/src/automigrate/fixes/vite4.ts new file mode 100644 index 000000000000..d04c4abd10d7 --- /dev/null +++ b/code/lib/cli/src/automigrate/fixes/vite4.ts @@ -0,0 +1,43 @@ +import chalk from 'chalk'; +import { dedent } from 'ts-dedent'; +import semver from 'semver'; +import type { Fix } from '../types'; + +const logger = console; + +interface Webpack5RunOptions { + viteVersion: string | null; +} + +export const vite4 = { + id: 'vite4', + + async check({ packageManager }) { + const viteVersion = await packageManager.getPackageVersion('vite'); + + if (!viteVersion || semver.gt(viteVersion, '4.0.0')) { + return null; + } + + return { viteVersion }; + }, + + prompt({ viteVersion: viteVersion }) { + const viteFormatted = chalk.cyan(`${viteVersion}`); + + return dedent` + We've detected your version of Vite is outdated (${viteFormatted}). + + Storybook 8.0.0 will require Vite 4.0.0 or later. + Do you want us to upgrade Vite for you? + `; + }, + + async run({ packageManager, dryRun }) { + const deps = [`vite`]; + logger.info(`✅ Adding dependencies: ${deps}`); + if (!dryRun) { + await packageManager.addDependencies({ installAsDevDependencies: true }, deps); + } + }, +} satisfies Fix; diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index 3adeff5e0ead..19d4ee8922e9 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -3,20 +3,25 @@ import chalk from 'chalk'; import boxen from 'boxen'; import { createWriteStream, move, remove } from 'fs-extra'; import tempy from 'tempy'; -import dedent from 'ts-dedent'; import { join } from 'path'; import invariant from 'tiny-invariant'; import { - getStorybookInfo, - loadMainConfig, - getCoercedStorybookVersion, JsPackageManagerFactory, + type JsPackageManager, + getCoercedStorybookVersion, + getStorybookInfo, } from '@storybook/core-common'; -import type { PackageManagerName } from '@storybook/core-common'; -import type { Fix, FixId, FixOptions, FixSummary } from './fixes'; -import { FixStatus, PreCheckFailure, allFixes } from './fixes'; +import type { + Fix, + FixId, + AutofixOptions, + FixSummary, + PreCheckFailure, + AutofixOptionsFromCLI, +} from './fixes'; +import { FixStatus, allFixes } from './fixes'; import { cleanLog } from './helpers/cleanLog'; import { getMigrationSummary } from './helpers/getMigrationSummary'; import { getStorybookData } from './helpers/mainConfigFile'; @@ -52,18 +57,47 @@ const logAvailableMigrations = () => { logger.info(`\nThe following migrations are available: ${availableFixes}`); }; +export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { + const packageManager = JsPackageManagerFactory.getPackageManager({ + force: options.packageManager, + }); + + const [packageJson, storybookVersion] = await Promise.all([ + packageManager.retrievePackageJson(), + getCoercedStorybookVersion(packageManager), + ]); + + const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( + packageJson, + options.configDir + ); + const configDir = options.configDir || inferredConfigDir || '.storybook'; + + if (!storybookVersion) { + throw new Error('Could not determine Storybook version'); + } + + if (!mainConfigPath) { + throw new Error('Could not determine main config path'); + } + + return automigrate({ ...options, packageManager, storybookVersion, mainConfigPath, configDir }); +}; + export const automigrate = async ({ fixId, fixes: inputFixes, dryRun, yes, - packageManager: pkgMgr, + packageManager, list, - configDir: userSpecifiedConfigDir, + configDir, + mainConfigPath, + storybookVersion, renderer: rendererPackage, skipInstall, hideMigrationSummary = false, -}: FixOptions = {}): Promise<{ +}: AutofixOptions): Promise<{ fixResults: Record; preCheckFailure?: PreCheckFailure; } | null> => { @@ -87,10 +121,12 @@ export const automigrate = async ({ const { fixResults, fixSummary, preCheckFailure } = await runFixes({ fixes, - pkgMgr, - userSpecifiedConfigDir, + packageManager, rendererPackage, skipInstall, + configDir, + mainConfigPath, + storybookVersion, dryRun, yes, }); @@ -107,7 +143,6 @@ export const automigrate = async ({ } if (!hideMigrationSummary) { - const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); const installationMetadata = await packageManager.findInstallations([ '@storybook/*', 'storybook', @@ -129,78 +164,30 @@ export async function runFixes({ fixes, dryRun, yes, - pkgMgr, - userSpecifiedConfigDir, rendererPackage, skipInstall, + configDir, + packageManager, + mainConfigPath, + storybookVersion, }: { fixes: Fix[]; yes?: boolean; dryRun?: boolean; - pkgMgr?: PackageManagerName; - userSpecifiedConfigDir?: string; rendererPackage?: string; skipInstall?: boolean; + configDir: string; + packageManager: JsPackageManager; + mainConfigPath: string; + storybookVersion: string; }): Promise<{ preCheckFailure?: PreCheckFailure; fixResults: Record; fixSummary: FixSummary; }> { - const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); - const fixResults = {} as Record; const fixSummary: FixSummary = { succeeded: [], failed: {}, manual: [], skipped: [] }; - const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( - await packageManager.retrievePackageJson(), - userSpecifiedConfigDir - ); - - const storybookVersion = await getCoercedStorybookVersion(packageManager); - - if (!storybookVersion) { - logger.info(dedent` - [Storybook automigrate] ❌ Unable to determine storybook version so the automigrations will be skipped. - 🤔 Are you running automigrate from your project directory? Please specify your Storybook config directory with the --config-dir flag. - `); - return { - fixResults, - fixSummary, - preCheckFailure: PreCheckFailure.UNDETECTED_SB_VERSION, - }; - } - - const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; - try { - await loadMainConfig({ configDir }); - } catch (err) { - const errMessage = String(err); - if (errMessage.includes('No configuration files have been found')) { - logger.info( - dedent`[Storybook automigrate] Could not find or evaluate your Storybook main.js config directory at ${chalk.blue( - configDir - )} so the automigrations will be skipped. You might be running this command in a monorepo or a non-standard project structure. If that is the case, please rerun this command by specifying the path to your Storybook config directory via the --config-dir option.` - ); - return { - fixResults, - fixSummary, - preCheckFailure: PreCheckFailure.MAINJS_NOT_FOUND, - }; - } - logger.info( - dedent`[Storybook automigrate] ❌ Failed trying to evaluate ${chalk.blue( - mainConfigPath - )} with the following error: ${errMessage}` - ); - logger.info('Please fix the error and try again.'); - - return { - fixResults, - fixSummary, - preCheckFailure: PreCheckFailure.MAINJS_EVALUATION, - }; - } - for (let i = 0; i < fixes.length; i += 1) { const f = fixes[i] as Fix; let result; diff --git a/code/lib/cli/src/automigrate/types.ts b/code/lib/cli/src/automigrate/types.ts index 0fa57c1fdfcc..97d20c09dc45 100644 --- a/code/lib/cli/src/automigrate/types.ts +++ b/code/lib/cli/src/automigrate/types.ts @@ -35,14 +35,19 @@ export enum PreCheckFailure { MAINJS_EVALUATION = 'mainjs_evaluation_error', } -export interface FixOptions { +export interface AutofixOptions extends Omit { + packageManager: JsPackageManager; + mainConfigPath: string; + storybookVersion: string; +} +export interface AutofixOptionsFromCLI { fixId?: FixId; list?: boolean; fixes?: Fix[]; yes?: boolean; - dryRun?: boolean; packageManager?: PackageManagerName; - configDir?: string; + dryRun?: boolean; + configDir: string; renderer?: string; skipInstall?: boolean; hideMigrationSummary?: boolean; diff --git a/code/lib/cli/src/generate.ts b/code/lib/cli/src/generate.ts index 71ea9841fc64..250cd200d206 100644 --- a/code/lib/cli/src/generate.ts +++ b/code/lib/cli/src/generate.ts @@ -17,7 +17,7 @@ import { migrate } from './migrate'; import { upgrade, type UpgradeOptions } from './upgrade'; import { sandbox } from './sandbox'; import { link } from './link'; -import { automigrate } from './automigrate'; +import { doAutomigrate } from './automigrate'; import { dev } from './dev'; import { build } from './build'; import { doctor } from './doctor'; @@ -80,6 +80,7 @@ command('upgrade') 'Force package manager for installing dependencies' ) .option('-y --yes', 'Skip prompting the user') + .option('-f --force', 'force the upgrade, skipping autoblockers') .option('-n --dry-run', 'Only check for upgrades, do not install') .option('-s --skip-check', 'Skip postinstall version and automigration checks') .option('-c, --config-dir ', 'Directory where to load Storybook configurations from') @@ -171,7 +172,7 @@ command('automigrate [fixId]') 'The renderer package for the framework Storybook is using.' ) .action(async (fixId, options) => { - await automigrate({ fixId, ...options }).catch((e) => { + await doAutomigrate({ fixId, ...options }).catch((e) => { logger.error(e); process.exit(1); }); diff --git a/code/lib/cli/src/generators/REACT_NATIVE/index.ts b/code/lib/cli/src/generators/REACT_NATIVE/index.ts index 94724ba1af59..c8e4582382dd 100644 --- a/code/lib/cli/src/generators/REACT_NATIVE/index.ts +++ b/code/lib/cli/src/generators/REACT_NATIVE/index.ts @@ -14,12 +14,15 @@ const generator = async ( const reactVersion = packageJson.dependencies.react; - const packagesToResolve = [ - // addon-ondevice-controls peer deps + const controlsPeerDependencies = [ 'react-native-safe-area-context', '@react-native-async-storage/async-storage', '@react-native-community/datetimepicker', '@react-native-community/slider', + ].filter((dep) => !packageJson.dependencies[dep] && !packageJson.devDependencies[dep]); + + const packagesToResolve = [ + ...controlsPeerDependencies, '@storybook/addon-ondevice-controls', '@storybook/addon-ondevice-actions', '@storybook/react-native', @@ -47,7 +50,6 @@ const generator = async ( packageManager.addScripts({ 'storybook-generate': 'sb-rn-get-stories', - 'storybook-watch': 'sb-rn-watcher', }); const storybookConfigFolder = '.storybook'; diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index dc5593ba6a4d..3c3cae9e5e5e 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -224,8 +224,8 @@ export async function baseGenerator( extraAddonsToInstall.push( '@storybook/addon-links', - '@storybook/addon-essentials' - // '@chromatic-com/storybook@^1' + '@storybook/addon-essentials', + '@chromatic-com/storybook@^1' ); // added to main.js diff --git a/code/lib/cli/src/helpers.ts b/code/lib/cli/src/helpers.ts index 95c22ee624f1..bbf81816a061 100644 --- a/code/lib/cli/src/helpers.ts +++ b/code/lib/cli/src/helpers.ts @@ -130,7 +130,7 @@ type CopyTemplateFilesOptions = { destination?: string; }; -const frameworkToRenderer: Record< +export const frameworkToRenderer: Record< SupportedFrameworks | SupportedRenderers, SupportedRenderers | 'vue' > = { diff --git a/code/lib/cli/src/migrate.ts b/code/lib/cli/src/migrate.ts index 5e0093d2480d..5e38507afd61 100644 --- a/code/lib/cli/src/migrate.ts +++ b/code/lib/cli/src/migrate.ts @@ -1,7 +1,11 @@ import { listCodemods, runCodemod } from '@storybook/codemod'; import { runFixes } from './automigrate'; import { bareMdxStoriesGlob } from './automigrate/fixes/bare-mdx-stories-glob'; -import { JsPackageManagerFactory } from '@storybook/core-common'; +import { + JsPackageManagerFactory, + getStorybookInfo, + getCoercedStorybookVersion, +} from '@storybook/core-common'; import { getStorybookVersionSpecifier } from './helpers'; const logger = console; @@ -11,7 +15,33 @@ export async function migrate(migration: any, { glob, dryRun, list, rename, pars listCodemods().forEach((key: any) => logger.log(key)); } else if (migration) { if (migration === 'mdx-to-csf' && !dryRun) { - await runFixes({ fixes: [bareMdxStoriesGlob] }); + const packageManager = JsPackageManagerFactory.getPackageManager(); + + const [packageJson, storybookVersion] = await Promise.all([ + // + packageManager.retrievePackageJson(), + getCoercedStorybookVersion(packageManager), + ]); + const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = + getStorybookInfo(packageJson); + const configDir = inferredConfigDir || '.storybook'; + + // GUARDS + if (!storybookVersion) { + throw new Error('Could not determine Storybook version'); + } + + if (!mainConfigPath) { + throw new Error('Could not determine main config path'); + } + + await runFixes({ + fixes: [bareMdxStoriesGlob], + configDir, + mainConfigPath, + packageManager, + storybookVersion, + }); await addStorybookBlocksPackage(); } await runCodemod(migration, { glob, dryRun, logger, rename, parser }); diff --git a/code/lib/cli/src/upgrade.ts b/code/lib/cli/src/upgrade.ts index 1d13c5a7f0c2..e9f4a8151b1c 100644 --- a/code/lib/cli/src/upgrade.ts +++ b/code/lib/cli/src/upgrade.ts @@ -13,13 +13,16 @@ import dedent from 'ts-dedent'; import boxen from 'boxen'; import type { JsPackageManager, PackageManagerName } from '@storybook/core-common'; import { - JsPackageManagerFactory, isCorePackage, versions, - commandLog, + getStorybookInfo, + getCoercedStorybookVersion, + loadMainConfig, + JsPackageManagerFactory, } from '@storybook/core-common'; -import { coerceSemver } from './helpers'; -import { automigrate } from './automigrate'; +import { automigrate } from './automigrate/index'; +import { autoblock } from './autoblock/index'; +import { PreCheckFailure } from './automigrate/types'; type Package = { package: string; @@ -108,28 +111,32 @@ export const checkVersionConsistency = () => { export interface UpgradeOptions { skipCheck: boolean; - packageManager: PackageManagerName; + packageManager?: PackageManagerName; dryRun: boolean; yes: boolean; + force: boolean; disableTelemetry: boolean; configDir?: string; } export const doUpgrade = async ({ skipCheck, - packageManager: pkgMgr, + packageManager: packageManagerName, dryRun, - configDir, + configDir: userSpecifiedConfigDir, yes, ...options }: UpgradeOptions) => { - const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); + const packageManager = JsPackageManagerFactory.getPackageManager({ force: packageManagerName }); // If we can't determine the existing version fallback to v0.0.0 to not block the upgrade const beforeVersion = (await getInstalledStorybookVersion(packageManager)) ?? '0.0.0'; const currentVersion = versions['@storybook/cli']; - const isCanary = currentVersion.startsWith('0.0.0'); + const isCanary = + currentVersion.startsWith('0.0.0') || + beforeVersion.startsWith('portal:') || + beforeVersion.startsWith('workspace:'); if (!isCanary && lt(currentVersion, beforeVersion)) { throw new UpgradeStorybookToLowerVersionError({ beforeVersion, currentVersion }); @@ -138,7 +145,13 @@ export const doUpgrade = async ({ throw new UpgradeStorybookToSameVersionError({ beforeVersion }); } - const latestVersion = await packageManager.latestVersion('@storybook/cli'); + const [latestVersion, packageJson, storybookVersion] = await Promise.all([ + // + packageManager.latestVersion('@storybook/cli'), + packageManager.retrievePackageJson(), + getCoercedStorybookVersion(packageManager), + ]); + const isOutdated = lt(currentVersion, latestVersion); const isPrerelease = prerelease(currentVersion) !== null; @@ -168,36 +181,74 @@ export const doUpgrade = async ({ ) ); - const packageJson = await packageManager.retrievePackageJson(); - - const toUpgradedDependencies = (deps: Record) => { - const monorepoDependencies = Object.keys(deps || {}).filter((dependency) => { - // don't upgrade @storybook/preset-create-react-app if react-scripts is < v5 - if (dependency === '@storybook/preset-create-react-app') { - const reactScriptsVersion = - packageJson.dependencies['react-scripts'] ?? packageJson.devDependencies['react-scripts']; - if (reactScriptsVersion && lt(coerceSemver(reactScriptsVersion), '5.0.0')) { - return false; - } - } + let results; + + const { configDir: inferredConfigDir, mainConfig: mainConfigPath } = getStorybookInfo( + packageJson, + userSpecifiedConfigDir + ); + const configDir = userSpecifiedConfigDir || inferredConfigDir || '.storybook'; + + let mainConfigLoadingError = ''; - // only upgrade packages that are in the monorepo - return dependency in versions; - }) as Array; - return monorepoDependencies.map((dependency) => { - /* add ^ modifier to the version if this is the latest stable or prerelease version - example outputs: @storybook/react@^8.0.0 */ - const maybeCaret = (!isOutdated || isPrerelease) && !isCanary ? '^' : ''; - return `${dependency}@${maybeCaret}${versions[dependency]}`; + const mainConfig = await loadMainConfig({ configDir }).catch((err) => { + mainConfigLoadingError = String(err); + return false; + }); + + // GUARDS + if (!storybookVersion) { + logger.info(missingStorybookVersionMessage()); + results = { preCheckFailure: PreCheckFailure.UNDETECTED_SB_VERSION }; + } else if ( + typeof mainConfigPath === 'undefined' || + mainConfigLoadingError.includes('No configuration files have been found') + ) { + logger.info(mainjsNotFoundMessage(configDir)); + results = { preCheckFailure: PreCheckFailure.MAINJS_NOT_FOUND }; + } else if (typeof mainConfig === 'boolean') { + logger.info(mainjsExecutionFailureMessage(mainConfigPath, mainConfigLoadingError)); + results = { preCheckFailure: PreCheckFailure.MAINJS_EVALUATION }; + } + + // BLOCKERS + if ( + !results && + typeof mainConfig !== 'boolean' && + typeof mainConfigPath !== 'undefined' && + !options.force + ) { + const blockResult = await autoblock({ + packageManager, + configDir, + packageJson, + mainConfig, + mainConfigPath, }); - }; + if (blockResult) { + results = { preCheckFailure: blockResult }; + } + } + + // INSTALL UPDATED DEPENDENCIES + if (!dryRun && !results) { + const toUpgradedDependencies = (deps: Record) => { + const monorepoDependencies = Object.keys(deps || {}).filter((dependency) => { + // only upgrade packages that are in the monorepo + return dependency in versions; + }) as Array; + return monorepoDependencies.map((dependency) => { + /* add ^ modifier to the version if this is the latest stable or prerelease version + example outputs: @storybook/react@^8.0.0 */ + const maybeCaret = (!isOutdated || isPrerelease) && !isCanary ? '^' : ''; + return `${dependency}@${maybeCaret}${versions[dependency]}`; + }); + }; - const upgradedDependencies = toUpgradedDependencies(packageJson.dependencies); - const upgradedDevDependencies = toUpgradedDependencies(packageJson.devDependencies); + const upgradedDependencies = toUpgradedDependencies(packageJson.dependencies); + const upgradedDevDependencies = toUpgradedDependencies(packageJson.devDependencies); - if (!dryRun) { - commandLog(`Updating dependencies in ${chalk.cyan('package.json')}..`); - logger.plain(''); + logger.info(`Updating dependencies in ${chalk.cyan('package.json')}..`); if (upgradedDependencies.length > 0) { await packageManager.addDependencies( { installAsDevDependencies: false, skipInstall: true, packageJson }, @@ -213,26 +264,61 @@ export const doUpgrade = async ({ await packageManager.installDependencies(); } - let automigrationResults; - if (!skipCheck) { + // AUTOMIGRATIONS + if (!skipCheck && !results && mainConfigPath && storybookVersion) { checkVersionConsistency(); - automigrationResults = await automigrate({ dryRun, yes, packageManager: pkgMgr, configDir }); + results = await automigrate({ + dryRun, + yes, + packageManager, + configDir, + mainConfigPath, + storybookVersion, + }); } + + // TELEMETRY if (!options.disableTelemetry) { - const afterVersion = await getInstalledStorybookVersion(packageManager); - const { preCheckFailure, fixResults } = automigrationResults || {}; + const { preCheckFailure, fixResults } = results || {}; const automigrationTelemetry = { automigrationResults: preCheckFailure ? null : fixResults, automigrationPreCheckFailure: preCheckFailure || null, }; - telemetry('upgrade', { + + await telemetry('upgrade', { beforeVersion, - afterVersion, + afterVersion: currentVersion, ...automigrationTelemetry, }); } }; +function missingStorybookVersionMessage(): string { + return dedent` + [Storybook automigrate] ❌ Unable to determine Storybook version so that the automigrations will be skipped. + 🤔 Are you running automigrate from your project directory? Please specify your Storybook config directory with the --config-dir flag. + `; +} + +function mainjsExecutionFailureMessage( + mainConfigPath: string, + mainConfigLoadingError: string +): string { + return dedent` + [Storybook automigrate] ❌ Failed trying to evaluate ${chalk.blue( + mainConfigPath + )} with the following error: ${mainConfigLoadingError} + + Please fix the error and try again. + `; +} + +function mainjsNotFoundMessage(configDir: string): string { + return dedent`[Storybook automigrate] Could not find or evaluate your Storybook main.js config directory at ${chalk.blue( + configDir + )} so the automigrations will be skipped. You might be running this command in a monorepo or a non-standard project structure. If that is the case, please rerun this command by specifying the path to your Storybook config directory via the --config-dir option.`; +} + export async function upgrade(options: UpgradeOptions): Promise { await withTelemetry('upgrade', { cliOptions: options }, () => doUpgrade(options)); } diff --git a/code/lib/cli/tsconfig.json b/code/lib/cli/tsconfig.json index e32ebe0f7b90..9c5db279ca2d 100644 --- a/code/lib/cli/tsconfig.json +++ b/code/lib/cli/tsconfig.json @@ -1,15 +1,11 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "types": [ - "node" - ], + "types": ["node"], "strict": true, "skipLibCheck": true, "resolveJsonModule": true, "noEmit": true }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/code/lib/client-logger/package.json b/code/lib/client-logger/package.json index 873ab291e7de..d75bb42b8ab4 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 561f12d94aab..259aae0056a3 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" @@ -31,6 +31,7 @@ "./dist/transforms/csf-hoist-story-annotations.js": "./dist/transforms/csf-hoist-story-annotations.js", "./dist/transforms/move-builtin-addons.js": "./dist/transforms/move-builtin-addons.js", "./dist/transforms/mdx-to-csf.js": "./dist/transforms/mdx-to-csf.js", + "./dist/transforms/migrate-to-test-package.js": "./dist/transforms/migrate-to-test-package.js", "./dist/transforms/storiesof-to-csf.js": "./dist/transforms/storiesof-to-csf.js", "./dist/transforms/update-addon-info.js": "./dist/transforms/update-addon-info.js", "./dist/transforms/update-organisation-name.js": "./dist/transforms/update-organisation-name.js", @@ -93,6 +94,7 @@ "./src/transforms/csf-2-to-3.ts", "./src/transforms/csf-hoist-story-annotations.js", "./src/transforms/mdx-to-csf.ts", + "./src/transforms/migrate-to-test-package.ts", "./src/transforms/move-builtin-addons.js", "./src/transforms/storiesof-to-csf.js", "./src/transforms/update-addon-info.js", diff --git a/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts b/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts new file mode 100644 index 000000000000..e6acb4b60279 --- /dev/null +++ b/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts @@ -0,0 +1,46 @@ +import { expect, test } from 'vitest'; +import transform from '../migrate-to-test-package'; +import dedent from 'ts-dedent'; + +expect.addSnapshotSerializer({ + serialize: (val: any) => (typeof val === 'string' ? val : val.toString()), + test: () => true, +}); + +const tsTransform = async (source: string) => + (await transform({ source, path: 'Component.stories.tsx' })).trim(); + +test('replace jest and testing-library with the test package', async () => { + const input = dedent` + import { expect } from '@storybook/jest'; + import { within, userEvent } from '@storybook/testing-library'; + `; + + expect(await tsTransform(input)).toMatchInlineSnapshot(` + import { expect } from '@storybook/test'; + import { within, userEvent } from '@storybook/test'; + `); +}); + +test('Make jest imports namespace imports', async () => { + const input = dedent` + import { expect, jest } from '@storybook/jest'; + import { within, userEvent } from '@storybook/testing-library'; + + const onFocusMock = jest.fn(); + const onSearchMock = jest.fn(); + + jest.spyOn(window, 'Something'); + `; + + expect(await tsTransform(input)).toMatchInlineSnapshot(` + import { expect } from '@storybook/test'; + import * as test from '@storybook/test'; + import { within, userEvent } from '@storybook/test'; + + const onFocusMock = test.fn(); + const onSearchMock = test.fn(); + + test.spyOn(window, 'Something'); + `); +}); diff --git a/code/lib/codemod/src/transforms/csf-2-to-3.ts b/code/lib/codemod/src/transforms/csf-2-to-3.ts index 38677f07f702..507c38999c88 100644 --- a/code/lib/codemod/src/transforms/csf-2-to-3.ts +++ b/code/lib/codemod/src/transforms/csf-2-to-3.ts @@ -204,16 +204,8 @@ export default async function transform(info: FileInfo, api: API, options: { par let output = printCsf(csf).code; try { - const prettierConfig = (await prettier.resolveConfig(info.path)) ?? { - printWidth: 100, - tabWidth: 2, - bracketSpacing: true, - trailingComma: 'es5', - singleQuote: true, - }; - output = await prettier.format(output, { - ...prettierConfig, + ...(await prettier.resolveConfig(info.path)), filepath: info.path, }); } catch (e) { diff --git a/code/lib/codemod/src/transforms/mdx-to-csf.ts b/code/lib/codemod/src/transforms/mdx-to-csf.ts index 53e2162ab19b..9c657c822e04 100644 --- a/code/lib/codemod/src/transforms/mdx-to-csf.ts +++ b/code/lib/codemod/src/transforms/mdx-to-csf.ts @@ -291,17 +291,10 @@ export async function transform(info: FileInfo, baseName: string): Promise<[stri const newMdx = mdxProcessor.stringify(root); let output = recast.print(file.path.node).code; - const prettierConfig = (await prettier.resolveConfig(`${info.path}.jsx`)) || { - printWidth: 100, - tabWidth: 2, - bracketSpacing: true, - trailingComma: 'es5', - singleQuote: true, - }; - + const path = `${info.path}.jsx`; output = await prettier.format(output.trim(), { - ...prettierConfig, - filepath: `${info.path}.jsx`, + ...(await prettier.resolveConfig(path)), + filepath: path, }); return [newMdx, output]; diff --git a/code/lib/codemod/src/transforms/migrate-to-test-package.ts b/code/lib/codemod/src/transforms/migrate-to-test-package.ts new file mode 100644 index 000000000000..02545aae06af --- /dev/null +++ b/code/lib/codemod/src/transforms/migrate-to-test-package.ts @@ -0,0 +1,63 @@ +/* eslint-disable no-underscore-dangle */ +import type { FileInfo } from 'jscodeshift'; +import { loadCsf, printCsf } from '@storybook/csf-tools'; +import type { BabelFile } from '@babel/core'; +import * as babel from '@babel/core'; +import * as t from '@babel/types'; +import prettier from 'prettier'; + +export default async function transform(info: FileInfo) { + const csf = loadCsf(info.source, { makeTitle: (title) => title }); + const fileNode = csf._ast; + // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606 + const file: BabelFile = new babel.File( + { filename: info.path }, + { code: info.source, ast: fileNode } + ); + + file.path.traverse({ + ImportDeclaration: (path) => { + if ( + path.node.source.value === '@storybook/jest' || + path.node.source.value === '@storybook/testing-library' + ) { + if (path.node.source.value === '@storybook/jest') { + path.get('specifiers').forEach((specifier) => { + if (specifier.isImportSpecifier()) { + const imported = specifier.get('imported'); + if (!imported.isIdentifier()) return; + if (imported.node.name === 'jest') { + specifier.remove(); + path.insertAfter( + t.importDeclaration( + [t.importNamespaceSpecifier(t.identifier('test'))], + t.stringLiteral('@storybook/test') + ) + ); + } + } + }); + } + path.get('source').replaceWith(t.stringLiteral('@storybook/test')); + } + }, + Identifier: (path) => { + if (path.node.name === 'jest') { + path.replaceWith(t.identifier('test')); + } + }, + }); + + let output = printCsf(csf).code; + try { + output = await prettier.format(output, { + ...(await prettier.resolveConfig(info.path)), + filepath: info.path, + }); + } catch (e) { + console.warn(`Failed applying prettier to ${info.path}.`); + } + return output; +} + +export const parser = 'tsx'; diff --git a/code/lib/codemod/src/transforms/storiesof-to-csf.js b/code/lib/codemod/src/transforms/storiesof-to-csf.js index 83fc7b058a20..993e9ff8c35b 100644 --- a/code/lib/codemod/src/transforms/storiesof-to-csf.js +++ b/code/lib/codemod/src/transforms/storiesof-to-csf.js @@ -265,14 +265,7 @@ export default async function transformer(file, api, options) { let output = source; try { - const prettierConfig = (await prettier.resolveConfig(file.path)) || { - printWidth: 100, - tabWidth: 2, - bracketSpacing: true, - trailingComma: 'es5', - singleQuote: true, - }; - + const prettierConfig = await prettier.resolveConfig(file.path); output = prettier.format(source, { ...prettierConfig, parser: jscodeshiftToPrettierParser(options.parser), diff --git a/code/lib/codemod/src/transforms/upgrade-deprecated-types.ts b/code/lib/codemod/src/transforms/upgrade-deprecated-types.ts index fb4cce571064..5751d139ba97 100644 --- a/code/lib/codemod/src/transforms/upgrade-deprecated-types.ts +++ b/code/lib/codemod/src/transforms/upgrade-deprecated-types.ts @@ -36,15 +36,10 @@ export default async function transform(info: FileInfo, api: API, options: { par let output = printCsf(csf).code; try { - const prettierConfig = (await prettier.resolveConfig(info.path)) || { - printWidth: 100, - tabWidth: 2, - bracketSpacing: true, - trailingComma: 'es5', - singleQuote: true, - }; - - output = await prettier.format(output, { ...prettierConfig, filepath: info.path }); + output = await prettier.format(output, { + ...(await prettier.resolveConfig(info.path)), + filepath: info.path, + }); } catch (e) { logger.log(`Failed applying prettier to ${info.path}.`); } diff --git a/code/lib/core-common/package.json b/code/lib/core-common/package.json index e6f4e629bc75..9a963332582d 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" @@ -48,10 +48,6 @@ "@storybook/csf-tools": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/types": "workspace:*", - "@types/find-cache-dir": "^3.2.1", - "@types/node": "^18.0.0", - "@types/node-fetch": "^2.6.4", - "@types/pretty-hrtime": "^1.0.0", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", "chalk": "^4.1.0", @@ -78,9 +74,13 @@ "util": "^0.12.4" }, "devDependencies": { + "@types/find-cache-dir": "^3.2.1", "@types/fs-extra": "^11.0.1", "@types/mock-fs": "^4.13.1", + "@types/node": "^18.0.0", + "@types/node-fetch": "^2.6.4", "@types/picomatch": "^2.3.0", + "@types/pretty-hrtime": "^1.0.0", "mock-fs": "^5.2.0", "slash": "^5.0.0", "type-fest": "~2.19", 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 923a06409968..8523d7224eda 100644 --- a/code/lib/core-common/src/js-package-manager/JsPackageManager.ts +++ b/code/lib/core-common/src/js-package-manager/JsPackageManager.ts @@ -8,7 +8,6 @@ import fs from 'fs'; import dedent from 'ts-dedent'; import { readFile, writeFile, readFileSync } from 'fs-extra'; import invariant from 'tiny-invariant'; -import { commandLog } from '../utils/log'; import type { PackageJson, PackageJsonWithDepsAndDevDeps } from './PackageJson'; import storybookPackagesVersions from '../versions'; import type { InstallationMetadata } from './types'; @@ -55,6 +54,9 @@ export abstract class JsPackageManager { basePath?: string ): Promise; + /** + * Get the INSTALLED version of a package from the package.json file + */ async getPackageVersion(packageName: string, basePath = this.cwd): Promise { const packageJSON = await this.getPackageJSON(packageName, basePath); return packageJSON ? packageJSON.version ?? null : null; @@ -128,21 +130,13 @@ export abstract class JsPackageManager { * Install dependencies listed in `package.json` */ public async installDependencies() { - let done = commandLog('Preparing to install dependencies'); - done(); - - logger.log(); - logger.log(); - - done = commandLog('Installing dependencies'); - + logger.log('Installing dependencies...'); logger.log(); try { await this.runInstall(); - done(); } catch (e) { - done('An error occurred while installing dependencies.'); + logger.error('An error occurred while installing dependencies.'); throw new HandledError(e); } } diff --git a/code/lib/core-common/src/utils/get-storybook-info.ts b/code/lib/core-common/src/utils/get-storybook-info.ts index dd462a6b0370..acf5aae77b91 100644 --- a/code/lib/core-common/src/utils/get-storybook-info.ts +++ b/code/lib/core-common/src/utils/get-storybook-info.ts @@ -14,9 +14,15 @@ export const rendererPackages: Record = { '@storybook/svelte': 'svelte', '@storybook/preact': 'preact', '@storybook/server': 'server', + // community (outside of monorepo) 'storybook-framework-qwik': 'qwik', 'storybook-solidjs': 'solid', + + /** + * @deprecated This is deprecated. + */ + '@storybook/vue': 'vue', }; export const frameworkPackages: Record = { diff --git a/code/lib/core-common/src/versions.ts b/code/lib/core-common/src/versions.ts index e1111f200be6..28ddbab5e59a 100644 --- a/code/lib/core-common/src/versions.ts +++ b/code/lib/core-common/src/versions.ts @@ -1,82 +1,82 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '8.0.0-alpha.17', - '@storybook/addon-actions': '8.0.0-alpha.17', - '@storybook/addon-backgrounds': '8.0.0-alpha.17', - '@storybook/addon-controls': '8.0.0-alpha.17', - '@storybook/addon-docs': '8.0.0-alpha.17', - '@storybook/addon-essentials': '8.0.0-alpha.17', - '@storybook/addon-highlight': '8.0.0-alpha.17', - '@storybook/addon-interactions': '8.0.0-alpha.17', - '@storybook/addon-jest': '8.0.0-alpha.17', - '@storybook/addon-links': '8.0.0-alpha.17', - '@storybook/addon-mdx-gfm': '8.0.0-alpha.17', - '@storybook/addon-measure': '8.0.0-alpha.17', - '@storybook/addon-outline': '8.0.0-alpha.17', - '@storybook/addon-storysource': '8.0.0-alpha.17', - '@storybook/addon-themes': '8.0.0-alpha.17', - '@storybook/addon-toolbars': '8.0.0-alpha.17', - '@storybook/addon-viewport': '8.0.0-alpha.17', - '@storybook/angular': '8.0.0-alpha.17', - '@storybook/blocks': '8.0.0-alpha.17', - '@storybook/builder-manager': '8.0.0-alpha.17', - '@storybook/builder-vite': '8.0.0-alpha.17', - '@storybook/builder-webpack5': '8.0.0-alpha.17', - '@storybook/channels': '8.0.0-alpha.17', - '@storybook/cli': '8.0.0-alpha.17', - '@storybook/client-logger': '8.0.0-alpha.17', - '@storybook/codemod': '8.0.0-alpha.17', - '@storybook/components': '8.0.0-alpha.17', - '@storybook/core-common': '8.0.0-alpha.17', - '@storybook/core-events': '8.0.0-alpha.17', - '@storybook/core-server': '8.0.0-alpha.17', - '@storybook/core-webpack': '8.0.0-alpha.17', - '@storybook/csf-plugin': '8.0.0-alpha.17', - '@storybook/csf-tools': '8.0.0-alpha.17', - '@storybook/docs-tools': '8.0.0-alpha.17', - '@storybook/ember': '8.0.0-alpha.17', - '@storybook/html': '8.0.0-alpha.17', - '@storybook/html-vite': '8.0.0-alpha.17', - '@storybook/html-webpack5': '8.0.0-alpha.17', - '@storybook/instrumenter': '8.0.0-alpha.17', - '@storybook/manager': '8.0.0-alpha.17', - '@storybook/manager-api': '8.0.0-alpha.17', - '@storybook/nextjs': '8.0.0-alpha.17', - '@storybook/node-logger': '8.0.0-alpha.17', - '@storybook/preact': '8.0.0-alpha.17', - '@storybook/preact-vite': '8.0.0-alpha.17', - '@storybook/preact-webpack5': '8.0.0-alpha.17', - '@storybook/preset-create-react-app': '8.0.0-alpha.17', - '@storybook/preset-html-webpack': '8.0.0-alpha.17', - '@storybook/preset-preact-webpack': '8.0.0-alpha.17', - '@storybook/preset-react-webpack': '8.0.0-alpha.17', - '@storybook/preset-server-webpack': '8.0.0-alpha.17', - '@storybook/preset-svelte-webpack': '8.0.0-alpha.17', - '@storybook/preset-vue3-webpack': '8.0.0-alpha.17', - '@storybook/preview': '8.0.0-alpha.17', - '@storybook/preview-api': '8.0.0-alpha.17', - '@storybook/react': '8.0.0-alpha.17', - '@storybook/react-dom-shim': '8.0.0-alpha.17', - '@storybook/react-vite': '8.0.0-alpha.17', - '@storybook/react-webpack5': '8.0.0-alpha.17', - '@storybook/router': '8.0.0-alpha.17', - '@storybook/server': '8.0.0-alpha.17', - '@storybook/server-webpack5': '8.0.0-alpha.17', - '@storybook/source-loader': '8.0.0-alpha.17', - '@storybook/svelte': '8.0.0-alpha.17', - '@storybook/svelte-vite': '8.0.0-alpha.17', - '@storybook/svelte-webpack5': '8.0.0-alpha.17', - '@storybook/sveltekit': '8.0.0-alpha.17', - '@storybook/telemetry': '8.0.0-alpha.17', - '@storybook/test': '8.0.0-alpha.17', - '@storybook/theming': '8.0.0-alpha.17', - '@storybook/types': '8.0.0-alpha.17', - '@storybook/vue3': '8.0.0-alpha.17', - '@storybook/vue3-vite': '8.0.0-alpha.17', - '@storybook/vue3-webpack5': '8.0.0-alpha.17', - '@storybook/web-components': '8.0.0-alpha.17', - '@storybook/web-components-vite': '8.0.0-alpha.17', - '@storybook/web-components-webpack5': '8.0.0-alpha.17', - sb: '8.0.0-alpha.17', - storybook: '8.0.0-alpha.17', + '@storybook/addon-a11y': '8.0.0-beta.2', + '@storybook/addon-actions': '8.0.0-beta.2', + '@storybook/addon-backgrounds': '8.0.0-beta.2', + '@storybook/addon-controls': '8.0.0-beta.2', + '@storybook/addon-docs': '8.0.0-beta.2', + '@storybook/addon-essentials': '8.0.0-beta.2', + '@storybook/addon-highlight': '8.0.0-beta.2', + '@storybook/addon-interactions': '8.0.0-beta.2', + '@storybook/addon-jest': '8.0.0-beta.2', + '@storybook/addon-links': '8.0.0-beta.2', + '@storybook/addon-mdx-gfm': '8.0.0-beta.2', + '@storybook/addon-measure': '8.0.0-beta.2', + '@storybook/addon-outline': '8.0.0-beta.2', + '@storybook/addon-storysource': '8.0.0-beta.2', + '@storybook/addon-themes': '8.0.0-beta.2', + '@storybook/addon-toolbars': '8.0.0-beta.2', + '@storybook/addon-viewport': '8.0.0-beta.2', + '@storybook/angular': '8.0.0-beta.2', + '@storybook/blocks': '8.0.0-beta.2', + '@storybook/builder-manager': '8.0.0-beta.2', + '@storybook/builder-vite': '8.0.0-beta.2', + '@storybook/builder-webpack5': '8.0.0-beta.2', + '@storybook/channels': '8.0.0-beta.2', + '@storybook/cli': '8.0.0-beta.2', + '@storybook/client-logger': '8.0.0-beta.2', + '@storybook/codemod': '8.0.0-beta.2', + '@storybook/components': '8.0.0-beta.2', + '@storybook/core-common': '8.0.0-beta.2', + '@storybook/core-events': '8.0.0-beta.2', + '@storybook/core-server': '8.0.0-beta.2', + '@storybook/core-webpack': '8.0.0-beta.2', + '@storybook/csf-plugin': '8.0.0-beta.2', + '@storybook/csf-tools': '8.0.0-beta.2', + '@storybook/docs-tools': '8.0.0-beta.2', + '@storybook/ember': '8.0.0-beta.2', + '@storybook/html': '8.0.0-beta.2', + '@storybook/html-vite': '8.0.0-beta.2', + '@storybook/html-webpack5': '8.0.0-beta.2', + '@storybook/instrumenter': '8.0.0-beta.2', + '@storybook/manager': '8.0.0-beta.2', + '@storybook/manager-api': '8.0.0-beta.2', + '@storybook/nextjs': '8.0.0-beta.2', + '@storybook/node-logger': '8.0.0-beta.2', + '@storybook/preact': '8.0.0-beta.2', + '@storybook/preact-vite': '8.0.0-beta.2', + '@storybook/preact-webpack5': '8.0.0-beta.2', + '@storybook/preset-create-react-app': '8.0.0-beta.2', + '@storybook/preset-html-webpack': '8.0.0-beta.2', + '@storybook/preset-preact-webpack': '8.0.0-beta.2', + '@storybook/preset-react-webpack': '8.0.0-beta.2', + '@storybook/preset-server-webpack': '8.0.0-beta.2', + '@storybook/preset-svelte-webpack': '8.0.0-beta.2', + '@storybook/preset-vue3-webpack': '8.0.0-beta.2', + '@storybook/preview': '8.0.0-beta.2', + '@storybook/preview-api': '8.0.0-beta.2', + '@storybook/react': '8.0.0-beta.2', + '@storybook/react-dom-shim': '8.0.0-beta.2', + '@storybook/react-vite': '8.0.0-beta.2', + '@storybook/react-webpack5': '8.0.0-beta.2', + '@storybook/router': '8.0.0-beta.2', + '@storybook/server': '8.0.0-beta.2', + '@storybook/server-webpack5': '8.0.0-beta.2', + '@storybook/source-loader': '8.0.0-beta.2', + '@storybook/svelte': '8.0.0-beta.2', + '@storybook/svelte-vite': '8.0.0-beta.2', + '@storybook/svelte-webpack5': '8.0.0-beta.2', + '@storybook/sveltekit': '8.0.0-beta.2', + '@storybook/telemetry': '8.0.0-beta.2', + '@storybook/test': '8.0.0-beta.2', + '@storybook/theming': '8.0.0-beta.2', + '@storybook/types': '8.0.0-beta.2', + '@storybook/vue3': '8.0.0-beta.2', + '@storybook/vue3-vite': '8.0.0-beta.2', + '@storybook/vue3-webpack5': '8.0.0-beta.2', + '@storybook/web-components': '8.0.0-beta.2', + '@storybook/web-components-vite': '8.0.0-beta.2', + '@storybook/web-components-webpack5': '8.0.0-beta.2', + sb: '8.0.0-beta.2', + storybook: '8.0.0-beta.2', }; diff --git a/code/lib/core-common/templates/base-preview-head.html b/code/lib/core-common/templates/base-preview-head.html index c19b0b5bdbcf..c4732d25281c 100644 --- a/code/lib/core-common/templates/base-preview-head.html +++ b/code/lib/core-common/templates/base-preview-head.html @@ -62,8 +62,17 @@ left: 0; right: 0; padding: 20px; - font-family: 'Nunito Sans', -apple-system, '.SFNSText-Regular', 'San Francisco', - BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-family: + 'Nunito Sans', + -apple-system, + '.SFNSText-Regular', + 'San Francisco', + BlinkMacSystemFont, + 'Segoe UI', + 'Helvetica Neue', + Helvetica, + Arial, + sans-serif; -webkit-font-smoothing: antialiased; overflow: auto; } @@ -257,7 +266,9 @@ .sb-argstableBlock-body { border-radius: 4px; - box-shadow: rgba(0, 0, 0, 0.1) 0 1px 3px 1px, rgba(0, 0, 0, 0.065) 0 0 0 1px; + box-shadow: + rgba(0, 0, 0, 0.1) 0 1px 3px 1px, + rgba(0, 0, 0, 0.065) 0 0 0 1px; } .sb-argstableBlock-body tr { background: transparent; diff --git a/code/lib/core-events/package.json b/code/lib/core-events/package.json index 0eefe17f46aa..e582c124ddd9 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 bf1f3b908125..1d00fe7feb56 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 9eb872991fce..29ed3fa3ac6f 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index 4ce9605ac273..8fbe1df9e82a 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 ac056fd90c57..a34e88502aa8 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Parse and manipulate CSF and Storybook config files", "keywords": [ "storybook" diff --git a/code/lib/docs-tools/package.json b/code/lib/docs-tools/package.json index f016071dd0e1..3db128a20be1 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 b838b5154515..d09086c92186 100644 --- a/code/lib/instrumenter/package.json +++ b/code/lib/instrumenter/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/instrumenter", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index a81d419741a6..8d8d7003ac71 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Core Storybook Manager API & Context", "keywords": [ "storybook" diff --git a/code/lib/manager-api/src/version.ts b/code/lib/manager-api/src/version.ts index 47c80ae4d286..2a37c88d9038 100644 --- a/code/lib/manager-api/src/version.ts +++ b/code/lib/manager-api/src/version.ts @@ -1 +1 @@ -export const version = '8.0.0-alpha.17'; +export const version = '8.0.0-beta.2'; diff --git a/code/lib/node-logger/package.json b/code/lib/node-logger/package.json index ece616f1a8ef..32b27fc56792 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/docs/storiesOf.md b/code/lib/preview-api/docs/storiesOf.md index 00f3b2945879..08e7009a9d0d 100644 --- a/code/lib/preview-api/docs/storiesOf.md +++ b/code/lib/preview-api/docs/storiesOf.md @@ -4,7 +4,7 @@ In Storybook 5.2 we introduced a simpler and more portable [Component Story Format](https://storybook.js.org/docs/react/api/csf), and all future tools and infrastructure will be oriented towards CSF. Therefore, we recommend migrating your stories out of `storiesOf` API, and even provide [automated tools to do this](#component-story-format-migration). -That said, the `storiesOf` API is not officially deprecated. For the time being we plan to support it for the foreseeable future. +That said, the `storiesOf` API is no longer actively maintained and has been removed as part of the Storybook 8 release. If you're working with a custom indexer or similar tooling that implements this API, we encourage using custom story indexers instead. Read our [Indexer API documentation](https://storybook.js.org/docs/api/main-config-indexers) for more information. ## storiesOf API diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 529a0c6742c3..4fd21320cb69 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/preview-api/src/modules/store/StoryStore.test.ts b/code/lib/preview-api/src/modules/store/StoryStore.test.ts index 7d796970b560..65f225c8ef6e 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.test.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.test.ts @@ -28,14 +28,6 @@ vi.mock('@storybook/global', async (importOriginal) => ({ vi.mock('@storybook/client-logger'); -const createGate = (): [Promise, (_?: any) => void] => { - let openGate = (_?: any) => {}; - const gate = new Promise((resolve) => { - openGate = resolve; - }); - return [gate, openGate]; -}; - const componentOneExports = { default: { title: 'Component One' }, a: { args: { foo: 'a' } }, @@ -435,22 +427,6 @@ describe('StoryStore', () => { './src/ComponentTwo.stories.js', ]); }); - - it('imports in batches', async () => { - const [gate, openGate] = createGate(); - const blockedImportFn = vi.fn(async (file) => { - await gate; - return importFn(file); - }); - const store = new StoryStore(storyIndex, blockedImportFn, projectAnnotations); - - const promise = store.loadAllCSFFiles({ batchSize: 1 }); - expect(blockedImportFn).toHaveBeenCalledTimes(1); - - openGate(); - await promise; - expect(blockedImportFn).toHaveBeenCalledTimes(3); - }); }); describe('extract', () => { diff --git a/code/lib/preview-api/src/modules/store/StoryStore.ts b/code/lib/preview-api/src/modules/store/StoryStore.ts index c779f13ddd2c..123ea5b984c8 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.ts @@ -41,9 +41,9 @@ import { prepareContext, } from './csf'; +// TODO -- what are reasonable values for these? const CSF_CACHE_SIZE = 1000; const STORY_CACHE_SIZE = 10000; -const EXTRACT_BATCH_SIZE = 20; export class StoryStore { public storyIndex: StoryIndexStore; @@ -127,32 +127,19 @@ export class StoryStore { return this.processCSFFileWithCache(moduleExports, importPath, title); } - async loadAllCSFFiles({ batchSize = EXTRACT_BATCH_SIZE } = {}): Promise< - StoryStore['cachedCSFFiles'] - > { - const importPaths = Object.entries(this.storyIndex.entries).map(([storyId, { importPath }]) => [ - importPath, - storyId, - ]); - - const loadInBatches = async ( - remainingImportPaths: typeof importPaths - ): Promise<{ importPath: Path; csfFile: CSFFile }[]> => { - if (remainingImportPaths.length === 0) return Promise.resolve([]); - - const csfFilePromiseList = remainingImportPaths - .slice(0, batchSize) - .map(async ([importPath, storyId]) => ({ - importPath, - csfFile: await this.loadCSFFileByStoryId(storyId), - })); - - const firstResults = await Promise.all(csfFilePromiseList); - const restResults = await loadInBatches(remainingImportPaths.slice(batchSize)); - return firstResults.concat(restResults); - }; + async loadAllCSFFiles(): Promise['cachedCSFFiles']> { + const importPaths: Record = {}; + Object.entries(this.storyIndex.entries).forEach(([storyId, { importPath }]) => { + importPaths[importPath] = storyId; + }); + + const list = await Promise.all( + Object.entries(importPaths).map(async ([importPath, storyId]) => ({ + importPath, + csfFile: await this.loadCSFFileByStoryId(storyId), + })) + ); - const list = await loadInBatches(importPaths); return list.reduce( (acc, { importPath, csfFile }) => { acc[importPath] = csfFile; diff --git a/code/lib/preview-api/src/modules/store/args.test.ts b/code/lib/preview-api/src/modules/store/args.test.ts index 0bfc4ab41a20..0d39874c766f 100644 --- a/code/lib/preview-api/src/modules/store/args.test.ts +++ b/code/lib/preview-api/src/modules/store/args.test.ts @@ -67,8 +67,9 @@ describe('mapArgsToTypes', () => { }); it('maps booleans', () => { + expect(mapArgsToTypes({ a: true }, { a: { type: booleanType } })).toStrictEqual({ a: true }); expect(mapArgsToTypes({ a: 'true' }, { a: { type: booleanType } })).toStrictEqual({ a: true }); - expect(mapArgsToTypes({ a: 'false' }, { a: { type: booleanType } })).toStrictEqual({ + expect(mapArgsToTypes({ a: false }, { a: { type: booleanType } })).toStrictEqual({ a: false, }); expect(mapArgsToTypes({ a: 'yes' }, { a: { type: booleanType } })).toStrictEqual({ a: false }); @@ -127,7 +128,7 @@ describe('mapArgsToTypes', () => { { key: { arr: ['1', '2'], - obj: { bool: 'true' }, + obj: { bool: true }, }, }, { @@ -157,7 +158,7 @@ describe('mapArgsToTypes', () => { key: [ { arr: ['1', '2'], - obj: { bool: 'true' }, + obj: { bool: true }, }, ], }, diff --git a/code/lib/preview-api/src/modules/store/args.ts b/code/lib/preview-api/src/modules/store/args.ts index 2634015bce76..7ce46f94a512 100644 --- a/code/lib/preview-api/src/modules/store/args.ts +++ b/code/lib/preview-api/src/modules/store/args.ts @@ -19,7 +19,7 @@ const map = (arg: unknown, argType: InputType): any => { case 'number': return Number(arg); case 'boolean': - return arg === 'true'; + return String(arg) === 'true'; case 'array': if (!type.value || !Array.isArray(arg)) return INCOMPATIBLE; return arg.reduce((acc, item, index) => { diff --git a/code/lib/preview-api/src/modules/store/csf/index.ts b/code/lib/preview-api/src/modules/store/csf/index.ts index 4b3aa8fa8c97..3ce7ca25109e 100644 --- a/code/lib/preview-api/src/modules/store/csf/index.ts +++ b/code/lib/preview-api/src/modules/store/csf/index.ts @@ -7,4 +7,4 @@ export * from './normalizeProjectAnnotations'; export * from './getValuesFromArgTypes'; export * from './composeConfigs'; export * from './stepRunners'; -export * from './testing-utils'; +export * from './portable-stories'; diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts new file mode 100644 index 000000000000..2ad7f7500f5b --- /dev/null +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.test.ts @@ -0,0 +1,168 @@ +import { describe, expect, vi, it } from 'vitest'; +import { composeStory, composeStories } from './portable-stories'; + +// Most integration tests for this functionality are located under renderers/react +describe('composeStory', () => { + const meta = { + title: 'Button', + parameters: { + firstAddon: true, + }, + args: { + label: 'Hello World', + primary: true, + }, + }; + + it('should return story with composed args and parameters', () => { + const Story = () => {}; + Story.args = { primary: true }; + Story.parameters = { + parameters: { + secondAddon: true, + }, + }; + + const composedStory = composeStory(Story, meta); + expect(composedStory.args).toEqual({ ...Story.args, ...meta.args }); + expect(composedStory.parameters).toEqual( + expect.objectContaining({ ...Story.parameters, ...meta.parameters }) + ); + }); + + it('should compose with a play function', async () => { + const spy = vi.fn(); + const Story = () => {}; + Story.args = { + primary: true, + }; + Story.play = async (context: any) => { + spy(context); + }; + + const composedStory = composeStory(Story, meta); + await composedStory.play!({ canvasElement: null }); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ + args: { + ...Story.args, + ...meta.args, + }, + }) + ); + }); + + it('should throw an error if Story is undefined', () => { + expect(() => { + // @ts-expect-error (invalid input) + composeStory(undefined, meta); + }).toThrow(); + }); + + describe('Id of the story', () => { + it('is exposed correctly when composeStories is used', () => { + const module = { + default: { + title: 'Example/Button', + }, + CSF3Primary: () => {}, + }; + const Primary = composeStory(module.CSF3Primary, module.default, {}); + expect(Primary.id).toBe('example-button--csf-3-primary'); + }); + it('is exposed correctly when composeStory is used and exportsName is passed', () => { + const module = { + default: { + title: 'Example/Button', + }, + CSF3Primary: () => {}, + }; + const Primary = composeStory(module.CSF3Primary, module.default, {}, {}, 'overwritten'); + expect(Primary.id).toBe('example-button--overwritten'); + }); + it("is not unique when composeStory is used and exportsName isn't passed", () => { + const Primary = composeStory({ render: () => {} }, {}); + expect(Primary.id).toContain('unknown'); + }); + }); +}); + +describe('composeStories', () => { + const composeStoryFn = vi.fn((v) => v); + const defaultAnnotations = { render: () => '' }; + it('should call composeStoryFn with stories', () => { + const composeStorySpy = vi.fn((v) => v); + const module = { + default: { + title: 'Button', + }, + StoryOne: () => {}, + StoryTwo: () => {}, + }; + const globalConfig = {}; + composeStories(module, globalConfig, composeStorySpy); + expect(composeStorySpy).toHaveBeenCalledWith( + module.StoryOne, + module.default, + globalConfig, + 'StoryOne' + ); + expect(composeStorySpy).toHaveBeenCalledWith( + module.StoryTwo, + module.default, + globalConfig, + 'StoryTwo' + ); + }); + + it('should not call composeStoryFn for non-story exports', () => { + const composeStorySpy = vi.fn((v) => v); + const module = { + default: { + title: 'Button', + excludeStories: /Data/, + }, + mockData: {}, + }; + composeStories(module, defaultAnnotations, composeStoryFn); + expect(composeStorySpy).not.toHaveBeenCalled(); + }); + + describe('non-story exports', () => { + it('should filter non-story exports with excludeStories', () => { + const StoryModuleWithNonStoryExports = { + default: { + title: 'Some/Component', + excludeStories: /.*Data/, + }, + LegitimateStory: () => 'hello world', + mockData: {}, + }; + + const result = composeStories( + StoryModuleWithNonStoryExports, + defaultAnnotations, + composeStoryFn + ); + expect(Object.keys(result)).not.toContain('mockData'); + }); + + it('should filter non-story exports with includeStories', () => { + const StoryModuleWithNonStoryExports = { + default: { + title: 'Some/Component', + includeStories: /.*Story/, + }, + LegitimateStory: () => 'hello world', + mockData: {}, + }; + + const result = composeStories( + StoryModuleWithNonStoryExports, + defaultAnnotations, + composeStoryFn + ); + expect(Object.keys(result)).not.toContain('mockData'); + }); + }); +}); diff --git a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts similarity index 73% rename from code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts rename to code/lib/preview-api/src/modules/store/csf/portable-stories.ts index b15a3345ed0c..91e4cdc365e1 100644 --- a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts @@ -12,15 +12,16 @@ import type { Parameters, ComposedStoryFn, StrictArgTypes, + ComposedStoryPlayContext, } from '@storybook/types'; import { HooksContext } from '../../../addons'; -import { composeConfigs } from '../composeConfigs'; -import { prepareContext, prepareStory } from '../prepareStory'; -import { normalizeStory } from '../normalizeStory'; -import { normalizeComponentAnnotations } from '../normalizeComponentAnnotations'; -import { getValuesFromArgTypes } from '../getValuesFromArgTypes'; -import { normalizeProjectAnnotations } from '../normalizeProjectAnnotations'; +import { composeConfigs } from './composeConfigs'; +import { prepareContext, prepareStory } from './prepareStory'; +import { normalizeStory } from './normalizeStory'; +import { normalizeComponentAnnotations } from './normalizeComponentAnnotations'; +import { getValuesFromArgTypes } from './getValuesFromArgTypes'; +import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = composeConfigs([]); @@ -74,24 +75,39 @@ export function composeStory = { + hooks: new HooksContext(), + globals: defaultGlobals, + args: { ...story.initialArgs }, + viewMode: 'story', + loaded: {}, + abortSignal: null as unknown as AbortSignal, + canvasElement: null, + ...story, + }; + const composedStory: ComposedStoryFn> = Object.assign( (extraArgs?: Partial) => { - const context: Partial = { - ...story, - hooks: new HooksContext(), - globals: defaultGlobals, - args: { ...story.initialArgs, ...extraArgs }, + const finalContext: StoryContext = { + ...context, + args: { ...context.initialArgs, ...extraArgs }, }; - return story.unboundStoryFn(prepareContext(context as StoryContext)); + return story.unboundStoryFn(prepareContext(finalContext)); }, { storyName, args: story.initialArgs as Partial, - play: story.playFunction as ComposedStoryPlayFn>, parameters: story.parameters as Parameters, argTypes: story.argTypes as StrictArgTypes, id: story.id, + play: story.playFunction + ? ((async (extraContext: ComposedStoryPlayContext) => + story.playFunction!({ + ...context, + ...extraContext, + })) as unknown as ComposedStoryPlayFn>) + : undefined, } ); diff --git a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.test.ts b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.test.ts deleted file mode 100644 index f0c811aa5abc..000000000000 --- a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, expect, vi, it } from 'vitest'; -import { composeStory, composeStories } from './index'; - -// Most integration tests for this functionality are located under renderers/react -describe('composeStory', () => { - const meta = { - title: 'Button', - parameters: { - firstAddon: true, - }, - args: { - label: 'Hello World', - primary: true, - }, - }; - - it('should return story with composed args and parameters', () => { - const Story = () => {}; - Story.args = { primary: true }; - Story.parameters = { - parameters: { - secondAddon: true, - }, - }; - - const composedStory = composeStory(Story, meta); - expect(composedStory.args).toEqual({ ...Story.args, ...meta.args }); - expect(composedStory.parameters).toEqual( - expect.objectContaining({ ...Story.parameters, ...meta.parameters }) - ); - }); - - it('should throw an error if Story is undefined', () => { - expect(() => { - // @ts-expect-error (invalid input) - composeStory(undefined, meta); - }).toThrow(); - }); -}); - -describe('composeStories', () => { - it('should call composeStoryFn with stories', () => { - const composeConfigFn = vi.fn((v) => v); - const module = { - default: { - title: 'Button', - }, - StoryOne: () => {}, - StoryTwo: () => {}, - }; - const globalConfig = {}; - composeStories(module, globalConfig, composeConfigFn); - expect(composeConfigFn).toHaveBeenCalledWith( - module.StoryOne, - module.default, - globalConfig, - 'StoryOne' - ); - expect(composeConfigFn).toHaveBeenCalledWith( - module.StoryTwo, - module.default, - globalConfig, - 'StoryTwo' - ); - }); - - it('should not call composeStoryFn for non-story exports', () => { - const composeConfigFn = vi.fn((v) => v); - const module = { - default: { - title: 'Button', - excludeStories: /Data/, - }, - mockData: {}, - }; - composeStories(module, {}, composeConfigFn); - expect(composeConfigFn).not.toHaveBeenCalled(); - }); -}); diff --git a/code/lib/preview/package.json b/code/lib/preview/package.json index f2bc5fb44111..6d8eb3ddf8b1 100644 --- a/code/lib/preview/package.json +++ b/code/lib/preview/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preview", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json index 2ef7c9690da6..2eff5f1f9b77 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/router/package.json b/code/lib/router/package.json index 7b09f8c119b6..926d45aa0ef5 100644 --- a/code/lib/router/package.json +++ b/code/lib/router/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/router", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Core Storybook Router", "keywords": [ "storybook" diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index 530f6ee8400b..978970834cc1 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Source loader", "keywords": [ "lib", diff --git a/code/lib/telemetry/package.json b/code/lib/telemetry/package.json index 2a664670c9a5..6792a8b06d75 100644 --- a/code/lib/telemetry/package.json +++ b/code/lib/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/telemetry", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 d9bde9ed0ec7..e50699b89c0b 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/test", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "", "keywords": [ "storybook" diff --git a/code/lib/test/src/expect.ts b/code/lib/test/src/expect.ts index da17eb52ff59..aa898bd7e36a 100644 --- a/code/lib/test/src/expect.ts +++ b/code/lib/test/src/expect.ts @@ -16,7 +16,7 @@ import { } from '@vitest/expect'; import * as matchers from '@testing-library/jest-dom/matchers'; import type { PromisifyObject } from './utils'; -import type { TestingLibraryMatchers } from '@testing-library/jest-dom/types/matchers'; +import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'; type Matchers = PromisifyObject> & TestingLibraryMatchers, Promise>; diff --git a/code/lib/theming/package.json b/code/lib/theming/package.json index a3e5d1e9e947..0f5d3d6289be 100644 --- a/code/lib/theming/package.json +++ b/code/lib/theming/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/theming", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Core Storybook Components", "keywords": [ "storybook" @@ -71,6 +71,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + }, "publishConfig": { "access": "public" }, diff --git a/code/lib/types/package.json b/code/lib/types/package.json index a098c859e24f..1849c1a5b2c1 100644 --- a/code/lib/types/package.json +++ b/code/lib/types/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/types", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Core Storybook TS Types", "keywords": [ "storybook" diff --git a/code/lib/types/src/modules/composedStory.ts b/code/lib/types/src/modules/composedStory.ts index 379bcb728908..5ce61bc678e8 100644 --- a/code/lib/types/src/modules/composedStory.ts +++ b/code/lib/types/src/modules/composedStory.ts @@ -55,7 +55,7 @@ export type ComposedStoryFn< TRenderer extends Renderer = Renderer, TArgs = Args, > = PartialArgsStoryFn & { - play: ComposedStoryPlayFn; + play: ComposedStoryPlayFn | undefined; args: TArgs; id: StoryId; storyName: string; diff --git a/code/nx.json b/code/nx.json index ad6c9a817fe7..b072caaa73ab 100644 --- a/code/nx.json +++ b/code/nx.json @@ -1,11 +1,5 @@ { "$schema": "./node_modules/nx/schemas/nx-schema.json", - "implicitDependencies": { - "package.json": { - "dependencies": "*", - "devDependencies": "*" - } - }, "pluginsConfig": { "@nrwl/js": { "analyzeSourceFiles": false @@ -47,10 +41,21 @@ "dependencies": true } ], - "outputs": ["{projectRoot}/dist"], + "outputs": [ + "{projectRoot}/dist" + ], "cache": true } }, "nxCloudAccessToken": "NGVmYTkxMmItYzY3OS00MjkxLTk1ZDktZDFmYTFmNmVlNGY4fHJlYWQ=", - "parallel": 1 + "namedInputs": { + "default": [ + "{projectRoot}/**/*", + "sharedGlobals" + ], + "sharedGlobals": [], + "production": [ + "default" + ] + } } diff --git a/code/package.json b/code/package.json index 415410b1cdff..ae2ef37fd85a 100644 --- a/code/package.json +++ b/code/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/root", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "private": true, "description": "Storybook root", "homepage": "https://storybook.js.org/", @@ -183,7 +183,7 @@ "@typescript-eslint/experimental-utils": "^5.62.0", "@typescript-eslint/parser": "^6.18.1", "@vitejs/plugin-react": "^3.0.1", - "@vitest/coverage-v8": "^1.0.1", + "@vitest/coverage-v8": "^1.2.2", "chromatic": "7.1.0", "concurrently": "^5.3.0", "cross-env": "^7.0.3", @@ -220,7 +220,7 @@ "util": "^0.12.4", "vite": "^4.0.0", "vite-plugin-turbosnap": "^1.0.1", - "vitest": "^1.0.1", + "vitest": "^1.2.2", "wait-on": "^7.0.1" }, "devDependencies": { diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json index 42d514786e9a..888077e98cd5 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 3cfece140836..5ed62bbe8a09 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 8c6536a98a27..9bf4e57057b3 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 8606dc343729..1178459c5875 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 b68652c4e502..429a852c91aa 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/a11y.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/a11y.snapshot index 0057e3dd70c4..6d22e73bf022 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/a11y.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/a11y.snapshot @@ -1,23 +1,18 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler a11y.json 1`] = ` -" export default { - title: \\"Addons/a11y\\", + title: "Addons/a11y", parameters: { options: { - selectedPanel: \\"storybook/a11y/panel\\" + selectedPanel: "storybook/a11y/panel" } } }; export const Label = { - name: \\"Label\\", + name: "Label", parameters: { server: { - id: \\"addons/a11y/label\\" + id: "addons/a11y/label" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/actions.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/actions.snapshot index b2f7434f4b0b..2317e70d92d2 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/actions.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/actions.snapshot @@ -1,30 +1,25 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler actions.json 1`] = ` -" export default { - title: \\"Addons/Actions\\", + title: "Addons/Actions", parameters: { options: { - selectedPanel: \\"storybook/actions/panel\\" + selectedPanel: "storybook/actions/panel" } } }; export const Multiple_actions_config = { - name: \\"Multiple actions + config\\", + name: "Multiple actions + config", parameters: { actions: [ - \\"click\\", - \\"contextmenu\\", + "click", + "contextmenu", { clearOnStoryChange: false } ], server: { - id: \\"addons/actions/story3\\" + id: "addons/actions/story3" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/backgrounds.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/backgrounds.snapshot index 12bac06c25cb..40a2bf3524a1 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/backgrounds.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/backgrounds.snapshot @@ -1,18 +1,15 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler backgrounds.json 1`] = ` -" export default { - title: \\"Addons/Backgrounds\\", + title: "Addons/Backgrounds", parameters: { backgrounds: [ { - name: \\"light\\", - value: \\"#eeeeee\\" + name: "light", + value: "#eeeeee" }, { - name: \\"dark\\", - value: \\"#222222\\", + name: "dark", + value: "#222222", default: true } ] @@ -20,12 +17,10 @@ export default { }; export const Story_1 = { - name: \\"Story 1\\", + name: "Story 1", parameters: { server: { - id: \\"addons/backgrounds/story1\\" + id: "addons/backgrounds/story1" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/controls.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/controls.snapshot index ccbda5cd711c..519ae472aca0 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/controls.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/controls.snapshot @@ -1,62 +1,57 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler controls.json 1`] = ` -" export default { - title: \\"Addons/Controls\\", + title: "Addons/Controls", parameters: { options: { - selectedPanel: \\"storybook/controls/panel\\" + selectedPanel: "storybook/controls/panel" } } }; export const Simple = { - name: \\"Simple\\", + name: "Simple", parameters: { server: { - id: \\"addons/controls/simple\\" + id: "addons/controls/simple" } }, args: { - name: \\"John Doe\\", - birthday: \\"1960-12-25T00:42:03.600Z\\", - favorite_color: \\"red\\", + name: "John Doe", + birthday: "1960-12-25T00:42:03.600Z", + favorite_color: "red", active: true, pets: 2, sports: [ - \\"football\\", - \\"baseball\\" + "football", + "baseball" ], - favorite_food: \\"Ice Cream\\", + favorite_food: "Ice Cream", other_things: { - hair: \\"Brown\\", - eyes: \\"Blue\\" + hair: "Brown", + eyes: "Blue" } }, argTypes: { birthday: { control: { - type: \\"date\\" + type: "date" } }, favorite_color: { control: { - type: \\"color\\" + type: "color" } }, favorite_food: { control: { - type: \\"select\\", + type: "select", options: { - hot_dog: \\"Hot Dog\\", - pizza: \\"Pizza\\", - burgers: \\"Burgers\\", - ice_cream: \\"Ice Cream\\" + hot_dog: "Hot Dog", + pizza: "Pizza", + burgers: "Burgers", + ice_cream: "Ice Cream" } } } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot index 14b34271465e..c8af3046016c 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/kitchen_sink.snapshot @@ -1,62 +1,57 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler kitchen_sink.json 1`] = ` -" export default { - title: \\"Kitchen Sink\\", + title: "Kitchen Sink", parameters: { backgrounds: [ { - name: \\"light\\", - value: \\"#eeeeee\\" + name: "light", + value: "#eeeeee" }, { - name: \\"dark\\", - value: \\"#222222\\", + name: "dark", + value: "#222222", default: true } ], options: { - selectedPanel: \\"storybook/a11y/panel\\" + selectedPanel: "storybook/a11y/panel" }, server: { params: { - color: \\"red\\" + color: "red" } } } }; export const Heading = { - name: \\"Heading\\", + name: "Heading", parameters: { actions: [ - \\"click\\", - \\"contextmenu\\", + "click", + "contextmenu", { clearOnStoryChange: false } ], server: { - id: \\"demo/heading\\", + id: "demo/heading", params: { - color: \\"orange\\" + color: "orange" } } }, args: { - name: \\"John Doe\\", + name: "John Doe", age: 44 } }; export const Button = { - name: \\"Button\\", + name: "Button", parameters: { server: { - id: \\"demo/button\\" + id: "demo/button" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/links.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/links.snapshot index 9c269a837cf1..140837fa3c52 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/links.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/links.snapshot @@ -1,18 +1,13 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler links.json 1`] = ` -" export default { - title: \\"Welcome\\", + title: "Welcome", }; export const Welcome = { - name: \\"Welcome\\", + name: "Welcome", parameters: { server: { - id: \\"welcome/welcome\\" + id: "welcome/welcome" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/multiple_stories.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/multiple_stories.snapshot index 756da4e91ab6..1e4c59f1ea35 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/multiple_stories.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/multiple_stories.snapshot @@ -1,36 +1,31 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler multiple_stories.json 1`] = ` -" export default { - title: \\"Demo\\", + title: "Demo", }; export const Heading = { - name: \\"Heading\\", + name: "Heading", parameters: { server: { - id: \\"demo/heading\\" + id: "demo/heading" } } }; export const Headings = { - name: \\"Headings\\", + name: "Headings", parameters: { server: { - id: \\"demo/headings\\" + id: "demo/headings" } } }; export const Button = { - name: \\"Button\\", + name: "Button", parameters: { server: { - id: \\"demo/button\\" + id: "demo/button" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.json b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.json index 60f6b720cf42..5084fdbe743f 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.json +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.json @@ -2,7 +2,8 @@ "title": "Params", "parameters": { "server": { - "params": { "color": "red" } + "params": { "color": "red" }, + "1200x600": { "viewport": { "width": 1200, "height": 600 } } } }, "stories": [ diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.snapshot index 1423ce39b9c5..0c6757db5a00 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params.snapshot @@ -1,28 +1,29 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler params.json 1`] = ` -" export default { - title: \\"Params\\", + title: "Params", parameters: { server: { params: { - color: \\"red\\" + color: "red" + }, + 1200x600: { + viewport: { + width: 1200, + height: 600 + } } } } }; export const Story = { - name: \\"Story\\", + name: "Story", parameters: { server: { - id: \\"params/story\\", + id: "params/story", params: { - message: \\"Hello World\\" + message: "Hello World" } } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params_override.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params_override.snapshot index 11a59bd1b516..7d6396a17ece 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params_override.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/params_override.snapshot @@ -1,29 +1,24 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`json-to-csf-compiler params_override.json 1`] = ` -" export default { - title: \\"Params\\", + title: "Params", parameters: { server: { params: { - color: \\"red\\" + color: "red" } } } }; export const Override = { - name: \\"Override\\", + name: "Override", parameters: { server: { - id: \\"params/override\\", + id: "params/override", params: { - message: \\"Hello World\\", - color: \\"green\\" + message: "Hello World", + color: "green" } } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yaml.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yaml.snapshot index cde9cda3d0bc..f22f2f5685d5 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yaml.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yaml.snapshot @@ -1,36 +1,31 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ya?ml-to-csf-compiler yaml.yaml 1`] = ` -" export default { - title: \\"Demo YAML\\", + title: "Demo YAML", }; export const Heading = { - name: \\"Heading\\", + name: "Heading", parameters: { server: { - id: \\"yaml/heading\\" + id: "yaml/heading" } } }; export const Headings = { - name: \\"Headings\\", + name: "Headings", parameters: { server: { - id: \\"yaml/headings\\" + id: "yaml/headings" } } }; export const Button = { - name: \\"Button\\", + name: "Button", parameters: { server: { - id: \\"yaml/button\\" + id: "yaml/button" } } }; -" -`; diff --git a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yml.snapshot b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yml.snapshot index 15c610e2fe6e..c95096f6c85e 100644 --- a/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yml.snapshot +++ b/code/presets/server-webpack/src/lib/compiler/__testfixtures__/yml.snapshot @@ -1,36 +1,31 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ya?ml-to-csf-compiler yml.yml 1`] = ` -" export default { - title: \\"Demo YML\\", + title: "Demo YML", }; export const Heading = { - name: \\"Heading\\", + name: "Heading", parameters: { server: { - id: \\"yaml/heading\\" + id: "yaml/heading" } } }; export const Headings = { - name: \\"Headings\\", + name: "Headings", parameters: { server: { - id: \\"yaml/headings\\" + id: "yaml/headings" } } }; export const Button = { - name: \\"Button\\", + name: "Button", parameters: { server: { - id: \\"yaml/button\\" + id: "yaml/button" } } }; -" -`; diff --git a/code/presets/server-webpack/vitest.config.ts b/code/presets/server-webpack/vitest.config.ts new file mode 100644 index 000000000000..045610882bc0 --- /dev/null +++ b/code/presets/server-webpack/vitest.config.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig, mergeConfig } from 'vitest/config'; +import { sep, posix } from 'path'; +import { vitestCommonConfig } from '../../vitest.workspace'; + +export default mergeConfig( + vitestCommonConfig, + defineConfig({ + test: { + environment: 'node', + name: __dirname.split(sep).slice(-2).join(posix.sep), + }, + }) +); diff --git a/code/presets/svelte-webpack/package.json b/code/presets/svelte-webpack/package.json index e2da349fe795..06dc39cc959e 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/presets/vue3-webpack/README.md b/code/presets/vue3-webpack/README.md index e361f8bc4222..4c2a2df89914 100644 --- a/code/presets/vue3-webpack/README.md +++ b/code/presets/vue3-webpack/README.md @@ -3,4 +3,4 @@ This package is a [preset](https://storybook.js.org/docs/addons/writing-presets#presets-api) that configures Storybook's webpack settings for handling Vue 3. It's an internal package that's not intended to be used directly by users. -- More info on [Storybook for Vue3](https://storybook.js.org/docs/get-started/why-storybook) \ No newline at end of file +- More info on [Storybook for Vue3](https://storybook.js.org/docs/get-started/why-storybook) diff --git a/code/presets/vue3-webpack/package.json b/code/presets/vue3-webpack/package.json index 8e9816532e90..1ee320a0a21f 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "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 d7f81ee9af31..21989c8648d9 100644 --- a/code/renderers/html/package.json +++ b/code/renderers/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook HTML renderer", "keywords": [ "storybook" diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index b6f6be41ad07..4b22d0909b43 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook Preact renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index f1459a0a511f..1aab923414bf 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook React renderer", "keywords": [ "storybook" diff --git a/code/renderers/react/src/__test__/__snapshots__/internals.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap similarity index 100% rename from code/renderers/react/src/__test__/__snapshots__/internals.test.tsx.snap rename to code/renderers/react/src/__test__/__snapshots__/portable-stories.test.tsx.snap diff --git a/code/renderers/react/src/__test__/internals.test.tsx b/code/renderers/react/src/__test__/internals.test.tsx deleted file mode 100644 index 8bed037619be..000000000000 --- a/code/renderers/react/src/__test__/internals.test.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import React from 'react'; -import { addons } from '@storybook/preview-api'; -import { cleanup, render, screen } from '@testing-library/react'; - -import { composeStories, composeStory } from '..'; - -import * as stories from './Button.stories'; - -const { CSF2StoryWithParamsAndDecorator } = composeStories(stories); - -it('returns composed args including default values from argtypes', () => { - expect(CSF2StoryWithParamsAndDecorator.args).toEqual({ - ...stories.CSF2StoryWithParamsAndDecorator.args, - }); -}); - -it('returns composed parameters from story', () => { - expect(CSF2StoryWithParamsAndDecorator.parameters).toEqual( - expect.objectContaining({ - ...stories.CSF2StoryWithParamsAndDecorator.parameters, - }) - ); -}); - -describe('Id of the story', () => { - it('is exposed correctly when composeStories is used', () => { - expect(CSF2StoryWithParamsAndDecorator.id).toBe( - 'example-button--csf-2-story-with-params-and-decorator' - ); - }); - it('is exposed correctly when composeStory is used and exportsName is passed', () => { - const exportName = Object.entries(stories).filter( - ([_, story]) => story === stories.CSF3Primary - )[0][0]; - const Primary = composeStory(stories.CSF3Primary, stories.default, {}, exportName); - expect(Primary.id).toBe('example-button--csf-3-primary'); - }); - it("is not unique when composeStory is used and exportsName isn't passed", () => { - const Primary = composeStory(stories.CSF3Primary, stories.default); - expect(Primary.id).toContain('unknown'); - }); -}); - -// common in addons that need to communicate between manager and preview -it('should pass with decorators that need addons channel', () => { - const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { - decorators: [ - (StoryFn: any) => { - addons.getChannel(); - return ; - }, - ], - }); - render(Hello world); - const buttonElement = screen.getByText(/Hello world/i); - expect(buttonElement).not.toBeNull(); - cleanup(); -}); - -describe('Unsupported formats', () => { - it('should throw error if story is undefined', () => { - const UnsupportedStory = () =>
hello world
; - UnsupportedStory.story = { parameters: {} }; - - const UnsupportedStoryModule: any = { - default: {}, - UnsupportedStory: undefined, - }; - - expect(() => { - composeStories(UnsupportedStoryModule); - }).toThrow(); - }); -}); - -describe('non-story exports', () => { - it('should filter non-story exports with excludeStories', () => { - const StoryModuleWithNonStoryExports = { - default: { - title: 'Some/Component', - excludeStories: /.*Data/, - }, - LegitimateStory: () =>
hello world
, - mockData: {}, - }; - - const result = composeStories(StoryModuleWithNonStoryExports); - expect(Object.keys(result)).not.toContain('mockData'); - }); - - it('should filter non-story exports with includeStories', () => { - const StoryModuleWithNonStoryExports = { - default: { - title: 'Some/Component', - includeStories: /.*Story/, - }, - LegitimateStory: () =>
hello world
, - mockData: {}, - }; - - const result = composeStories(StoryModuleWithNonStoryExports); - expect(Object.keys(result)).not.toContain('mockData'); - }); -}); - -// Batch snapshot testing -const testCases = Object.values(composeStories(stories)).map((Story) => [ - // The ! is necessary in Typescript only, as the property is part of a partial type - Story.storyName!, - Story, -]); -it.each(testCases)('Renders %s story', async (_storyName, Story) => { - cleanup(); - const tree = await render(); - expect(tree.baseElement).toMatchSnapshot(); -}); diff --git a/code/renderers/react/src/__test__/composeStories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx similarity index 81% rename from code/renderers/react/src/__test__/composeStories.test.tsx rename to code/renderers/react/src/__test__/portable-stories.test.tsx index a8cc360e751b..afa0b70142e4 100644 --- a/code/renderers/react/src/__test__/composeStories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -1,11 +1,11 @@ import { vi, it, expect, afterEach, describe } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; - +import { addons } from '@storybook/preview-api'; import type { Meta } from '@storybook/react'; import { expectTypeOf } from 'expect-type'; -import { setProjectAnnotations, composeStories, composeStory } from '..'; +import { setProjectAnnotations, composeStories, composeStory } from '..'; import type { Button } from './Button'; import * as stories from './Button.stories'; @@ -99,13 +99,28 @@ describe('CSF3', () => { const { container } = render(); - await CSF3InputFieldFilled.play({ canvasElement: container }); + await CSF3InputFieldFilled.play!({ canvasElement: container }); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); }); }); +// common in addons that need to communicate between manager and preview +it('should pass with decorators that need addons channel', () => { + const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { + decorators: [ + (StoryFn: any) => { + addons.getChannel(); + return StoryFn(); + }, + ], + }); + render(Hello world); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); +}); + describe('ComposeStories types', () => { // this file tests Typescript types that's why there are no assertions it('Should support typescript operators', () => { @@ -122,3 +137,11 @@ describe('ComposeStories types', () => { }).toMatchTypeOf(); }); }); + +// Batch snapshot testing +const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); +it.each(testCases)('Renders %s story', async (_storyName, Story) => { + cleanup(); + const tree = await render(); + expect(tree.baseElement).toMatchSnapshot(); +}); diff --git a/code/renderers/react/src/index.ts b/code/renderers/react/src/index.ts index 07a118109716..bad002de9370 100644 --- a/code/renderers/react/src/index.ts +++ b/code/renderers/react/src/index.ts @@ -4,7 +4,7 @@ import './globals'; export * from './public-types'; -export * from './testing-api'; +export * from './portable-stories'; // optimization: stop HMR propagation in webpack if (typeof module !== 'undefined') module?.hot?.decline(); diff --git a/code/renderers/react/src/testing-api.ts b/code/renderers/react/src/portable-stories.ts similarity index 100% rename from code/renderers/react/src/testing-api.ts rename to code/renderers/react/src/portable-stories.ts diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index 66718f159567..5cca1880cb39 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook Server renderer", "keywords": [ "storybook" diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index f39db3aabb48..e1eb7d92163d 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook Svelte renderer", "keywords": [ "storybook" diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index b2f604d491d4..1803817d1357 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook Vue 3 renderer", "keywords": [ "storybook" diff --git a/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts b/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts index 1b14978a9976..239416df5c35 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/Button.stories.ts @@ -53,7 +53,6 @@ const getCaptionForLocale = (locale: string) => { export const CSF2StoryWithLocale: CSF2Story = (args, { globals }) => ({ components: { Button }, setup() { - console.log({ globals }); const label = getCaptionForLocale(globals.locale); return { args: { ...args, label } }; }, diff --git a/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/internals.test.tsx.snap b/code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap similarity index 100% rename from code/renderers/vue3/src/__tests__/composeStories/__snapshots__/internals.test.tsx.snap rename to code/renderers/vue3/src/__tests__/composeStories/__snapshots__/portable-stories.test.ts.snap diff --git a/code/renderers/vue3/src/__tests__/composeStories/internals.test.tsx b/code/renderers/vue3/src/__tests__/composeStories/internals.test.tsx deleted file mode 100644 index 3be07251c9c0..000000000000 --- a/code/renderers/vue3/src/__tests__/composeStories/internals.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react'; -import { addons } from '@storybook/preview-api'; -import { render, screen } from '@testing-library/vue'; -import { describe, it, expect } from 'vitest'; - -import { composeStories, composeStory } from '../../testing-api'; - -import * as stories from './Button.stories'; - -const { CSF2StoryWithParamsAndDecorator } = composeStories(stories); - -it('returns composed args including default values from argtypes', () => { - expect({ - ...stories.default.args, - ...CSF2StoryWithParamsAndDecorator.args, - }).toEqual(expect.objectContaining(CSF2StoryWithParamsAndDecorator.args)); -}); - -it('returns composed parameters from story', () => { - expect(CSF2StoryWithParamsAndDecorator.parameters).toEqual( - expect.objectContaining({ - ...stories.CSF2StoryWithParamsAndDecorator.parameters, - }) - ); -}); - -describe('Id of the story', () => { - it('is exposed correctly when composeStories is used', () => { - expect(CSF2StoryWithParamsAndDecorator.id).toBe( - 'example-button--csf-2-story-with-params-and-decorator' - ); - }); - it('is exposed correctly when composeStory is used and exportsName is passed', () => { - const exportName = Object.entries(stories).filter( - ([_, story]) => story === stories.CSF3Primary - )[0][0]; - const Primary = composeStory(stories.CSF3Primary, stories.default, {}, exportName); - expect(Primary.id).toBe('example-button--csf-3-primary'); - }); - it("is not unique when composeStory is used and exportsName isn't passed", () => { - const Primary = composeStory(stories.CSF3Primary, stories.default); - expect(Primary.id).toContain('unknown'); - }); -}); - -// common in addons that need to communicate between manager and preview -it('should pass with decorators that need addons channel', () => { - const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { - decorators: [ - (StoryFn: any) => { - addons.getChannel(); - return StoryFn(); - }, - ], - }); - render(PrimaryWithChannels({ label: 'Hello world' })); - const buttonElement = screen.getByText(/Hello world/i); - expect(buttonElement).not.toBeNull(); -}); - -describe('Unsupported formats', () => { - it('should throw error if story is undefined', () => { - const UnsupportedStory = () =>
hello world
; - UnsupportedStory.story = { parameters: {} }; - - const UnsupportedStoryModule: any = { - default: {}, - UnsupportedStory: undefined, - }; - - expect(() => { - composeStories(UnsupportedStoryModule); - }).toThrow(); - }); -}); - -describe('non-story exports', () => { - it('should filter non-story exports with excludeStories', () => { - const StoryModuleWithNonStoryExports = { - default: { - title: 'Some/Component', - excludeStories: /.*Data/, - }, - LegitimateStory: () =>
hello world
, - mockData: {}, - }; - - const result = composeStories(StoryModuleWithNonStoryExports); - expect(Object.keys(result)).not.toContain('mockData'); - }); - - it('should filter non-story exports with includeStories', () => { - const StoryModuleWithNonStoryExports = { - default: { - title: 'Some/Component', - includeStories: /.*Story/, - }, - LegitimateStory: () =>
hello world
, - mockData: {}, - }; - - const result = composeStories(StoryModuleWithNonStoryExports); - expect(Object.keys(result)).not.toContain('mockData'); - }); -}); - -// Batch snapshot testing -const testCases = Object.values(composeStories(stories)).map((Story) => [ - // The ! is necessary in Typescript only, as the property is part of a partial type - Story.storyName!, - Story, -]); -it.each(testCases)('Renders %s story', async (_storyName, Story) => { - if (typeof Story === 'string' || _storyName === 'CSF2StoryWithParamsAndDecorator') { - return; - } - - await new Promise((resolve) => setTimeout(resolve, 0)); - - const tree = await render(Story()); - expect(tree.baseElement).toMatchSnapshot(); -}); diff --git a/code/renderers/vue3/src/__tests__/composeStories/composeStories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts similarity index 77% rename from code/renderers/vue3/src/__tests__/composeStories/composeStories.test.ts rename to code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts index cb2372a4c4e9..4c541e1c4536 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/composeStories.test.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts @@ -1,11 +1,13 @@ /// ; import { it, expect, vi, describe } from 'vitest'; import { render, screen } from '@testing-library/vue'; +import { addons } from '@storybook/preview-api'; import { expectTypeOf } from 'expect-type'; import type { Meta } from '@storybook/vue3'; + import * as stories from './Button.stories'; import type Button from './Button.vue'; -import { composeStories, composeStory, setProjectAnnotations } from '../../testing-api'; +import { composeStories, composeStory, setProjectAnnotations } from '../../portable-stories'; // example with composeStories, returns an object with all stories composed with args/decorators const { CSF3Primary } = composeStories(stories); @@ -84,13 +86,28 @@ describe('CSF3', () => { const { container } = render(CSF3InputFieldFilled()); - await CSF3InputFieldFilled.play({ canvasElement: container as HTMLElement }); + await CSF3InputFieldFilled.play!({ canvasElement: container as HTMLElement }); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); }); }); +// common in addons that need to communicate between manager and preview +it('should pass with decorators that need addons channel', () => { + const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { + decorators: [ + (StoryFn: any) => { + addons.getChannel(); + return StoryFn(); + }, + ], + }); + render(PrimaryWithChannels({ label: 'Hello world' })); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); +}); + describe('ComposeStories types', () => { it('Should support typescript operators', () => { type ComposeStoriesParam = Parameters[0]; @@ -106,3 +123,16 @@ describe('ComposeStories types', () => { }).toMatchTypeOf(); }); }); + +// Batch snapshot testing +const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); +it.each(testCases)('Renders %s story', async (_storyName, Story) => { + if (typeof Story === 'string' || _storyName === 'CSF2StoryWithParamsAndDecorator') { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 0)); + + const tree = await render(Story()); + expect(tree.baseElement).toMatchSnapshot(); +}); diff --git a/code/renderers/vue3/src/index.ts b/code/renderers/vue3/src/index.ts index d3c7431a00e1..87e6074fb708 100644 --- a/code/renderers/vue3/src/index.ts +++ b/code/renderers/vue3/src/index.ts @@ -4,7 +4,7 @@ import './globals'; export { setup } from './render'; export * from './public-types'; -export * from './testing-api'; +export * from './portable-stories'; // optimization: stop HMR propagation in webpack try { diff --git a/code/renderers/vue3/src/testing-api.ts b/code/renderers/vue3/src/portable-stories.ts similarity index 100% rename from code/renderers/vue3/src/testing-api.ts rename to code/renderers/vue3/src/portable-stories.ts diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index 6b3e4df52625..ff50940d4c28 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.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook web-components renderer", "keywords": [ "lit", diff --git a/code/ui/.storybook/preview.tsx b/code/ui/.storybook/preview.tsx index 8d2bfc9aa908..1054d62a5d59 100644 --- a/code/ui/.storybook/preview.tsx +++ b/code/ui/.storybook/preview.tsx @@ -11,7 +11,6 @@ import { useTheme, } from '@storybook/theming'; import { useArgs, DocsContext as DocsContextProps } from '@storybook/preview-api'; -import { Symbols } from '@storybook/components'; import type { PreviewWeb } from '@storybook/preview-api'; import type { ReactRenderer } from '@storybook/react'; import type { Channel } from '@storybook/channels'; diff --git a/code/ui/blocks/package.json b/code/ui/blocks/package.json index c791b0321446..0369a178e992 100644 --- a/code/ui/blocks/package.json +++ b/code/ui/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/blocks", - "version": "8.0.0-alpha.17", + "version": "8.0.0-beta.2", "description": "Storybook Doc Blocks", "keywords": [ "storybook" @@ -51,7 +51,7 @@ "@storybook/csf": "^0.1.2", "@storybook/docs-tools": "workspace:*", "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.2.3", + "@storybook/icons": "^1.2.5", "@storybook/manager-api": "workspace:*", "@storybook/preview-api": "workspace:*", "@storybook/theming": "workspace:*", @@ -78,6 +78,14 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + }, "publishConfig": { "access": "public" }, diff --git a/code/ui/blocks/src/components/ArgsTable/ArgValue.tsx b/code/ui/blocks/src/components/ArgsTable/ArgValue.tsx index 131aab285a1b..7d549c5f45a7 100644 --- a/code/ui/blocks/src/components/ArgsTable/ArgValue.tsx +++ b/code/ui/blocks/src/components/ArgsTable/ArgValue.tsx @@ -3,8 +3,9 @@ import React, { useState } from 'react'; import memoize from 'memoizerific'; import uniq from 'lodash/uniq.js'; import { styled } from '@storybook/theming'; -import { WithTooltipPure, Icons, SyntaxHighlighter, codeCommon } from '@storybook/components'; +import { WithTooltipPure, SyntaxHighlighter, codeCommon } from '@storybook/components'; import type { PropSummaryValue } from './types'; +import { ChevronSmallDownIcon, ChevronSmallUpIcon } from '@storybook/icons'; interface ArgValueProps { value?: PropSummaryValue; @@ -86,10 +87,11 @@ const Detail = styled.div<{ width: string }>(({ theme, width }) => ({ }, })); -const ArrowIcon = styled(Icons)({ - height: 10, - width: 10, - minWidth: 10, +const ChevronUpIcon = styled(ChevronSmallUpIcon)({ + marginLeft: 4, +}); + +const ChevronDownIcon = styled(ChevronSmallDownIcon)({ marginLeft: 4, }); @@ -176,7 +178,7 @@ const ArgSummary: FC = ({ value, initialExpandedArgs }) => { > {summaryAsString} - + {isOpen ? : } ); diff --git a/code/ui/blocks/src/components/ArgsTable/SectionRow.tsx b/code/ui/blocks/src/components/ArgsTable/SectionRow.tsx index b23f225584db..450075b56aa7 100644 --- a/code/ui/blocks/src/components/ArgsTable/SectionRow.tsx +++ b/code/ui/blocks/src/components/ArgsTable/SectionRow.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import React, { useState } from 'react'; import { transparentize, lighten } from 'polished'; import { styled } from '@storybook/theming'; -import { Icons } from '@storybook/components'; +import { ChevronDownIcon, ChevronRightIcon } from '@storybook/icons'; type Level = 'section' | 'subsection'; @@ -14,7 +14,21 @@ export interface SectionRowProps { colSpan: number; } -const ExpanderIcon = styled(Icons)(({ theme }) => ({ +const ExpanderIconDown = styled(ChevronDownIcon)(({ theme }) => ({ + marginRight: 8, + marginLeft: -10, + marginTop: -2, // optical alignment + height: 12, + width: 12, + color: + theme.base === 'light' + ? transparentize(0.25, theme.color.defaultText) + : transparentize(0.3, theme.color.defaultText), + border: 'none', + display: 'inline-block', +})); + +const ExpanderIconRight = styled(ChevronRightIcon)(({ theme }) => ({ marginRight: 8, marginLeft: -10, marginTop: -2, // optical alignment @@ -100,7 +114,6 @@ export const SectionRow: FC = ({ // @ts-expect-error (Converted from ts-ignore) const itemCount = children?.length || 0; const caption = level === 'subsection' ? `${itemCount} item${itemCount !== 1 ? 's' : ''}` : ''; - const icon = expanded ? 'arrowdown' : 'arrowright'; const helperText = `${expanded ? 'Hide' : 'Show'} ${ level === 'subsection' ? itemCount : label @@ -114,7 +127,7 @@ export const SectionRow: FC = ({ {helperText} - + {expanded ? : } {label} diff --git a/code/ui/blocks/src/components/IconGallery.stories.tsx b/code/ui/blocks/src/components/IconGallery.stories.tsx index b14417402fdd..0dc183e79311 100644 --- a/code/ui/blocks/src/components/IconGallery.stories.tsx +++ b/code/ui/blocks/src/components/IconGallery.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Icons as ExampleIcon } from '@storybook/components'; import { IconItem, IconGallery } from './IconGallery'; +import { AddIcon, FaceHappyIcon, HomeIcon, SubtractIcon } from '@storybook/icons'; export default { component: IconGallery, @@ -9,16 +9,16 @@ export default { export const DefaultStyle = () => ( - + - + - + - + example diff --git a/code/ui/blocks/src/controls/Color.tsx b/code/ui/blocks/src/controls/Color.tsx index c9bf13a526c9..ff2eae90a49b 100644 --- a/code/ui/blocks/src/controls/Color.tsx +++ b/code/ui/blocks/src/controls/Color.tsx @@ -4,10 +4,11 @@ import { HexColorPicker, HslaStringColorPicker, RgbaStringColorPicker } from 're import convert from 'color-convert'; import throttle from 'lodash/throttle.js'; import { styled } from '@storybook/theming'; -import { TooltipNote, WithTooltip, Form, Icons } from '@storybook/components'; +import { TooltipNote, WithTooltip, Form } from '@storybook/components'; import type { ControlProps, ColorValue, ColorConfig, PresetColor } from './types'; import { getControlId } from './helpers'; +import { MarkupIcon } from '@storybook/icons'; const Wrapper = styled.div({ position: 'relative', @@ -74,7 +75,7 @@ const Input = styled(Form.Input)(({ theme }) => ({ fontFamily: theme.typography.fonts.base, })); -const ToggleIcon = styled(Icons)(({ theme }) => ({ +const ToggleIcon = styled(MarkupIcon)(({ theme }) => ({ position: 'absolute', zIndex: 1, top: 6, @@ -358,7 +359,7 @@ export const ColorControl: FC = ({ onFocus={(e: FocusEvent) => e.target.select()} placeholder="Choose color..." /> - {value ? : null} + {value ? : null}
); }; diff --git a/code/ui/blocks/src/controls/Object.tsx b/code/ui/blocks/src/controls/Object.tsx index c6b94fc23951..d6c4d2328af0 100644 --- a/code/ui/blocks/src/controls/Object.tsx +++ b/code/ui/blocks/src/controls/Object.tsx @@ -3,8 +3,8 @@ import cloneDeep from 'lodash/cloneDeep.js'; import type { ComponentProps, SyntheticEvent, FC, FocusEvent } from 'react'; import React, { useCallback, useMemo, useState, useEffect, useRef } from 'react'; import { styled, useTheme, type Theme } from '@storybook/theming'; -import { Form, Icons, IconButton, Button } from '@storybook/components'; -import { EyeCloseIcon, EyeIcon } from '@storybook/icons'; +import { Form, IconButton, Button } from '@storybook/components'; +import { AddIcon, EyeCloseIcon, EyeIcon, SubtractIcon } from '@storybook/icons'; import { JsonTree, getObjectType } from './react-editable-json-tree'; import { getControlId, getControlSetterButtonId } from './helpers'; import type { ControlProps, ObjectValue, ObjectConfig } from './types'; @@ -133,7 +133,7 @@ const ButtonInline = styled.button<{ primary?: boolean }>(({ theme, primary }) = order: primary ? 'initial' : 9, })); -const ActionIcon = styled(Icons)<{ disabled?: boolean }>(({ theme, icon, disabled }) => ({ +const ActionAddIcon = styled(AddIcon)<{ disabled?: boolean }>(({ theme, disabled }) => ({ display: 'inline-block', verticalAlign: 'middle', width: 15, @@ -142,11 +142,22 @@ const ActionIcon = styled(Icons)<{ disabled?: boolean }>(({ theme, icon, disable marginLeft: 5, cursor: disabled ? 'not-allowed' : 'pointer', color: theme.textMutedColor, - '&:hover': disabled - ? {} - : { - color: icon === 'subtract' ? theme.color.negative : theme.color.ancillary, - }, + '&:hover': disabled ? {} : { color: theme.color.ancillary }, + 'svg + &': { + marginLeft: 0, + }, +})); + +const ActionSubstractIcon = styled(SubtractIcon)<{ disabled?: boolean }>(({ theme, disabled }) => ({ + display: 'inline-block', + verticalAlign: 'middle', + width: 15, + height: 15, + padding: 3, + marginLeft: 5, + cursor: disabled ? 'not-allowed' : 'pointer', + color: theme.textMutedColor, + '&:hover': disabled ? {} : { color: theme.color.negative }, 'svg + &': { marginLeft: 0, }, @@ -309,8 +320,8 @@ export const ObjectControl: FC = ({ name, value, onChange }) => { Save } - plusMenuElement={} - minusMenuElement={} + plusMenuElement={} + minusMenuElement={} inputElement={(_: any, __: any, ___: any, key: string) => key ? : } diff --git a/code/ui/blocks/src/controls/options/Select.tsx b/code/ui/blocks/src/controls/options/Select.tsx index e2f9835a7c47..574c604b180b 100644 --- a/code/ui/blocks/src/controls/options/Select.tsx +++ b/code/ui/blocks/src/controls/options/Select.tsx @@ -3,12 +3,12 @@ import React from 'react'; import { styled } from '@storybook/theming'; import type { CSSObject } from '@storybook/theming'; import { logger } from '@storybook/client-logger'; -import { Icons } from '@storybook/components'; import type { ControlProps, OptionsSelection, NormalizedOptionsConfig } from '../types'; import { selectedKey, selectedKeys, selectedValues } from './helpers'; import { getControlId } from '../helpers'; +import { ChevronSmallDownIcon } from '@storybook/icons'; const styleResets: CSSObject = { // resets @@ -102,7 +102,7 @@ const SingleSelect: FC = ({ name, value, options, onChange }) => { return ( - +