diff --git a/.circleci/config.yml b/.circleci/config.yml index 1cd268cbbe1b..b23edd89ef69 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ executors: default: "small" working_directory: /tmp/storybook docker: - - image: cimg/node:18.18.0 + - image: cimg/node:18.19.1 environment: NODE_OPTIONS: --max_old_space_size=6144 resource_class: <> @@ -30,7 +30,7 @@ executors: default: "small" working_directory: /tmp/storybook docker: - - image: cimg/node:18.18.0-browsers + - image: cimg/node:18.19.1-browsers environment: NODE_OPTIONS: --max_old_space_size=6144 resource_class: <> @@ -66,6 +66,7 @@ orbs: browser-tools: circleci/browser-tools@1.4.1 discord: antonioned/discord@0.1.0 codecov: codecov/codecov@3.2.4 + nx: nrwl/nx@1.6.2 commands: cancel-workflow-on-failure: @@ -190,6 +191,9 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - nx/set-shas: + main-branch-name: "next" + workflow-name: << pipeline.parameters.workflow >> - run: name: Check command: | diff --git a/.nvmrc b/.nvmrc index 87ec8842b158..3c5535cf60a0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.18.2 +18.19.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e731ddb2702c..c78e29fa62ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 8.0.4 + +- Addon Docs: Support Stencil based display names in source snippets - [#26592](https://github.com/storybookjs/storybook/pull/26592), thanks @yannbf! +- CLI: Instruct the correct auto-migration command - [#26515](https://github.com/storybookjs/storybook/pull/26515), thanks @ndelangen! +- CLI: Throw an error when running upgrade command in incorrect cwd - [#26585](https://github.com/storybookjs/storybook/pull/26585), thanks @yannbf! + +## 8.0.3 + +- Bug: Remove redundant component check, as we auto-generate titles from the file system - [#26516](https://github.com/storybookjs/storybook/pull/26516), thanks @kasperpeulen! +- UI: Replace the icon prop in the Manager API - [#26477](https://github.com/storybookjs/storybook/pull/26477), thanks @cdedreuille! + ## 8.0.2 - Addon Docs: Fix [Object object] displayName in some JSX components - [#26566](https://github.com/storybookjs/storybook/pull/26566), thanks @yannbf! diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 428d19caca31..a956bce6d243 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,20 @@ +## 8.1.0-alpha.5 + +- Addon-docs: Fix `react-dom/server` imports breaking stories and docs - [#26557](https://github.com/storybookjs/storybook/pull/26557), thanks @JReinhold! +- Args: Add possibility to mark controls as read-only - [#26577](https://github.com/storybookjs/storybook/pull/26577), thanks @valentinpalkovic! +- Automigrations: Add migration note about new react-docgen default - [#26620](https://github.com/storybookjs/storybook/pull/26620), thanks @valentinpalkovic! +- Automigrations: Fix missing support for mts vite config - [#26441](https://github.com/storybookjs/storybook/pull/26441), thanks @drik98! +- CLI: Automigrations copy edits - [#26342](https://github.com/storybookjs/storybook/pull/26342), thanks @joevaugh4n! +- CLI: Improve Yarn berry error parsing - [#26616](https://github.com/storybookjs/storybook/pull/26616), thanks @yannbf! +- Codemods: Escape filename given as argument - [#26430](https://github.com/storybookjs/storybook/pull/26430), thanks @YukiKitagata! +- NextJS: Support path aliases when no base url is set - [#26651](https://github.com/storybookjs/storybook/pull/26651), thanks @yannbf! +- Node: Safe use of `document` for preview - [#24248](https://github.com/storybookjs/storybook/pull/24248), thanks @DylanPiercey! +- React-Docgen: Make sure to be able to handle empty unions - [#26639](https://github.com/storybookjs/storybook/pull/26639), thanks @kasperpeulen! +- Test: Add @storybook/test as dev dependency - [#26458](https://github.com/storybookjs/storybook/pull/26458), thanks @arnabsen! +- Theming: Update emotion dependencies - [#26623](https://github.com/storybookjs/storybook/pull/26623), thanks @SimenB! +- Viewport: Fix missing style - [#26530](https://github.com/storybookjs/storybook/pull/26530), thanks @jpzwarte! +- Webpack: Hide runtime errors - [#23175](https://github.com/storybookjs/storybook/pull/23175), thanks @donaldpipowitch! + ## 8.1.0-alpha.4 - Addon Docs: Support Stencil based display names in source snippets - [#26592](https://github.com/storybookjs/storybook/pull/26592), thanks @yannbf! diff --git a/CHANGELOG.v1-5.md b/CHANGELOG.v1-5.md index 77b7589e5539..2a965d45f9f3 100644 --- a/CHANGELOG.v1-5.md +++ b/CHANGELOG.v1-5.md @@ -3527,7 +3527,7 @@ See [the RC announcement](https://gist.github.com/shilman/0332090b63f1798a58ed8d - UI: Fix ie 11 ([#5599](https://github.com/storybookjs/storybook/pull/5599)) - CLI: Fix for when outputDir is an absolute path ([#5573](https://github.com/storybookjs/storybook/pull/5573)) - CLI: Avoid false-negative checks for port ([#5565](https://github.com/storybookjs/storybook/pull/5565)) -- Core: Prioritise programatic configuration ([#5564](https://github.com/storybookjs/storybook/pull/5564)) +- Core: Prioritise programmatic configuration ([#5564](https://github.com/storybookjs/storybook/pull/5564)) - UI: FIX clear search ([#5550](https://github.com/storybookjs/storybook/pull/5550)) - Core: Transpile safe-eval package ([#5498](https://github.com/storybookjs/storybook/pull/5498)) - UI: Add default backgrounds to official example ([#5585](https://github.com/storybookjs/storybook/pull/5585)) @@ -5258,7 +5258,7 @@ NOTE: As part of the generic addon decorators, we've reversed the order of addon #### Features - Storybook for Marko [#3504](https://github.com/storybookjs/storybook/pull/3504) -- Storybook addon Jest angular suport [#3532](https://github.com/storybookjs/storybook/pull/3532) +- Storybook addon Jest angular support [#3532](https://github.com/storybookjs/storybook/pull/3532) - Storybook for HTML snippets [#3475](https://github.com/storybookjs/storybook/pull/3475) - Feature/config custom chrome executable path [#3518](https://github.com/storybookjs/storybook/pull/3518) - Channel-postmessage: handle events from the same window [#3519](https://github.com/storybookjs/storybook/pull/3519) @@ -9224,7 +9224,7 @@ Minor features including a new "events" addon, as well as the usual bugfixes, cl - Fixed knobs addon editing bug [#1233](https://github.com/storybookjs/storybook/pull/1233) - Fix bug in addons/graphql in reIndentQuery [#1207](https://github.com/storybookjs/storybook/pull/1207) -- Marksy initialized with mtrcConf intead of marksyConf [#1205](https://github.com/storybookjs/storybook/pull/1205) +- Marksy initialized with mtrcConf instead of marksyConf [#1205](https://github.com/storybookjs/storybook/pull/1205) #### Documentation diff --git a/CHANGELOG.v6.md b/CHANGELOG.v6.md index 8501b232d289..5e7ce4179aee 100644 --- a/CHANGELOG.v6.md +++ b/CHANGELOG.v6.md @@ -1950,7 +1950,7 @@ Fix bad publish of `6.4.0-alpha.27` to the `latest` tag ### Bug Fixes - Controls: Don't set arg in validateOptions if it would be `undefined` ([#15654](https://github.com/storybookjs/storybook/pull/15654)) -- Trailing comma handling for "-s" command line paramenter ([#15615](https://github.com/storybookjs/storybook/pull/15615)) +- Trailing comma handling for "-s" command line parameter ([#15615](https://github.com/storybookjs/storybook/pull/15615)) - Controls: Fix color matching behavior for non-string types ([#15549](https://github.com/storybookjs/storybook/pull/15549)) - Composition: Fix refs ordering ([#15527](https://github.com/storybookjs/storybook/pull/15527)) @@ -1963,7 +1963,7 @@ Fix bad publish of `6.4.0-alpha.27` to the `latest` tag ### Bug Fixes -- CLI: Fix trailing comma handling for "-s" command line paramenter ([#15615](https://github.com/storybookjs/storybook/pull/15615)) +- CLI: Fix trailing comma handling for "-s" command line parameter ([#15615](https://github.com/storybookjs/storybook/pull/15615)) - Components: Lazy-load syntax highlighter ([#15607](https://github.com/storybookjs/storybook/pull/15607)) ### Maintenance diff --git a/MIGRATION.md b/MIGRATION.md index 2d6f1d834ecf..345f0ff2baf7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -4177,7 +4177,7 @@ For more information, [see the documentation](https://github.com/storybookjs/sto Storybook has built-in Typescript support in 6.0. That means you should remove your complex Typescript configurations from your `.storybook` config. We've tried to pick sensible defaults that work out of the box, especially for nice prop table generation in `@storybook/addon-docs`. -To migrate from an old setup, we recommend deleting any typescript-specific webpack/babel configurations in your project. You should also remove `@storybook/preset-typescript`, which is superceded by the built-in configuration. +To migrate from an old setup, we recommend deleting any typescript-specific webpack/babel configurations in your project. You should also remove `@storybook/preset-typescript`, which is superseded by the built-in configuration. If you want to override the defaults, see the [typescript configuration docs](https://storybook.js.org/docs/react/configure/typescript). @@ -5570,7 +5570,7 @@ The `@storybook/react-native` had built-in addons (`addon-actions` and `addon-li 1. `imageSnapshot` test function was extracted from `addon-storyshots` and moved to a new package - `addon-storyshots-puppeteer` that now will - be dependant on puppeteer. [README](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-puppeteer) + be dependent on puppeteer. [README](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-puppeteer) 2. `getSnapshotFileName` export was replaced with the `Stories2SnapsConverter` class that now can be overridden for a custom implementation of the snapshot-name generation. [README](https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core#stories2snapsconverter) diff --git a/code/addons/actions/src/models/ActionOptions.ts b/code/addons/actions/src/models/ActionOptions.ts index ffd8d373b3fa..c3e831801b50 100644 --- a/code/addons/actions/src/models/ActionOptions.ts +++ b/code/addons/actions/src/models/ActionOptions.ts @@ -1,7 +1,7 @@ import type { Options as TelejsonOptions } from 'telejson'; interface Options { - depth: number; // backards compatibility, remove in 7.0 + depth: number; // backwards compatibility, remove in 7.0 clearOnStoryChange: boolean; limit: number; implicit: boolean; diff --git a/code/addons/actions/src/runtime/action.ts b/code/addons/actions/src/runtime/action.ts index fab9e8aae1d4..4d15504c36b6 100644 --- a/code/addons/actions/src/runtime/action.ts +++ b/code/addons/actions/src/runtime/action.ts @@ -83,7 +83,7 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti } const channel = addons.getChannel(); - // this makes sure that in js enviroments like react native you can still get an id + // this makes sure that in js environments like react native you can still get an id const id = generateId(); const minDepth = 5; // anything less is really just storybook internals const serializedArgs = args.map(serializeArg); diff --git a/code/addons/docs/ember/README.md b/code/addons/docs/ember/README.md index e1d907435883..7491de442845 100644 --- a/code/addons/docs/ember/README.md +++ b/code/addons/docs/ember/README.md @@ -55,7 +55,7 @@ import docJson from '../dist/storybook-docgen/index.json'; setJSONDoc(docJson); ``` -Finally, be sure to fill in the `component` field in your story metadata. This should be a string that matches the name of the `@class` used in your souce comments: +Finally, be sure to fill in the `component` field in your story metadata. This should be a string that matches the name of the `@class` used in your source comments: ```ts export default { diff --git a/code/addons/docs/src/preset.ts b/code/addons/docs/src/preset.ts index 68c7efb39f8b..31248e7af990 100644 --- a/code/addons/docs/src/preset.ts +++ b/code/addons/docs/src/preset.ts @@ -1,4 +1,4 @@ -import { dirname, join } from 'path'; +import { dirname, join, isAbsolute } from 'path'; import rehypeSlug from 'rehype-slug'; import rehypeExternalLinks from 'rehype-external-links'; @@ -147,6 +147,8 @@ export const viteFinal = async (config: any, options: Options) => { resolve: { alias: { react, + // Vite doesn't respect export maps when resolving an absolute path, so we need to do that manually here + ...(isAbsolute(reactDom) && { 'react-dom/server': `${reactDom}/server.browser.js` }), 'react-dom': reactDom, '@mdx-js/react': mdx, /** diff --git a/code/addons/docs/template/stories/docs2/ResolvedReact.jsx b/code/addons/docs/template/stories/docs2/ResolvedReact.jsx new file mode 100644 index 000000000000..f16c20f04fce --- /dev/null +++ b/code/addons/docs/template/stories/docs2/ResolvedReact.jsx @@ -0,0 +1,28 @@ +import React, * as ReactExport from 'react'; +import * as ReactDom from 'react-dom'; +import * as ReactDomServer from 'react-dom/server'; + +export const ResolvedReact = () => { + return ( + <> +

+ react:{' '} + + {ReactExport.version ?? 'no version export found'} + +

+

+ react-dom:{' '} + + {ReactDom.version ?? 'no version export found'} + +

+

+ react-dom/server:{' '} + + {ReactDomServer.version ?? 'no version export found'} + +

+ + ); +}; diff --git a/code/addons/docs/template/stories/docs2/ResolvedReact.mdx b/code/addons/docs/template/stories/docs2/ResolvedReact.mdx index 7a5f04ab6bc8..bd3ad01c2e08 100644 --- a/code/addons/docs/template/stories/docs2/ResolvedReact.mdx +++ b/code/addons/docs/template/stories/docs2/ResolvedReact.mdx @@ -1,13 +1,29 @@ -import { version as reactVersion } from 'react'; -import { version as reactDomVersion } from 'react-dom'; -import { ResolvedReactVersion } from './ResolvedReactVersion'; +import { Meta } from '@storybook/blocks'; +import * as ReactExport from 'react'; +import * as ReactDom from 'react-dom'; +import * as ReactDomServer from 'react-dom/server'; +import { ResolvedReact } from './ResolvedReact'; + + + +This doc is used to display the resolved version of React and its related packages. +As long as `@storybook/addon-docs` is installed, `react` and `react-dom` should be available to import from and should resolve to the same version. + +The MDX here ensures that it works in an MDX file. + +- See the [autodocs](/docs/addons-docs-docs2-resolvedreact--docs) for how it resolves in autodocs. +- See the [Story](/story/addons-docs-docs2-resolvedreact--story) for how it resolves in the actual story. + +**Note: There appears to be a bug in the _production_ build of `react-dom`, where it reports version `18.2.0-next-9e3b772b8-20220608` while in fact version `18.2.0` is installed.** ## In MDX -react: {reactVersion} +react: {ReactExport.version ?? 'no version export found'} + +react-dom: {ReactDom.version ?? 'no version export found'} -react-dom: {reactDomVersion} +react-dom/server: {ReactDomServer.version ?? 'no version export found'} -## In `ResolvedReactVersion` component +## In `ResolvedReact` component - + diff --git a/code/addons/docs/template/stories/docs2/ResolvedReactVersion.jsx b/code/addons/docs/template/stories/docs2/ResolvedReactVersion.jsx deleted file mode 100644 index 6e094c1e64d0..000000000000 --- a/code/addons/docs/template/stories/docs2/ResolvedReactVersion.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { version as reactVersion } from 'react'; -import { version as reactDomVersion } from 'react-dom'; - -export const ResolvedReactVersion = () => { - return ( - <> -

- react: {reactVersion} -

-

- react-dom: {reactDomVersion} -

- - ); -}; diff --git a/code/addons/docs/template/stories/docs2/resolved-react.stories.ts b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts new file mode 100644 index 000000000000..91f12041962b --- /dev/null +++ b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts @@ -0,0 +1,70 @@ +import { within, expect } from '@storybook/test'; +import * as ReactExport from 'react'; +import * as ReactDom from 'react-dom'; +import * as ReactDomServer from 'react-dom/server'; + +/** + * This component is used to display the resolved version of React and its related packages. + * As long as `@storybook/addon-docs` is installed, `react` and `react-dom` should be available to import from and should resolve to the same version. + * + * The autodocs here ensures that it also works in the generated documentation. + * + * - See the [MDX docs](/docs/addons-docs-docs2-resolvedreact--mdx) for how it resolves in MDX. + * - See the [Story](/story/addons-docs-docs2-resolvedreact--story) for how it resolves in the actual story. + * + * **Note: There appears to be a bug in the _production_ build of `react-dom`, where it reports version `18.2.0-next-9e3b772b8-20220608` while in fact version `18.2.0` is installed.** + */ +export default { + title: 'Docs2/ResolvedReact', + component: globalThis.Components.Html, + tags: ['autodocs'], + argTypes: { + content: { table: { disable: true } }, + }, + args: { + content: ` +

+ react: ${ + ReactExport.version ?? 'no version export found' + } +

+

+ react-dom: ${ + ReactDom.version ?? 'no version export found' + } +

+

+ react-dom/server: ${ + ReactDomServer.version ?? 'no version export found' + } +

+ `, + }, + parameters: { + docs: { + name: 'ResolvedReact', + }, + }, +}; + +export const Story = { + // This test is more or less the same as the E2E test we have for MDX and autodocs entries in addon-docs.spec.ts + play: async ({ canvasElement, step, parameters }: any) => { + const canvas = await within(canvasElement); + + const actualReactVersion = (await canvas.findByTestId('react')).textContent; + const actualReactDomVersion = (await canvas.findByTestId('react-dom')).textContent; + const actualReactDomServerVersion = (await canvas.findByTestId('react-dom-server')).textContent; + + step('Expect React packages to all resolve to the same version', () => { + // react-dom has a bug in its production build, reporting version 18.2.0-next-9e3b772b8-20220608 even though version 18.2.0 is installed. + expect(actualReactDomVersion!.startsWith(actualReactVersion!)).toBeTruthy(); + + if (parameters.renderer === 'preact') { + // the preact/compat alias doesn't have a version export in react-dom/server + return; + } + expect(actualReactDomServerVersion).toBe(actualReactVersion); + }); + }, +}; diff --git a/code/addons/docs/web-components/README.md b/code/addons/docs/web-components/README.md index 0aae578e47d8..c5417be88e43 100644 --- a/code/addons/docs/web-components/README.md +++ b/code/addons/docs/web-components/README.md @@ -31,7 +31,7 @@ In order to get [Props tables](..docs/../../docs/props-tables.md) documentation for web-components you will need to have a [custom-elements.json](https://github.com/webcomponents/custom-elements-json) file. -You can hand write it or better generate it. Depending on the web components sugar you are choosing your milage may vary. +You can hand write it or better generate it. Depending on the web components sugar you are choosing your mileage may vary. Known analyzers that output `custom-elements.json` v1.0.0: diff --git a/code/addons/viewport/src/Tool.tsx b/code/addons/viewport/src/Tool.tsx index 37bc22500e9b..da600e576675 100644 --- a/code/addons/viewport/src/Tool.tsx +++ b/code/addons/viewport/src/Tool.tsx @@ -74,6 +74,7 @@ const flip = ({ width, height, ...styles }: ViewportStyles) => ({ const ActiveViewportSize = styled.div(() => ({ display: 'inline-flex', + alignItems: 'center', })); const ActiveViewportLabel = styled.div(({ theme }) => ({ diff --git a/code/builders/builder-webpack5/src/presets/preview-preset.ts b/code/builders/builder-webpack5/src/presets/preview-preset.ts index bf061b5bd32d..cf181be488cc 100644 --- a/code/builders/builder-webpack5/src/presets/preview-preset.ts +++ b/code/builders/builder-webpack5/src/presets/preview-preset.ts @@ -9,9 +9,13 @@ export const entries = async (_: unknown, options: any) => { // Suppress informational messages when --quiet is specified. webpack-hot-middleware's quiet // parameter would also suppress warnings. result = result.concat( - `${require.resolve('webpack-hot-middleware/client')}?reload=true&quiet=false&noInfo=${ - options.quiet - }` + `${require.resolve( + 'webpack-hot-middleware/client' + )}?reload=true&quiet=false&overlay=${JSON.stringify({ + errors: true, + warnings: false, + runtimeErrors: false, + })}&noInfo=${options.quiet}` ); } diff --git a/code/e2e-tests/addon-docs.spec.ts b/code/e2e-tests/addon-docs.spec.ts index 72470acb62ab..db7b7b7d5e05 100644 --- a/code/e2e-tests/addon-docs.spec.ts +++ b/code/e2e-tests/addon-docs.spec.ts @@ -185,30 +185,71 @@ test.describe('addon-docs', () => { }); test('should resolve react to the correct version', async ({ page }) => { + // Arrange - Navigate to MDX docs const sbPage = new SbPage(page); - await sbPage.navigateToUnattachedDocs('addons/docs/docs2', 'ResolvedReact'); + await sbPage.navigateToStory('addons/docs/docs2/resolvedreact', 'mdx', 'docs'); const root = sbPage.previewRoot(); - let expectedReactVersion = /^18/; + // Arrange - Setup expectations + let expectedReactVersionRange = /^18/; if ( templateName.includes('preact') || templateName.includes('react-webpack/17') || templateName.includes('react-vite/17') ) { - expectedReactVersion = /^17/; + expectedReactVersionRange = /^17/; } else if (templateName.includes('react16')) { - expectedReactVersion = /^16/; + expectedReactVersionRange = /^16/; } + // Arrange - Get the actual versions const mdxReactVersion = await root.getByTestId('mdx-react'); const mdxReactDomVersion = await root.getByTestId('mdx-react-dom'); + const mdxReactDomServerVersion = await root.getByTestId('mdx-react-dom-server'); const componentReactVersion = await root.getByTestId('component-react'); const componentReactDomVersion = await root.getByTestId('component-react-dom'); + const componentReactDomServerVersion = await root.getByTestId('component-react-dom-server'); + + // Assert - The versions are in the expected range + await expect(mdxReactVersion).toHaveText(expectedReactVersionRange); + await expect(componentReactVersion).toHaveText(expectedReactVersionRange); + await expect(mdxReactDomVersion).toHaveText(expectedReactVersionRange); + await expect(componentReactDomVersion).toHaveText(expectedReactVersionRange); + if (!templateName.includes('preact')) { + // preact/compat alias doesn't have a version export in react-dom/server + await expect(mdxReactDomServerVersion).toHaveText(expectedReactVersionRange); + await expect(componentReactDomServerVersion).toHaveText(expectedReactVersionRange); + } + + // Arrange - Navigate to autodocs + await sbPage.navigateToStory('addons/docs/docs2/resolvedreact', 'docs'); + + // Arrange - Get the actual versions + const autodocsReactVersion = await root.getByTestId('react'); + const autodocsReactDomVersion = await root.getByTestId('react-dom'); + const autodocsReactDomServerVersion = await root.getByTestId('react-dom-server'); + + // Assert - The versions are in the expected range + await expect(autodocsReactVersion).toHaveText(expectedReactVersionRange); + await expect(autodocsReactDomVersion).toHaveText(expectedReactVersionRange); + if (!templateName.includes('preact')) { + await expect(autodocsReactDomServerVersion).toHaveText(expectedReactVersionRange); + } + + // Arrange - Navigate to story + await sbPage.navigateToStory('addons/docs/docs2/resolvedreact', 'story'); - await expect(mdxReactVersion).toHaveText(expectedReactVersion); - await expect(mdxReactDomVersion).toHaveText(expectedReactVersion); - await expect(componentReactVersion).toHaveText(expectedReactVersion); - await expect(componentReactDomVersion).toHaveText(expectedReactVersion); + // Arrange - Get the actual versions + const storyReactVersion = await root.getByTestId('react'); + const storyReactDomVersion = await root.getByTestId('react-dom'); + const storyReactDomServerVersion = await root.getByTestId('react-dom-server'); + + // Assert - The versions are in the expected range + await expect(storyReactVersion).toHaveText(expectedReactVersionRange); + await expect(storyReactDomVersion).toHaveText(expectedReactVersionRange); + if (!templateName.includes('preact')) { + await expect(storyReactDomServerVersion).toHaveText(expectedReactVersionRange); + } }); test('should have stories from multiple CSF files in autodocs', async ({ page }) => { diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts index df5ca4ff59a8..fae5d359a6a7 100644 --- a/code/e2e-tests/util.ts +++ b/code/e2e-tests/util.ts @@ -40,7 +40,7 @@ export class SbPage { /** * Visit a story by selecting it from the sidebar. */ - async navigateToStory(title: string, name: string) { + async navigateToStory(title: string, name: string, viewMode?: 'docs' | 'story') { await this.openComponent(title); const titleId = toId(title); @@ -50,11 +50,10 @@ export class SbPage { const storyLink = this.page.locator('*', { has: this.page.locator(`> ${storyLinkId}`) }); await storyLink.click({ force: true }); - // assert url changes - const viewMode = name === 'docs' ? 'docs' : 'story'; - await this.page.waitForURL((url) => - url.search.includes(`path=/${viewMode}/${titleId}--${storyId}`) + url.search.includes( + `path=/${viewMode ?? name === 'docs' ? 'docs' : 'story'}/${titleId}--${storyId}` + ) ); const selected = await storyLink.getAttribute('data-selected'); @@ -84,7 +83,7 @@ export class SbPage { } async waitUntilLoaded() { - // make sure we start every test with clean state – to avoid possible flakyness + // make sure we start every test with clean state – to avoid possible flakiness await this.page.context().addInitScript(() => { const storeState = { layout: { diff --git a/code/frameworks/nextjs/src/imports/webpack.ts b/code/frameworks/nextjs/src/imports/webpack.ts index 0b7a45b13bc8..57d837d0fdb2 100644 --- a/code/frameworks/nextjs/src/imports/webpack.ts +++ b/code/frameworks/nextjs/src/imports/webpack.ts @@ -11,8 +11,8 @@ export const configureImports = ({ }): void => { const configLoadResult = loadConfig(configDir); - if (configLoadResult.resultType === 'failed' || !configLoadResult.baseUrl) { - // either not a typescript project or tsconfig contains no baseUrl + if (configLoadResult.resultType === 'failed') { + // either not a typescript project or tsconfig is not found - we bail return; } diff --git a/code/lib/cli/src/automigrate/fixes/react-docgen.test.ts b/code/lib/cli/src/automigrate/fixes/react-docgen.test.ts index ba46fd8ee77b..22461bc18aeb 100644 --- a/code/lib/cli/src/automigrate/fixes/react-docgen.test.ts +++ b/code/lib/cli/src/automigrate/fixes/react-docgen.test.ts @@ -25,31 +25,35 @@ describe('no-ops', () => { check({ packageManager: {}, main: { + framework: '@storybook/react-vite', typescript: { // @ts-expect-error assume react reactDocgen: 'react-docgen-typescript', }, }, }) - ).resolves.toBeFalsy(); + ).resolves.toBeNull(); await expect( check({ packageManager: {}, main: { + framework: '@storybook/react-vite', typescript: { // @ts-expect-error assume react reactDocgen: false, }, }, }) - ).resolves.toBeFalsy(); + ).resolves.toBeNull(); }); + it('typescript.reactDocgen and typescript.reactDocgenTypescriptOptions are both unset', async () => { await expect( check({ packageManager: {}, main: { + framework: '@storybook/react-vite', typescript: { // @ts-expect-error assume react reactDocgen: 'react-docgen-typescript', @@ -57,22 +61,55 @@ describe('no-ops', () => { }, }, }) - ).resolves.toBeFalsy(); + ).resolves.toBeNull(); + }); + + it('typescript.reactDocgen is undefined and it is not a react framework', async () => { + await expect( + check({ + packageManager: {}, + main: { + framework: '@storybook/sveltekit', + }, + }) + ).resolves.toBeNull(); }); }); describe('continue', () => { + it('should resolve if the framework is using a react renderer', async () => { + await expect( + check({ + packageManager: {}, + main: { + framework: '@storybook/nextjs', + }, + }) + ).resolves.toEqual({ + reactDocgenTypescriptOptions: undefined, + reactDocgen: undefined, + }); + }); + it('typescript.reactDocgenTypescriptOptions is set', async () => { await expect( check({ packageManager: {}, main: { + framework: '@storybook/react-vite', typescript: { // @ts-expect-error assume react - reactDocgenTypescriptOptions: {}, + reactDocgenTypescriptOptions: { + someOption: true, + }, }, }, }) - ).resolves.toBeTruthy(); + ).resolves.toEqual({ + reactDocgenTypescriptOptions: { + someOption: true, + }, + reactDocgen: undefined, + }); }); }); diff --git a/code/lib/cli/src/automigrate/fixes/react-docgen.ts b/code/lib/cli/src/automigrate/fixes/react-docgen.ts index ef89a24915ff..f791be0ab068 100644 --- a/code/lib/cli/src/automigrate/fixes/react-docgen.ts +++ b/code/lib/cli/src/automigrate/fixes/react-docgen.ts @@ -1,15 +1,15 @@ import { dedent } from 'ts-dedent'; -import { updateMainConfig } from '../helpers/mainConfigFile'; +import { getRendererName, updateMainConfig } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; +import chalk from 'chalk'; const logger = console; interface Options { - reactDocgenTypescriptOptions: any; + reactDocgenTypescriptOptions?: any; + reactDocgen?: 'react-docgen-typescript' | 'react-docgen' | false; } -/** - */ export const reactDocgen: Fix = { id: 'react-docgen', @@ -17,13 +17,20 @@ export const reactDocgen: Fix = { async check({ mainConfig }) { // @ts-expect-error assume react - const { reactDocgenTypescriptOptions } = mainConfig.typescript || {}; + const { reactDocgenTypescriptOptions, reactDocgen: rDocgen } = mainConfig.typescript || {}; - return reactDocgenTypescriptOptions ? { reactDocgenTypescriptOptions } : null; + const rendererName = getRendererName(mainConfig); + + if (rendererName !== 'react' || rDocgen !== undefined) { + return null; + } + + return { reactDocgenTypescriptOptions, reactDocgen: rDocgen }; }, - prompt() { - return dedent` + prompt({ reactDocgenTypescriptOptions }) { + if (reactDocgenTypescriptOptions) { + return dedent` You have "typescript.reactDocgenTypescriptOptions" configured in your main.js, but "typescript.reactDocgen" is unset. @@ -37,15 +44,34 @@ export const reactDocgen: Fix = { https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-docgen-component-analysis-by-default `; + } else { + return dedent` + Since Storybook 8.0, ${chalk.cyan( + 'react-docgen' + )} is now the default for generating component controls, replacing ${chalk.cyan( + 'react-docgen-typescript' + )}. + This offers better performance and suits most cases. + However, for complex TypeScript types or specific type features, the generated controls might not be as precise. + + For more on this change, check the migration guide: + ${chalk.yellow( + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-docgen-component-analysis-by-default' + )} + + For known "react-docgen" limitations, see: + ${chalk.yellow('https://github.com/storybookjs/storybook/issues/26606')} + + Would you like to switch back to ${chalk.cyan('react-docgen-typescript')} in your Storybook? + `; + } }, - async run({ dryRun, mainConfigPath }) { + async run({ dryRun, mainConfigPath, result }) { if (!dryRun) { await updateMainConfig({ mainConfigPath, dryRun: !!dryRun }, async (main) => { logger.info(`✅ Setting typescript.reactDocgen`); - if (!dryRun) { - main.setFieldValue(['typescript', 'reactDocgen'], 'react-docgen-typescript'); - } + main.setFieldValue(['typescript', 'reactDocgen'], 'react-docgen-typescript'); }); } }, diff --git a/code/lib/cli/src/automigrate/fixes/remove-argtypes-regex.ts b/code/lib/cli/src/automigrate/fixes/remove-argtypes-regex.ts index 78dd25049b27..3220bbeeec9d 100644 --- a/code/lib/cli/src/automigrate/fixes/remove-argtypes-regex.ts +++ b/code/lib/cli/src/automigrate/fixes/remove-argtypes-regex.ts @@ -39,17 +39,20 @@ export const removeArgtypesRegex: Fix<{ argTypesRegex: NodePath; previewConfigPa ${argTypesRegex.buildCodeFrameError(`${previewConfigPath}`).message} - In Storybook you can write so-called play functions, which are used to render your stories interactively. - Mocking action args in play functions was done implicitly by analyzing the argTypesRegex. - - Since Storybook 8, implicit action args mocking isn't supported anymore. + Storybook's play functions let you render your stories interactively. + + In the past, play functions mocked action args implicitly by analyzing the argTypesRegex + in your preview.js|ts file. + + However, Storybook 8 changes this behavior, and we now recommend using the + (fn) function to mock your component's methods instead. - Use the following command to check for mocked action usages in your play functions: + Use the following command to check for implied mocked actions in your play functions: ${chalk.cyan( 'npx storybook migrate find-implicit-spies --glob="**/*.stories.@(js|jsx|ts|tsx)"' )} - And follow the documentation to migrate your play functions: + Then, refer to our docs to migrate your play functions to Storybook 8: ${chalk.yellow( 'https://storybook.js.org/docs/8.0/essentials/actions#via-storybooktest-fn-spy-function' )} 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 index 3eed2c36ab70..59ce82d3cbbe 100644 --- a/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts +++ b/code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts @@ -43,7 +43,7 @@ export const removeJestTestingLibrary: Fix<{ incompatiblePackages: string[] }> = const versionToInstall = getStorybookVersionSpecifier(packageJson); - await packageManager.addDependencies({ packageJson }, [ + await packageManager.addDependencies({ installAsDevDependencies: true, packageJson }, [ `@storybook/test@${versionToInstall}`, ]); diff --git a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts index e81637dc6cb2..3334fd958169 100644 --- a/code/lib/cli/src/automigrate/fixes/vite-config-file.ts +++ b/code/lib/cli/src/automigrate/fixes/vite-config-file.ts @@ -20,7 +20,7 @@ export const viteConfigFile = { async check({ mainConfig, packageManager, mainConfigPath }) { let isViteConfigFileFound = !!(await findUp( - ['vite.config.js', 'vite.config.mjs', 'vite.config.cjs', 'vite.config.ts'], + ['vite.config.js', 'vite.config.mjs', 'vite.config.cjs', 'vite.config.ts', 'vite.config.mts'], { cwd: mainConfigPath ? path.join(mainConfigPath, '..') : process.cwd() } )); diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.test.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.test.ts index a4f0e7e5e92f..8f2f0fde245f 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.test.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest'; import { getBuilderPackageName, getFrameworkPackageName, + getRendererName, getRendererPackageNameFromFramework, } from './mainConfigFile'; @@ -133,6 +134,61 @@ describe('getFrameworkPackageName', () => { }); }); +describe('getRendererName', () => { + it('should return null when mainConfig is undefined', () => { + const rendererName = getRendererName(undefined); + expect(rendererName).toBeNull(); + }); + + it('should return null when framework package name or path is not found', () => { + const mainConfig = {}; + + const rendererName = getRendererName(mainConfig as any); + expect(rendererName).toBeNull(); + }); + + it('should return renderer name when framework is a string', () => { + const frameworkPackage = '@storybook/react-webpack5'; + const mainConfig = { + framework: frameworkPackage, + }; + + const rendererName = getRendererName(mainConfig as any); + expect(rendererName).toBe('react'); + }); + + it('should return renderer name when framework.name contains valid framework package name', () => { + const frameworkPackage = '@storybook/react-vite'; + const packageNameOrPath = `/path/to/${frameworkPackage}`; + const mainConfig = { + framework: { name: packageNameOrPath }, + }; + + const rendererName = getRendererName(mainConfig as any); + expect(rendererName).toBe('react'); + }); + + it('should return renderer name when framework.name contains windows backslash paths', () => { + const packageNameOrPath = 'c:\\path\\to\\@storybook\\sveltekit'; + const mainConfig = { + framework: { name: packageNameOrPath }, + }; + + const rendererName = getRendererName(mainConfig as any); + expect(rendererName).toBe('svelte'); + }); + + it(`should return undefined when framework does not contain the name of a valid framework package`, () => { + const packageNameOrPath = '@my-org/storybook-framework'; + const mainConfig = { + framework: packageNameOrPath, + }; + + const rendererName = getRendererName(mainConfig as any); + expect(rendererName).toBeUndefined(); + }); +}); + describe('getRendererPackageNameFromFramework', () => { it('should return null when given no package name', () => { // @ts-expect-error (Argument of type 'undefined' is not assignable) diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts index 2ba805c95614..3fd56fc8107b 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts @@ -13,6 +13,7 @@ import dedent from 'ts-dedent'; import path from 'path'; import type { JsPackageManager } from '@storybook/core-common'; import { getCoercedStorybookVersion } from '@storybook/core-common'; +import { frameworkToRenderer } from '../../helpers'; const logger = console; @@ -36,6 +37,23 @@ export const getFrameworkPackageName = (mainConfig?: StorybookConfigRaw) => { ); }; +/** + * Given a Storybook configuration object, retrieves the inferred renderer name from the framework. + * @param mainConfig - The main Storybook configuration object to lookup. + * @returns - The renderer name. If not found, returns null. + */ +export const getRendererName = (mainConfig?: StorybookConfigRaw) => { + const frameworkPackageName = getFrameworkPackageName(mainConfig); + + if (!frameworkPackageName) { + return null; + } + + const frameworkName = frameworkPackages[frameworkPackageName]; + + return frameworkToRenderer[frameworkName as keyof typeof frameworkToRenderer]; +}; + /** * Given a Storybook configuration object, retrieves the package name or file path of the builder. * @param mainConfig - The main Storybook configuration object to lookup. diff --git a/code/lib/cli/src/automigrate/index.test.ts b/code/lib/cli/src/automigrate/index.test.ts index 78a098e9c8d7..11824e58d8a3 100644 --- a/code/lib/cli/src/automigrate/index.test.ts +++ b/code/lib/cli/src/automigrate/index.test.ts @@ -102,8 +102,8 @@ const runFixWrapper = async ({ describe('runFixes', () => { beforeEach(() => { retrievePackageJson.mockResolvedValue({ - depedencies: [], - devDepedencies: [], + dependencies: [], + devDependencies: [], }); getPackageVersion.mockImplementation((packageName) => { return beforeVersion; diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index b28021be7049..9d8a839bfcce 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -429,6 +429,9 @@ const baseTemplates = { renderer: '@storybook/preact', builder: '@storybook/builder-vite', }, + modifications: { + extraDependencies: ['preact-render-to-string'], + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'preact-vite/default-ts': { @@ -439,6 +442,9 @@ const baseTemplates = { renderer: '@storybook/preact', builder: '@storybook/builder-vite', }, + modifications: { + extraDependencies: ['preact-render-to-string'], + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'qwik-vite/default-ts': { diff --git a/code/lib/codemod/src/index.ts b/code/lib/codemod/src/index.ts index b55c790adf83..a5299921a211 100644 --- a/code/lib/codemod/src/index.ts +++ b/code/lib/codemod/src/index.ts @@ -89,7 +89,7 @@ export async function runCodemod( '-t', `${TRANSFORM_DIR}/${codemod}.js`, ...parserArgs, - ...files, + ...files.map((file) => `"${file}"`), ], { stdio: 'inherit', diff --git a/code/lib/core-common/src/js-package-manager/Yarn2Proxy.test.ts b/code/lib/core-common/src/js-package-manager/Yarn2Proxy.test.ts index 8cd9822a8c35..f024d19c1133 100644 --- a/code/lib/core-common/src/js-package-manager/Yarn2Proxy.test.ts +++ b/code/lib/core-common/src/js-package-manager/Yarn2Proxy.test.ts @@ -276,35 +276,65 @@ describe('Yarn 2 Proxy', () => { }); describe('parseErrors', () => { - it('should parse yarn2 errors', () => { + it('should single yarn2 error message', () => { const YARN2_ERROR_SAMPLE = ` ➤ YN0000: ┌ Resolution step ➤ YN0001: │ Error: react@npm:28.2.0: No candidates found - at ge (/Users/yannbraga/.cache/node/corepack/yarn/3.5.1/yarn.js:439:8124) + at ge (/Users/xyz/.cache/node/corepack/yarn/3.5.1/yarn.js:439:8124) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Promise.allSettled (index 8) - at async io (/Users/yannbraga/.cache/node/corepack/yarn/3.5.1/yarn.js:390:10398) + at async io (/Users/xyz/.cache/node/corepack/yarn/3.5.1/yarn.js:390:10398) ➤ YN0000: └ Completed in 2s 369ms ➤ YN0000: Failed with errors in 2s 372ms ➤ YN0032: fsevents@npm:2.3.2: Implicit dependencies on node-gyp are discouraged ➤ YN0061: @npmcli/move-file@npm:2.0.1 is deprecated: This functionality has been moved to @npmcli/fs `; - expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual( - 'YARN2 error YN0001 - EXCEPTION: react@npm:28.2.0: No candidates found' + expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toMatchInlineSnapshot( + ` + "YARN2 error + YN0001: EXCEPTION + -> Error: react@npm:28.2.0: No candidates found + " + ` ); }); - it('should show unknown yarn2 error', () => { - const YARN2_ERROR_SAMPLE = dedent` + it('shows multiple yarn2 error messages', () => { + const YARN2_ERROR_SAMPLE = ` + ➤ YN0000: · Yarn 4.1.1 ➤ YN0000: ┌ Resolution step - ➤ YN0000: └ Completed in 2s 369ms - ➤ YN0000: Failed with errors in 2s 372ms - ➤ YN0032: fsevents@npm:2.3.2: Implicit dependencies on node-gyp are discouraged - ➤ YN0061: @npmcli/move-file@npm:2.0.1 is deprecated: This functionality has been moved to @npmcli/fs + ➤ YN0085: │ + @chromatic-com/storybook@npm:1.2.25, and 300 more. + ➤ YN0000: └ Completed in 0s 763ms + ➤ YN0000: ┌ Post-resolution validation + ➤ YN0002: │ before-storybook@workspace:. doesn't provide @testing-library/dom (p1ac37), requested by @testing-library/user-event. + ➤ YN0002: │ before-storybook@workspace:. doesn't provide eslint (p1f657), requested by eslint-plugin-storybook. + ➤ YN0086: │ Some peer dependencies are incorrectly met; run yarn explain peer-requirements for details, where is the six-letter p-prefixed code. + ➤ YN0000: └ Completed + ➤ YN0000: ┌ Fetch step + ➤ YN0000: └ Completed + ➤ YN0000: ┌ Link step + ➤ YN0014: │ Failed to import certain dependencies + ➤ YN0071: │ Cannot link @storybook/test into before-storybook@workspace:. dependency @testing-library/jest-dom@npm:6.4.2 [ae73b] conflicts with parent dependency @testing-library/jest-dom@npm:5.17.0 + ➤ YN0071: │ Cannot link @storybook/test into before-storybook@workspace:. dependency @testing-library/user-event@npm:14.5.2 [ae73b] conflicts with parent dependency @testing-library/user-event@npm:13.5.0 [1b0ac] + ➤ YN0000: └ Completed in 0s 262ms + ➤ YN0000: · Failed with errors in 1s 301ms `; - expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual(`YARN2 error`); + expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toMatchInlineSnapshot( + ` + "YARN2 error + YN0014: YARN_IMPORT_FAILED + -> Failed to import certain dependencies + + YN0071: NM_CANT_INSTALL_EXTERNAL_SOFT_LINK + -> Cannot link @storybook/test into before-storybook@workspace:. dependency @testing-library/jest-dom@npm:6.4.2 [ae73b] conflicts with parent dependency @testing-library/jest-dom@npm:5.17.0 + + YN0071: NM_CANT_INSTALL_EXTERNAL_SOFT_LINK + -> Cannot link @storybook/test into before-storybook@workspace:. dependency @testing-library/user-event@npm:14.5.2 [ae73b] conflicts with parent dependency @testing-library/user-event@npm:13.5.0 [1b0ac] + " + ` + ); }); }); }); diff --git a/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts b/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts index 09f535e2dfa5..7014357cc44b 100644 --- a/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts @@ -10,10 +10,35 @@ import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; import { parsePackageData } from './util'; -const YARN2_ERROR_REGEX = /(YN\d{4}):.*?Error:\s+(.*)/i; +const CRITICAL_YARN2_ERROR_CODES = { + YN0001: 'EXCEPTION', + YN0009: 'BUILD_FAILED', + YN0010: 'RESOLVER_NOT_FOUND', + YN0011: 'FETCHER_NOT_FOUND', + YN0012: 'LINKER_NOT_FOUND', + YN0014: 'YARN_IMPORT_FAILED', + YN0015: 'REMOTE_INVALID', + YN0016: 'REMOTE_NOT_FOUND', + YN0020: 'MISSING_LOCKFILE_ENTRY', + YN0021: 'WORKSPACE_NOT_FOUND', + YN0028: 'FROZEN_LOCKFILE_EXCEPTION', + YN0030: 'FETCH_FAILED', + YN0046: 'AUTOMERGE_FAILED_TO_PARSE', + YN0062: 'INCOMPATIBLE_OS', + YN0063: 'INCOMPATIBLE_CPU', + YN0071: 'NM_CANT_INSTALL_EXTERNAL_SOFT_LINK', + YN0072: 'NM_PRESERVE_SYMLINKS_REQUIRED', + YN0075: 'PROLOG_INSTANTIATION_ERROR', + YN0076: 'INCOMPATIBLE_ARCHITECTURE', + YN0078: 'RESOLUTION_MISMATCH', + YN0081: 'NETWORK_UNSAFE_HTTP', + YN0082: 'RESOLUTION_FAILED', + YN0083: 'AUTOMERGE_GIT_ERROR', +}; + const YARN2_ERROR_CODES = { + ...CRITICAL_YARN2_ERROR_CODES, YN0000: 'UNNAMED', - YN0001: 'EXCEPTION', YN0002: 'MISSING_PEER_DEPENDENCY', YN0003: 'CYCLIC_DEPENDENCIES', YN0004: 'DISABLED_BUILD_SCRIPTS', @@ -21,31 +46,19 @@ const YARN2_ERROR_CODES = { YN0006: 'SOFT_LINK_BUILD', YN0007: 'MUST_BUILD', YN0008: 'MUST_REBUILD', - YN0009: 'BUILD_FAILED', - YN0010: 'RESOLVER_NOT_FOUND', - YN0011: 'FETCHER_NOT_FOUND', - YN0012: 'LINKER_NOT_FOUND', YN0013: 'FETCH_NOT_CACHED', - YN0014: 'YARN_IMPORT_FAILED', - YN0015: 'REMOTE_INVALID', - YN0016: 'REMOTE_NOT_FOUND', YN0017: 'RESOLUTION_PACK', YN0018: 'CACHE_CHECKSUM_MISMATCH', YN0019: 'UNUSED_CACHE_ENTRY', - YN0020: 'MISSING_LOCKFILE_ENTRY', - YN0021: 'WORKSPACE_NOT_FOUND', YN0022: 'TOO_MANY_MATCHING_WORKSPACES', YN0023: 'CONSTRAINTS_MISSING_DEPENDENCY', YN0024: 'CONSTRAINTS_INCOMPATIBLE_DEPENDENCY', YN0025: 'CONSTRAINTS_EXTRANEOUS_DEPENDENCY', YN0026: 'CONSTRAINTS_INVALID_DEPENDENCY', YN0027: 'CANT_SUGGEST_RESOLUTIONS', - YN0028: 'FROZEN_LOCKFILE_EXCEPTION', YN0029: 'CROSS_DRIVE_VIRTUAL_LOCAL', - YN0030: 'FETCH_FAILED', YN0031: 'DANGEROUS_NODE_MODULES', YN0032: 'NODE_GYP_INJECTED', - YN0046: 'AUTOMERGE_FAILED_TO_PARSE', YN0047: 'AUTOMERGE_IMMUTABLE', YN0048: 'AUTOMERGE_SUCCESS', YN0049: 'AUTOMERGE_REQUIRED', @@ -53,16 +66,17 @@ const YARN2_ERROR_CODES = { YN0059: 'INVALID_RANGE_PEER_DEPENDENCY', YN0060: 'INCOMPATIBLE_PEER_DEPENDENCY', YN0061: 'DEPRECATED_PACKAGE', - YN0062: 'INCOMPATIBLE_OS', - YN0063: 'INCOMPATIBLE_CPU', YN0068: 'UNUSED_PACKAGE_EXTENSION', YN0069: 'REDUNDANT_PACKAGE_EXTENSION', - YN0071: 'NM_CANT_INSTALL_EXTERNAL_SOFT_LINK', - YN0072: 'NM_PRESERVE_SYMLINKS_REQUIRED', YN0074: 'NM_HARDLINKS_MODE_DOWNGRADED', - YN0075: 'PROLOG_INSTANTIATION_ERROR', - YN0076: 'INCOMPATIBLE_ARCHITECTURE', YN0077: 'GHOST_ARCHITECTURE', + YN0080: 'NETWORK_DISABLED', + YN0085: 'UPDATED_RESOLUTION_RECORD', + YN0086: 'EXPLAIN_PEER_DEPENDENCIES_CTA', + YN0087: 'MIGRATION_SUCCESS', + YN0088: 'VERSION_NOTICE', + YN0089: 'TIPS_NOTICE', + YN0090: 'OFFLINE_MODE_ENABLED', }; // This encompasses both yarn 2 and yarn 3 @@ -284,26 +298,25 @@ export class Yarn2Proxy extends JsPackageManager { } public parseErrorFromLogs(logs: string): string { - let finalMessage = 'YARN2 error'; - const match = logs.match(YARN2_ERROR_REGEX); - - if (match) { - const errorCode = match[1] as keyof typeof YARN2_ERROR_CODES; - if (errorCode) { - finalMessage = `${finalMessage} ${errorCode}`; - } - - const errorType = YARN2_ERROR_CODES[errorCode]; - if (errorType) { - finalMessage = `${finalMessage} - ${errorType}`; - } - - const errorMessage = match[2]; - if (errorMessage) { - finalMessage = `${finalMessage}: ${errorMessage}`; + const finalMessage = 'YARN2 error'; + const errorCodesWithMessages: { code: string; message: string }[] = []; + const regex = /(YN\d{4}): (.+)/g; + let match: RegExpExecArray | null; + + while ((match = regex.exec(logs)) !== null) { + const code = match[1]; + const message = match[2].replace(/[┌│└]/g, '').trim(); + if (CRITICAL_YARN2_ERROR_CODES[code]) { + errorCodesWithMessages.push({ + code, + message: `${CRITICAL_YARN2_ERROR_CODES[code]}\n-> ${message}\n`, + }); } } - return finalMessage.trim(); + return [ + finalMessage, + errorCodesWithMessages.map(({ code, message }) => `${code}: ${message}`).join('\n'), + ].join('\n'); } } diff --git a/code/lib/core-webpack/src/importPipeline.ts b/code/lib/core-webpack/src/importPipeline.ts index c4a7a220a94b..167bae11c100 100644 --- a/code/lib/core-webpack/src/importPipeline.ts +++ b/code/lib/core-webpack/src/importPipeline.ts @@ -3,9 +3,9 @@ type ModuleExports = Record; // If an import is in flight when another import arrives, block it until the first completes. // This is to avoid a situation where webpack kicks off a second compilation whilst the // first is still completing, cf: https://github.com/webpack/webpack/issues/15541#issuecomment-1143138832 -// Note the way this code works if N futher `import()`s occur while the first is in flight, +// Note the way this code works if N further `import()`s occur while the first is in flight, // they will all be kicked off in the same tick and not block each other. This is by design, -// Webpack can handle multiple invalidations simutaneously, just not in quick succession. +// Webpack can handle multiple invalidations simultaneously, just not in quick succession. export function importPipeline() { let importGate: Promise = Promise.resolve(); diff --git a/code/lib/docs-tools/src/argTypes/convert/flow/convert.ts b/code/lib/docs-tools/src/argTypes/convert/flow/convert.ts index 5886babe7301..b950be171bee 100644 --- a/code/lib/docs-tools/src/argTypes/convert/flow/convert.ts +++ b/code/lib/docs-tools/src/argTypes/convert/flow/convert.ts @@ -41,13 +41,13 @@ export const convert = (type: FlowType): SBType | void => { case 'signature': return { ...base, ...convertSig(type) }; case 'union': - if (type.elements.every(isLiteral)) { - return { ...base, name: 'enum', value: type.elements.map(toEnumOption) }; + if (type.elements?.every(isLiteral)) { + return { ...base, name: 'enum', value: type.elements?.map(toEnumOption) }; } - return { ...base, name, value: type.elements.map(convert) }; + return { ...base, name, value: type.elements?.map(convert) }; case 'intersection': - return { ...base, name, value: type.elements.map(convert) }; + return { ...base, name, value: type.elements?.map(convert) }; default: return { ...base, name: 'other', value: name }; } diff --git a/code/lib/docs-tools/src/argTypes/convert/typescript/convert.ts b/code/lib/docs-tools/src/argTypes/convert/typescript/convert.ts index 2bf1fe65e552..95436dfcb567 100644 --- a/code/lib/docs-tools/src/argTypes/convert/typescript/convert.ts +++ b/code/lib/docs-tools/src/argTypes/convert/typescript/convert.ts @@ -38,19 +38,19 @@ export const convert = (type: TSType): SBType | void => { return { ...base, ...convertSig(type) }; case 'union': let result; - if (type.elements.every((element) => element.name === 'literal')) { + if (type.elements?.every((element) => element.name === 'literal')) { result = { ...base, name: 'enum', // @ts-expect-error fix types - value: type.elements.map((v) => parseLiteral(v.value)), + value: type.elements?.map((v) => parseLiteral(v.value)), }; } else { - result = { ...base, name, value: type.elements.map(convert) }; + result = { ...base, name, value: type.elements?.map(convert) }; } return result; case 'intersection': - return { ...base, name, value: type.elements.map(convert) }; + return { ...base, name, value: type.elements?.map(convert) }; default: return { ...base, name: 'other', value: name }; } diff --git a/code/lib/docs-tools/src/argTypes/convert/typescript/types.ts b/code/lib/docs-tools/src/argTypes/convert/typescript/types.ts index aed18f3f4160..79df2e812c00 100644 --- a/code/lib/docs-tools/src/argTypes/convert/typescript/types.ts +++ b/code/lib/docs-tools/src/argTypes/convert/typescript/types.ts @@ -9,7 +9,7 @@ type TSArgType = TSType; type TSCombinationType = TSBaseType & { name: 'union' | 'intersection'; - elements: TSType[]; + elements?: TSType[]; }; type TSFuncSigType = TSBaseType & { diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index 57c812109e89..f0a11336666b 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -576,7 +576,7 @@ export class Instrumenter { update(call: Call) { this.channel.emit(EVENTS.CALL, call); this.setState(call.storyId, ({ calls }) => { - // Omit earlier calls for the same ID, which may have been superceded by a later invocation. + // Omit earlier calls for the same ID, which may have been superseded by a later invocation. // This typically happens when calls are part of a callback which runs multiple times. const callsById = calls .concat(call) diff --git a/code/lib/manager-api/src/tests/stories.test.ts b/code/lib/manager-api/src/tests/stories.test.ts index ba45fb688c31..9adec207d158 100644 --- a/code/lib/manager-api/src/tests/stories.test.ts +++ b/code/lib/manager-api/src/tests/stories.test.ts @@ -225,7 +225,7 @@ describe('stories API', () => { }, }); const { index } = store.getState(); - // We need exact key ordering, even if in theory JS doens't guarantee it + // We need exact key ordering, even if in theory JS doesn't guarantee it expect(Object.keys(index!)).toEqual(['a', 'a-b', 'a-b--1']); expect(index!.a).toMatchObject({ type: 'root', @@ -264,7 +264,7 @@ describe('stories API', () => { }, }); const { index } = store.getState(); - // We need exact key ordering, even if in theory JS doens't guarantee it + // We need exact key ordering, even if in theory JS doesn't guarantee it expect(Object.keys(index!)).toEqual(['a', 'a--1']); expect(index!.a).toMatchObject({ type: 'component', @@ -295,7 +295,7 @@ describe('stories API', () => { }, }); const { index } = store.getState(); - // We need exact key ordering, even if in theory JS doens't guarantee it + // We need exact key ordering, even if in theory JS doesn't guarantee it expect(Object.keys(index!)).toEqual(['a', 'a--1', 'a--2', 'b', 'b--1']); expect(index!.a).toMatchObject({ type: 'component', diff --git a/code/lib/preview-api/src/modules/preview-web/UrlStore.ts b/code/lib/preview-api/src/modules/preview-web/UrlStore.ts index 16d66ba7f6b6..b7890cc3687a 100644 --- a/code/lib/preview-api/src/modules/preview-web/UrlStore.ts +++ b/code/lib/preview-api/src/modules/preview-web/UrlStore.ts @@ -22,7 +22,7 @@ const getQueryString = ({ selection?: Selection; extraParams?: qs.ParsedQs; }) => { - const { search = '' } = document.location; + const search = typeof document !== 'undefined' ? document.location.search : ''; const { path, selectedKind, selectedStory, ...rest } = qs.parse(search, { ignoreQueryPrefix: true, }); @@ -65,20 +65,22 @@ const getFirstString = (v: ValueOf): string | void => { }; export const getSelectionSpecifierFromPath: () => SelectionSpecifier | null = () => { - const query = qs.parse(document.location.search, { ignoreQueryPrefix: true }); - const args = typeof query.args === 'string' ? parseArgsParam(query.args) : undefined; - const globals = typeof query.globals === 'string' ? parseArgsParam(query.globals) : undefined; - - let viewMode = getFirstString(query.viewMode) as ViewMode; - if (typeof viewMode !== 'string' || !viewMode.match(/docs|story/)) { - viewMode = 'story'; - } - - const path = getFirstString(query.path); - const storyId = path ? pathToId(path) : getFirstString(query.id); - - if (storyId) { - return { storySpecifier: storyId, args, globals, viewMode }; + if (typeof document !== 'undefined') { + const query = qs.parse(document.location.search, { ignoreQueryPrefix: true }); + const args = typeof query.args === 'string' ? parseArgsParam(query.args) : undefined; + const globals = typeof query.globals === 'string' ? parseArgsParam(query.globals) : undefined; + + let viewMode = getFirstString(query.viewMode) as ViewMode; + if (typeof viewMode !== 'string' || !viewMode.match(/docs|story/)) { + viewMode = 'story'; + } + + const path = getFirstString(query.path); + const storyId = path ? pathToId(path) : getFirstString(query.id); + + if (storyId) { + return { storySpecifier: storyId, args, globals, viewMode }; + } } return null; diff --git a/code/lib/preview-api/src/modules/preview-web/WebView.ts b/code/lib/preview-api/src/modules/preview-web/WebView.ts index 1046df03abba..82c6a294bf05 100644 --- a/code/lib/preview-api/src/modules/preview-web/WebView.ts +++ b/code/lib/preview-api/src/modules/preview-web/WebView.ts @@ -46,22 +46,24 @@ export class WebView implements View { constructor() { // Special code for testing situations - // eslint-disable-next-line @typescript-eslint/naming-convention - const { __SPECIAL_TEST_PARAMETER__ } = qs.parse(document.location.search, { - ignoreQueryPrefix: true, - }); - switch (__SPECIAL_TEST_PARAMETER__) { - case 'preparing-story': { - this.showPreparingStory(); - this.testing = true; - break; - } - case 'preparing-docs': { - this.showPreparingDocs(); - this.testing = true; - break; + if (typeof document !== 'undefined') { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { __SPECIAL_TEST_PARAMETER__ } = qs.parse(document.location.search, { + ignoreQueryPrefix: true, + }); + switch (__SPECIAL_TEST_PARAMETER__) { + case 'preparing-story': { + this.showPreparingStory(); + this.testing = true; + break; + } + case 'preparing-docs': { + this.showPreparingDocs(); + this.testing = true; + break; + } + default: // pass; } - default: // pass; } } @@ -114,8 +116,10 @@ export class WebView implements View { checkIfLayoutExists(layout: keyof typeof layoutClassMap) { if (!layoutClassMap[layout]) { logger.warn( - dedent`The desired layout: ${layout} is not a valid option. - The possible options are: ${Object.keys(layoutClassMap).join(', ')}, none.` + dedent` + The desired layout: ${layout} is not a valid option. + The possible options are: ${Object.keys(layoutClassMap).join(', ')}, none. + ` ); } } diff --git a/code/lib/preview-api/src/modules/preview-web/render/Render.ts b/code/lib/preview-api/src/modules/preview-web/render/Render.ts index 9ed82484900e..e1a82c998410 100644 --- a/code/lib/preview-api/src/modules/preview-web/render/Render.ts +++ b/code/lib/preview-api/src/modules/preview-web/render/Render.ts @@ -5,7 +5,7 @@ export type RenderType = 'story' | 'docs'; /** * A "Render" represents the rendering of a single entry to a single location * - * The implemenations of render are used for two key purposes: + * The implementations of render are used for two key purposes: * - Tracking the state of the rendering as it moves between preparing, rendering and tearing down. * - Tracking what is rendered to know if a change requires re-rendering or teardown + recreation. */ diff --git a/code/lib/preview-api/src/modules/store/GlobalsStore.test.ts b/code/lib/preview-api/src/modules/store/GlobalsStore.test.ts index 5d67a641b454..7cee817d2261 100644 --- a/code/lib/preview-api/src/modules/store/GlobalsStore.test.ts +++ b/code/lib/preview-api/src/modules/store/GlobalsStore.test.ts @@ -146,7 +146,7 @@ describe('GlobalsStore', () => { arg2: { defaultValue: 'arg2' }, }, }); - // However undeclared valuse aren't persisted + // However undeclared values aren't persisted expect(store.get()).toEqual({ arg1: 'new-arg1', arg2: 'new-arg2' }); }); }); diff --git a/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts b/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts index 0e76648dd5cb..b05485e5a506 100644 --- a/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts +++ b/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts @@ -150,7 +150,7 @@ describe('prepareStory', () => { }); }); - it('can be overriden by `undefined`', () => { + it('can be overridden by `undefined`', () => { const { initialArgs } = prepareStory( { id, name, args: { a: undefined }, moduleExport }, { id, title, args: { a: 'component' } }, diff --git a/code/lib/react-dom-shim/src/preset.ts b/code/lib/react-dom-shim/src/preset.ts index 63b2889ba93b..e863a53262b6 100644 --- a/code/lib/react-dom-shim/src/preset.ts +++ b/code/lib/react-dom-shim/src/preset.ts @@ -1,5 +1,5 @@ import type { Options } from '@storybook/types'; -import { join, dirname } from 'path'; +import { join, dirname, isAbsolute } from 'path'; import { readFile } from 'fs/promises'; /** @@ -17,6 +17,12 @@ const getIsReactVersion18 = async (options: Options) => { const resolvedReact = await options.presets.apply<{ reactDom?: string }>('resolvedReact', {}); const reactDom = resolvedReact.reactDom || dirname(require.resolve('react-dom/package.json')); + if (!isAbsolute(reactDom)) { + // if react-dom is not resolved to a file we can't be sure if the version in package.json is correct or even if package.json exists + // this happens when react-dom is resolved to 'preact/compat' for example + return false; + } + const { version } = JSON.parse(await readFile(join(reactDom, 'package.json'), 'utf-8')); return version.startsWith('18') || version.startsWith('0.0.0'); }; diff --git a/code/lib/theming/package.json b/code/lib/theming/package.json index 00290cc8e9ff..8a24530c41cd 100644 --- a/code/lib/theming/package.json +++ b/code/lib/theming/package.json @@ -56,8 +56,8 @@ }, "devDependencies": { "@emotion/cache": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/react": "^11.11.1", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@types/fs-extra": "^11.0.1", "@types/node": "^18.0.0", diff --git a/code/lib/types/src/modules/story.ts b/code/lib/types/src/modules/story.ts index f22d18ad7a3d..ee34ad43ce79 100644 --- a/code/lib/types/src/modules/story.ts +++ b/code/lib/types/src/modules/story.ts @@ -79,7 +79,7 @@ export type NormalizedStoryAnnotations = 'storyName' | 'story' | 'decorators' | 'loaders' > & { moduleExport: ModuleExport; - // You cannot actually set id on story annotations, but we normalize it to be there for convience + // You cannot actually set id on story annotations, but we normalize it to be there for convenience id: StoryId; argTypes?: StrictArgTypes; name: StoryName; diff --git a/code/package.json b/code/package.json index 6e2885c0d0c1..feb222cbcaf0 100644 --- a/code/package.json +++ b/code/package.json @@ -299,5 +299,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.1.0-alpha.5" } diff --git a/code/renderers/preact/src/preset.ts b/code/renderers/preact/src/preset.ts index 03b11e7e6097..28e5a428af0a 100644 --- a/code/renderers/preact/src/preset.ts +++ b/code/renderers/preact/src/preset.ts @@ -13,3 +13,19 @@ export const previewAnnotations: PresetProperty<'previewAnnotations'> = async ( .concat([join(__dirname, 'entry-preview.mjs')]) .concat(docsEnabled ? [join(__dirname, 'entry-preview-docs.mjs')] : []); }; + +/** + * Alias react and react-dom to preact/compat similar to the preact vite preset + * https://github.com/preactjs/preset-vite/blob/main/src/index.ts#L238-L239 + */ +export const resolvedReact = async (existing: any) => { + try { + return { + ...existing, + react: 'preact/compat', + reactDom: 'preact/compat', + }; + } catch (e) { + return existing; + } +}; diff --git a/code/renderers/preact/src/render.tsx b/code/renderers/preact/src/render.tsx index 014a6ec19733..e4c4ab64b485 100644 --- a/code/renderers/preact/src/render.tsx +++ b/code/renderers/preact/src/render.tsx @@ -16,7 +16,7 @@ export const render: ArgsStoryFn = (args, context) => { } // @ts-expect-error I think the type of Component should be Preact.ComponentType, but even that - // doens't make TS happy, I suspect because TS wants "react" components. + // doesn't make TS happy, I suspect because TS wants "react" components. return ; }; diff --git a/code/renderers/preact/template/stories/React.js b/code/renderers/preact/template/stories/React.jsx similarity index 100% rename from code/renderers/preact/template/stories/React.js rename to code/renderers/preact/template/stories/React.jsx diff --git a/code/renderers/preact/template/stories/react-compat.stories.js b/code/renderers/preact/template/stories/react-compat.stories.jsx similarity index 96% rename from code/renderers/preact/template/stories/react-compat.stories.js rename to code/renderers/preact/template/stories/react-compat.stories.jsx index 33f1078d3154..b43a0650e748 100644 --- a/code/renderers/preact/template/stories/react-compat.stories.js +++ b/code/renderers/preact/template/stories/react-compat.stories.jsx @@ -1,4 +1,4 @@ -import { ReactFunctionalComponent, ReactClassComponent } from './React'; +import { ReactFunctionalComponent, ReactClassComponent } from './React.jsx'; export default { component: ReactFunctionalComponent, diff --git a/code/ui/blocks/src/blocks/useStory.ts b/code/ui/blocks/src/blocks/useStory.ts index 63b2581e2d39..95edc5b7392a 100644 --- a/code/ui/blocks/src/blocks/useStory.ts +++ b/code/ui/blocks/src/blocks/useStory.ts @@ -16,7 +16,7 @@ export function useStories( context: DocsContextProps ): (PreparedStory | void)[] { // Legacy docs pages can reference any story by id. Those stories will need to be - // asyncronously loaded; we use the state for this + // asynchronously loaded; we use the state for this const [storiesById, setStories] = useState>>({}); useEffect(() => { diff --git a/code/ui/blocks/src/components/ArgsTable/types.ts b/code/ui/blocks/src/components/ArgsTable/types.ts index a8f3dd1d453e..653bf236acdb 100644 --- a/code/ui/blocks/src/components/ArgsTable/types.ts +++ b/code/ui/blocks/src/components/ArgsTable/types.ts @@ -41,6 +41,21 @@ export interface ArgType { description?: string; defaultValue?: any; if?: Conditional; + table?: { + category?: string; + disable?: boolean; + subcategory?: string; + defaultValue?: { + summary: string; + detail?: string; + }; + type?: { + summary: string; + detail?: string; + }; + readonly?: boolean; + [key: string]: any; + }; [key: string]: any; } diff --git a/code/ui/blocks/src/controls/Boolean.stories.tsx b/code/ui/blocks/src/controls/Boolean.stories.tsx index 8f7c043701c6..4629f2cc3644 100644 --- a/code/ui/blocks/src/controls/Boolean.stories.tsx +++ b/code/ui/blocks/src/controls/Boolean.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { within, fireEvent, waitFor, expect } from '@storybook/test'; +import { within, fireEvent, waitFor, expect, fn } from '@storybook/test'; import { addons } from '@storybook/preview-api'; import { RESET_STORY_ARGS, STORY_ARGS_UPDATED } from '@storybook/core-events'; import { BooleanControl } from './Boolean'; @@ -14,31 +14,36 @@ const meta = { info: 'This is info for the Boolean control stories', jsx: { useBooleanShorthandSyntax: false }, }, -} as Meta; + args: { + onChange: fn(), + }, +} satisfies Meta; export default meta; -export const True: StoryObj = { +type Story = StoryObj; + +export const True: Story = { args: { value: true, name: 'True', }, }; -export const False: StoryObj = { +export const False: Story = { args: { value: false, name: 'False', }, }; -export const Undefined: StoryObj = { +export const Undefined: Story = { args: { value: undefined, name: 'Undefined', }, }; -export const Toggling: StoryObj = { +export const Toggling: Story = { args: { value: undefined, name: 'Toggling', @@ -78,7 +83,7 @@ export const Toggling: StoryObj = { }, }; -export const TogglingInDocs: StoryObj = { +export const TogglingInDocs: Story = { ...Toggling, args: { name: 'Toggling In Docs', @@ -89,3 +94,19 @@ export const TogglingInDocs: StoryObj = { }, }, }; + +export const Readonly: Story = { + args: { + name: 'readonly', + value: true, + argType: { table: { readonly: true } }, + }, +}; + +export const ReadonlyAndUndefined: Story = { + args: { + name: 'readonly-and-undefined', + value: undefined, + argType: { table: { readonly: true } }, + }, +}; diff --git a/code/ui/blocks/src/controls/Boolean.tsx b/code/ui/blocks/src/controls/Boolean.tsx index 7045d214b3c3..267f44cd5107 100644 --- a/code/ui/blocks/src/controls/Boolean.tsx +++ b/code/ui/blocks/src/controls/Boolean.tsx @@ -19,6 +19,13 @@ const Label = styled.label(({ theme }) => ({ background: theme.boolean.background, borderRadius: '3em', padding: 1, + '&[aria-disabled="true"]': { + opacity: 0.5, + + input: { + cursor: 'not-allowed', + }, + }, input: { appearance: 'none', @@ -98,8 +105,16 @@ export type BooleanProps = ControlProps & BooleanConfig; * * ``` */ -export const BooleanControl: FC = ({ name, value, onChange, onBlur, onFocus }) => { +export const BooleanControl: FC = ({ + name, + value, + onChange, + onBlur, + onFocus, + argType, +}) => { const onSetFalse = useCallback(() => onChange(false), [onChange]); + const readonly = !!argType?.table?.readonly; if (value === undefined) { return ( @@ -117,13 +133,14 @@ export const BooleanControl: FC = ({ name, value, onChange, onBlur const parsedValue = typeof value === 'string' ? parse(value) : value; return ( -