diff --git a/code/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch b/code/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch index ea5e834a06df..9a1b74e203bd 100644 --- a/code/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch +++ b/code/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch @@ -1,10 +1,10 @@ diff --git a/dist/index.js b/dist/index.js -index 974d6b26f626024fc9904908100c9ecaa54f43e1..5be2d35267e7f0525c6588758dbebe72599f88a9 100644 +index 974d6b26f626024fc9904908100c9ecaa54f43e1..5d9d92a0796e02630ccdd1174d4fd25e016d2b06 100644 --- a/dist/index.js +++ b/dist/index.js -@@ -6,31 +6,37 @@ import { processError } from '@vitest/utils/error'; +@@ -6,28 +6,35 @@ import { processError } from '@vitest/utils/error'; import { util } from 'chai'; - + const MATCHERS_OBJECT = Symbol.for("matchers-object"); -const JEST_MATCHERS_OBJECT = Symbol.for("$$jest-matchers-object"); +// Patched this symbol for storybook, so that @storybook/test can be used in a jest environment as well. @@ -12,17 +12,14 @@ index 974d6b26f626024fc9904908100c9ecaa54f43e1..5be2d35267e7f0525c6588758dbebe72 +const JEST_MATCHERS_OBJECT = Symbol.for("$$jest-matchers-object-storybook"); const GLOBAL_EXPECT = Symbol.for("expect-global"); const ASYMMETRIC_MATCHERS_OBJECT = Symbol.for("asymmetric-matchers-object"); - + if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) { const globalState = /* @__PURE__ */ new WeakMap(); - const matchers = /* @__PURE__ */ Object.create(null); - const assymetricMatchers = /* @__PURE__ */ Object.create(null); +- const assymetricMatchers = /* @__PURE__ */ Object.create(null); Object.defineProperty(globalThis, MATCHERS_OBJECT, { get: () => globalState }); -+ Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, { -+ get: () => assymetricMatchers -+ }); +} +if (!Object.prototype.hasOwnProperty.call(globalThis, JEST_MATCHERS_OBJECT)) { + const matchers = /* @__PURE__ */ Object.create(null); @@ -34,15 +31,14 @@ index 974d6b26f626024fc9904908100c9ecaa54f43e1..5be2d35267e7f0525c6588758dbebe72 matchers }) }); -- Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, { -- get: () => assymetricMatchers -- }); ++} ++if (!Object.prototype.hasOwnProperty.call(globalThis, ASYMMETRIC_MATCHERS_OBJECT)) { ++ const assymetricMatchers = /* @__PURE__ */ Object.create(null); + Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, { + get: () => assymetricMatchers + }); } + function getState(expect) { return globalThis[MATCHERS_OBJECT].get(expect); } -+ - function setState(state, expect) { - const map = globalThis[MATCHERS_OBJECT]; - const current = map.get(expect) || {}; diff --git a/code/builders/builder-vite/README.md b/code/builders/builder-vite/README.md index 02532578fcad..18a2e0ec6df3 100644 --- a/code/builders/builder-vite/README.md +++ b/code/builders/builder-vite/README.md @@ -27,7 +27,7 @@ When installing Storybook, use the `--builder=vite` flag if you do not have a `v The builder supports both development mode in Storybook and building a static production version. -Your `vite.config` file will be used by Storybook. If you need to customize the vite config for Storybook, you have two choices: +Your `vite.config` file will be used by Storybook. If you need to customize the Vite config for Storybook, you have two choices: 1. Set values in your `vite.config` conditionally, based on an environment variable, for example. 2. Add a `viteFinal` config to your `.storybook/main.js` file. See [Customize Vite config](#customize-vite-config) for details. @@ -44,12 +44,12 @@ npx storybook@latest init --builder vite && npm run storybook ### Migration from webpack / CRA 1. Install `vite` and `@storybook/builder-vite` -2. Remove any explicit project dependencies on `webpack`, `react-scripts`, and any other webpack plugins or loaders. +2. Remove any explicit project dependencies on `webpack`, `react-scripts`, and any other Webpack plugins or loaders. 3. If you were previously using `@storybook/manager-webpack5`, you can remove it. Also remove `@storybook/builder-webpack5` or `@storybook/builder-webpack4` if they are installed. -4. Choose a vite-based Storybook "framework" to set in the `framework` option of your `.storybook/main.js` file. -5. Remove storybook webpack cache (`rm -rf node_modules/.cache`) -6. Update your `/public/index.html` file for vite (be sure there are no `%PUBLIC_URL%` inside it, which is a CRA variable) -7. Be sure that any files containing JSX syntax use a `.jsx` or `.tsx` file extension, which [vite requires](https://vitejs.dev/guide/features.html#jsx). This includes `.storybook/preview.jsx` if it contains JSX syntax. +4. Choose a Vite-based Storybook "framework" to set in the `framework` option of your `.storybook/main.js` file. +5. Remove Storybook Webpack cache (`rm -rf node_modules/.cache`) +6. Update your `/public/index.html` file for Vite (be sure there are no `%PUBLIC_URL%` inside it, which is a CRA variable) +7. Be sure that any files containing JSX syntax use a `.jsx` or `.tsx` file extension, which [Vite requires](https://vitejs.dev/guide/features.html#jsx). This includes `.storybook/preview.jsx` if it contains JSX syntax. 8. If you are using `@storybook/addon-interactions`, for now you'll need to add a [workaround](https://github.com/storybookjs/storybook/issues/18399) for jest-mock relying on the node `global` variable by creating a `.storybook/preview-head.html` file containing the following: ```html @@ -58,9 +58,9 @@ npx storybook@latest init --builder vite && npm run storybook ``` -9. Start up your storybook using the same `yarn storybook` or `npm run storybook` commands you are used to. +9. Start up your Storybook using the same `yarn storybook` or `npm run storybook` commands you are used to. -For other details about the differences between vite and webpack projects, be sure to read through the [vite documentation](https://vitejs.dev/). +For other details about the differences between Vite and Webpack projects, be sure to read through the [Vite documentation](https://vitejs.dev/). ### Customize Vite config @@ -105,7 +105,7 @@ const config = { export default config; ``` -The `viteFinal` function will give you `config` which is the combination of your project's vite config and the builder's own Vite config. +The `viteFinal` function will give you `config` which is the combination of your project's Vite config and the builder's own Vite config. You can tweak this as you want, for example to set up aliases, add new plugins etc. The `configType` variable will be either `"DEVELOPMENT"` or `"PRODUCTION"`. @@ -149,7 +149,7 @@ If you're using TypeScript, we encourage you to experiment and see which option The builder will by default enable Vite's [server.fs.strict](https://vitejs.dev/config/#server-fs-strict) option, for increased security. The default project `root` is set to the parent directory of the -storybook configuration directory. This can be overridden in viteFinal. +Storybook configuration directory. This can be overridden in [viteFinal](https://storybook.js.org/docs/api/main-config-vite-final). ## Known issues @@ -158,7 +158,8 @@ storybook configuration directory. This can be overridden in viteFinal. ## Contributing The Vite builder cannot build itself. -Are you willing to contribute? We are especially looking for Vue 3 and Svelte experts, as the current maintainers are react users. + +Are you willing to contribute? We are especially looking for Vue and Svelte experts, as the current maintainers are React users. Have a look at the GitHub issues with the `vite` label for known bugs. If you find any new bugs, feel free to create an issue or send a pull request! diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index acfd7e4e6eb7..0321ec966fd7 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -1,7 +1,7 @@ import { getStorybookInfo, serverRequire, - getStorybookVersion, + getCoercedStorybookVersion, isCorePackage, JsPackageManagerFactory, type PackageManagerName, @@ -107,8 +107,9 @@ export async function add( // add to package.json const isStorybookAddon = addonName.startsWith('@storybook/'); const isAddonFromCore = isCorePackage(addonName); - const storybookVersion = await getStorybookVersion(packageManager); + const storybookVersion = await getCoercedStorybookVersion(packageManager); const version = versionSpecifier || (isAddonFromCore ? storybookVersion : latestVersion); + const addonWithVersion = SemVer.valid(version) ? `${addonName}@^${version}` : `${addonName}@${version}`; diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts index c816ee90c5ec..c721dae39a31 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts @@ -12,7 +12,7 @@ import chalk from 'chalk'; import dedent from 'ts-dedent'; import path from 'path'; import type { JsPackageManager } from '@storybook/core-common'; -import { getStorybookVersion } from '@storybook/core-common'; +import { getCoercedStorybookVersion } from '@storybook/core-common'; const logger = console; @@ -93,7 +93,7 @@ export const getStorybookData = async ({ configDir: configDirFromScript, previewConfig: previewConfigPath, } = getStorybookInfo(packageJson, userDefinedConfigDir); - const storybookVersion = await getStorybookVersion(packageManager); + const storybookVersion = await getCoercedStorybookVersion(packageManager); const configDir = userDefinedConfigDir || configDirFromScript || '.storybook'; diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index 604393de492c..3adeff5e0ead 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -10,7 +10,7 @@ import invariant from 'tiny-invariant'; import { getStorybookInfo, loadMainConfig, - getStorybookVersion, + getCoercedStorybookVersion, JsPackageManagerFactory, } from '@storybook/core-common'; import type { PackageManagerName } from '@storybook/core-common'; @@ -156,7 +156,7 @@ export async function runFixes({ userSpecifiedConfigDir ); - const storybookVersion = await getStorybookVersion(packageManager); + const storybookVersion = await getCoercedStorybookVersion(packageManager); if (!storybookVersion) { logger.info(dedent` 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 79dfe4a5fb29..923a06409968 100644 --- a/code/lib/core-common/src/js-package-manager/JsPackageManager.ts +++ b/code/lib/core-common/src/js-package-manager/JsPackageManager.ts @@ -55,7 +55,10 @@ export abstract class JsPackageManager { basePath?: string ): Promise; - public abstract getPackageVersion(packageName: string, basePath?: string): Promise; + async getPackageVersion(packageName: string, basePath = this.cwd): Promise { + const packageJSON = await this.getPackageJSON(packageName, basePath); + return packageJSON ? packageJSON.version ?? null : null; + } // NOTE: for some reason yarn prefers the npm registry in // local development, so always use npm diff --git a/code/lib/core-common/src/js-package-manager/NPMProxy.ts b/code/lib/core-common/src/js-package-manager/NPMProxy.ts index 6e4e2939fca1..62d8be2fec5c 100644 --- a/code/lib/core-common/src/js-package-manager/NPMProxy.ts +++ b/code/lib/core-common/src/js-package-manager/NPMProxy.ts @@ -4,7 +4,6 @@ import dedent from 'ts-dedent'; import { sync as findUpSync } from 'find-up'; import { existsSync, readFileSync } from 'fs'; import path from 'path'; -import semver from 'semver'; import { logger } from '@storybook/node-logger'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; @@ -102,11 +101,6 @@ export class NPMProxy extends JsPackageManager { return packageJson; } - public async getPackageVersion(packageName: string, basePath = this.cwd): Promise { - const packageJson = await this.getPackageJSON(packageName, basePath); - return packageJson ? semver.coerce(packageJson.version)?.version ?? null : null; - } - getInstallArgs(): string[] { if (!this.installArgs) { this.installArgs = []; diff --git a/code/lib/core-common/src/js-package-manager/PNPMProxy.ts b/code/lib/core-common/src/js-package-manager/PNPMProxy.ts index d6cb6c99c175..7e7f0279e226 100644 --- a/code/lib/core-common/src/js-package-manager/PNPMProxy.ts +++ b/code/lib/core-common/src/js-package-manager/PNPMProxy.ts @@ -3,7 +3,6 @@ import dedent from 'ts-dedent'; import { sync as findUpSync } from 'find-up'; import path from 'path'; import fs from 'fs'; -import semver from 'semver'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; @@ -159,12 +158,6 @@ export class PNPMProxy extends JsPackageManager { return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); } - async getPackageVersion(packageName: string, basePath = this.cwd): Promise { - const packageJSON = await this.getPackageJSON(packageName, basePath); - - return packageJSON ? semver.coerce(packageJSON.version)?.version ?? null : null; - } - protected getResolutions(packageJson: PackageJson, versions: Record) { return { overrides: { diff --git a/code/lib/core-common/src/js-package-manager/Yarn1Proxy.ts b/code/lib/core-common/src/js-package-manager/Yarn1Proxy.ts index 039a06f956cf..2018e8f8ddf4 100644 --- a/code/lib/core-common/src/js-package-manager/Yarn1Proxy.ts +++ b/code/lib/core-common/src/js-package-manager/Yarn1Proxy.ts @@ -2,7 +2,6 @@ import dedent from 'ts-dedent'; import { sync as findUpSync } from 'find-up'; import { existsSync, readFileSync } from 'fs'; import path from 'path'; -import semver from 'semver'; import { createLogStream } from '../utils/cli'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; @@ -82,11 +81,6 @@ export class Yarn1Proxy extends JsPackageManager { return JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as Record; } - public async getPackageVersion(packageName: string, basePath = this.cwd): Promise { - const packageJson = await this.getPackageJSON(packageName, basePath); - return packageJson ? semver.coerce(packageJson.version)?.version ?? null : null; - } - public async findInstallations(pattern: string[]) { const commandResult = await this.executeCommand({ command: 'yarn', 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 3dde844cba15..4f76868d395c 100644 --- a/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/core-common/src/js-package-manager/Yarn2Proxy.ts @@ -4,7 +4,6 @@ import { existsSync, readFileSync } from 'fs'; import path from 'path'; import { PosixFS, VirtualFS, ZipOpenFS } from '@yarnpkg/fslib'; import { getLibzipSync } from '@yarnpkg/libzip'; -import semver from 'semver'; import { createLogStream } from '../utils/cli'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; @@ -174,11 +173,6 @@ export class Yarn2Proxy extends JsPackageManager { return packageJson; } - async getPackageVersion(packageName: string, basePath = this.cwd): Promise { - const packageJSON = await this.getPackageJSON(packageName, basePath); - return packageJSON ? semver.coerce(packageJSON.version)?.version ?? null : null; - } - protected getResolutions(packageJson: PackageJson, versions: Record) { return { resolutions: { diff --git a/code/lib/core-common/src/utils/cli.test.ts b/code/lib/core-common/src/utils/cli.test.ts index f2444debb22a..708a11ebbba8 100644 --- a/code/lib/core-common/src/utils/cli.test.ts +++ b/code/lib/core-common/src/utils/cli.test.ts @@ -5,13 +5,12 @@ describe('UTILS', () => { describe.each([ ['@storybook/react', true], ['@storybook/node-logger', true], - ['@storybook/addon-info', true], - ['@storybook/something-random', true], - ['@storybook/preset-create-react-app', false], ['@storybook/linter-config', false], ['@storybook/design-system', false], ['@storybook/addon-styling', false], ['@storybook/addon-styling-webpack', false], + ['@storybook/addon-webpack5-compiler-swc', false], + ['@storybook/addon-webpack5-compiler-babel', false], ['@nx/storybook', false], ['@nrwl/storybook', false], ])('isCorePackage', (input, output) => { diff --git a/code/lib/core-common/src/utils/cli.ts b/code/lib/core-common/src/utils/cli.ts index be022eac4460..c4103bd59137 100644 --- a/code/lib/core-common/src/utils/cli.ts +++ b/code/lib/core-common/src/utils/cli.ts @@ -4,6 +4,7 @@ import { join } from 'path'; import tempy from 'tempy'; import { rendererPackages } from './get-storybook-info'; import type { JsPackageManager } from '../js-package-manager'; +import versions from '../versions'; export function parseList(str: string): string[] { return str @@ -12,7 +13,13 @@ export function parseList(str: string): string[] { .filter((item) => item.length > 0); } -export async function getStorybookVersion(packageManager: JsPackageManager) { +/** + * Given a package manager, returns the coerced version of Storybook. + * It tries to find renderer packages in the project and returns the coerced version of the first one found. + * Example: + * If @storybook/react version 8.0.0-alpha.14 is installed, it returns the coerced version 8.0.0 + */ +export async function getCoercedStorybookVersion(packageManager: JsPackageManager) { const packages = ( await Promise.all( Object.keys(rendererPackages).map(async (pkg) => ({ @@ -97,34 +104,4 @@ export const createLogStream = async ( }); }; -const PACKAGES_EXCLUDED_FROM_CORE = [ - '@storybook/addon-bench', - '@storybook/addon-console', - '@storybook/addon-onboarding', - '@storybook/addon-postcss', - '@storybook/addon-designs', - '@storybook/addon-styling', - '@storybook/addon-styling-webpack', - '@storybook/bench', - '@storybook/builder-vite', - '@storybook/csf', - '@storybook/design-system', - '@storybook/ember-cli-storybook', - '@storybook/eslint-config-storybook', - '@storybook/expect', - '@storybook/jest', - '@storybook/linter-config', - '@storybook/mdx1-csf', - '@storybook/mdx2-csf', - '@storybook/react-docgen-typescript-plugin', - '@storybook/storybook-deployer', - '@storybook/test-runner', - '@storybook/testing-library', - '@storybook/testing-react', - '@nrwl/storybook', - '@nx/storybook', -]; -export const isCorePackage = (pkg: string) => - pkg.startsWith('@storybook/') && - !pkg.startsWith('@storybook/preset-') && - !PACKAGES_EXCLUDED_FROM_CORE.includes(pkg); +export const isCorePackage = (pkg: string) => Object.keys(versions).includes(pkg); diff --git a/code/lib/test/package.json b/code/lib/test/package.json index f49a54ad4b89..ef71e012cd78 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -48,7 +48,7 @@ "@storybook/instrumenter": "workspace:*", "@storybook/preview-api": "workspace:*", "@testing-library/dom": "^9.3.1", - "@testing-library/jest-dom": "^6.1.3", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/user-event": "14.3.0", "@vitest/expect": "1.1.3", "@vitest/spy": "^1.1.3", diff --git a/code/lib/test/src/expect.ts b/code/lib/test/src/expect.ts index 7e8b9d95cf03..da17eb52ff59 100644 --- a/code/lib/test/src/expect.ts +++ b/code/lib/test/src/expect.ts @@ -16,9 +16,10 @@ 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'; type Matchers = PromisifyObject> & - matchers.TestingLibraryMatchers, Promise>; + TestingLibraryMatchers, Promise>; // We only expose the jest compatible API for now export interface Assertion extends Matchers { diff --git a/code/lib/test/src/index.test.ts b/code/lib/test/src/index.test.ts new file mode 100644 index 000000000000..bed58592c1d6 --- /dev/null +++ b/code/lib/test/src/index.test.ts @@ -0,0 +1,8 @@ +import { it } from 'vitest'; +import { expect, fn } from '@storybook/test'; + +it('storybook expect and fn can be used in vitest test', () => { + const spy = fn(); + spy(1); + expect(spy).toHaveBeenCalledWith(1); +}); diff --git a/code/yarn.lock b/code/yarn.lock index ef68bf35adb6..ea86e67e973f 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -31,6 +31,13 @@ __metadata: languageName: node linkType: hard +"@adobe/css-tools@npm:^4.3.2": + version: 4.3.3 + resolution: "@adobe/css-tools@npm:4.3.3" + checksum: e76e712df713964b87cdf2aca1f0477f19bebd845484d5fcba726d3ec7782366e2f26ec8cb2dcfaf47081a5c891987d8a9f5c3f30d11e1eb3c1848adc27fcb24 + languageName: node + linkType: hard + "@ampproject/remapping@npm:2.2.1, @ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.2.1": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -6658,7 +6665,7 @@ __metadata: "@storybook/instrumenter": "workspace:*" "@storybook/preview-api": "workspace:*" "@testing-library/dom": "npm:^9.3.1" - "@testing-library/jest-dom": "npm:^6.1.3" + "@testing-library/jest-dom": "npm:^6.4.0" "@testing-library/user-event": "npm:14.3.0" "@vitest/expect": "npm:1.1.3" "@vitest/spy": "npm:^1.1.3" @@ -6920,7 +6927,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:^6.1.3, @testing-library/jest-dom@npm:^6.1.4": +"@testing-library/jest-dom@npm:^6.1.4": version: 6.1.5 resolution: "@testing-library/jest-dom@npm:6.1.5" dependencies: @@ -6950,6 +6957,39 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-dom@npm:^6.4.0": + version: 6.4.0 + resolution: "@testing-library/jest-dom@npm:6.4.0" + dependencies: + "@adobe/css-tools": "npm:^4.3.2" + "@babel/runtime": "npm:^7.9.2" + aria-query: "npm:^5.0.0" + chalk: "npm:^3.0.0" + css.escape: "npm:^1.5.1" + dom-accessibility-api: "npm:^0.6.3" + lodash: "npm:^4.17.15" + redent: "npm:^3.0.0" + peerDependencies: + "@jest/globals": ">= 28" + "@types/bun": "*" + "@types/jest": ">= 28" + jest: ">= 28" + vitest: ">= 0.32" + peerDependenciesMeta: + "@jest/globals": + optional: true + "@types/bun": + optional: true + "@types/jest": + optional: true + jest: + optional: true + vitest: + optional: true + checksum: 6b7eba9ca388986a721fb12f84adf0f5534bf7ec5851982023a889c4a0afac6e9e91291bdac39e1f59a05adefd7727e30463d98b21c3da32fbfec229ccb11ef1 + languageName: node + linkType: hard + "@testing-library/react@npm:^11.2.2": version: 11.2.7 resolution: "@testing-library/react@npm:11.2.7" @@ -8474,12 +8514,12 @@ __metadata: "@vitest/expect@patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch": version: 1.1.3 - resolution: "@vitest/expect@patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch::version=1.1.3&hash=5d51c9" + resolution: "@vitest/expect@patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch::version=1.1.3&hash=8fb073" dependencies: "@vitest/spy": "npm:1.1.3" "@vitest/utils": "npm:1.1.3" chai: "npm:^4.3.10" - checksum: 426287f864f58b05b1c4689bc87b4ef2ca7b3316a22e8e42d94ee9c125cbc0caf294618c9a1201a8ddf8ab68ce1ab194d1e34589f7d608906a3dc679074cfe22 + checksum: c3bbcae82050b7e92438c85e679ef2cb09162dc5638a10b3f0b5a8fc5600dfb0be578a244d84012ae2f1715748189393ac0fc72b891efff3503338221795ebe5 languageName: node linkType: hard @@ -12988,6 +13028,13 @@ __metadata: languageName: node linkType: hard +"dom-accessibility-api@npm:^0.6.3": + version: 0.6.3 + resolution: "dom-accessibility-api@npm:0.6.3" + checksum: 10bee5aa514b2a9a37c87cd81268db607a2e933a050074abc2f6fa3da9080ebed206a320cbc123567f2c3087d22292853bdfdceaffdd4334ffe2af9510b29360 + languageName: node + linkType: hard + "dom-converter@npm:^0.2.0": version: 0.2.0 resolution: "dom-converter@npm:0.2.0" diff --git a/docs/api/arg-types.md b/docs/api/arg-types.md index ce52bad09f84..ed9f66b4cdd0 100644 --- a/docs/api/arg-types.md +++ b/docs/api/arg-types.md @@ -16,13 +16,13 @@ If you are using the Storybook [docs](../writing-docs/index.md) addon (installed To do so, Storybook uses various static analysis tools depending on your framework. -| Framework | Static analysis tool | -| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| React | [react-docgen](https://github.com/reactjs/react-docgen) or [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) | -| Vue 3 | [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) | -| Angular | [compodoc](https://compodoc.app/) | -| WebComponents | [custom-element.json](https://github.com/webcomponents/custom-elements-json) | -| Ember | [YUI doc](https://github.com/ember-learn/ember-cli-addon-docs-yuidoc#documenting-components) | +| Framework | Static analysis tool | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| React | [react-docgen](https://github.com/reactjs/react-docgen) (default) or [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) | +| Vue | [vue-docgen-api](https://github.com/vue-styleguidist/vue-styleguidist/tree/dev/packages/vue-docgen-api) | +| Angular | [compodoc](https://compodoc.app/) | +| WebComponents | [custom-element.json](https://github.com/webcomponents/custom-elements-json) | +| Ember | [YUI doc](https://github.com/ember-learn/ember-cli-addon-docs-yuidoc#documenting-components) | The data structure of `argTypes` is designed to match the output of the these tools. Properties specified manually will override what is inferred. diff --git a/docs/api/index.md b/docs/api/index.md index 168f0d0456a4..60edd614c32c 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -82,7 +82,7 @@ An overview of all available API references for Storybook. Parameters - Parameters are static metadata used to configure your stories addons in Storybook. They are specified at the story, meta (component), project (global) levels. + Parameters are static metadata used to configure your stories addons in Storybook. They are specified at the story, meta (component), project (global) levels. diff --git a/docs/api/main-config-typescript.md b/docs/api/main-config-typescript.md index ae25c4fb308a..9af77907c54c 100644 --- a/docs/api/main-config-typescript.md +++ b/docs/api/main-config-typescript.md @@ -6,15 +6,32 @@ Parent: [main.js|ts configuration](./main-config.md) Type: + + ```ts { check?: boolean; checkOptions?: CheckOptions; reactDocgen?: 'react-docgen' | 'react-docgen-typescript' | false; reactDocgenTypescriptOptions?: ReactDocgenTypescriptOptions; + skipCompiler?: boolean; } ``` + + + + +```ts +{ + check?: boolean; + checkOptions?: CheckOptions; + skipCompiler?: boolean; +} +``` + + + Configures how Storybook handles [TypeScript files](../configure/typescript.md). ## `check` @@ -49,6 +66,8 @@ Options to pass to `fork-ts-checker-webpack-plugin`, if [enabled](#check). See [ + + ## `reactDocgen` Type: `'react-docgen' | 'react-docgen-typescript' | false` @@ -58,13 +77,13 @@ Default: - `false`: if `@storybook/react` is not installed - `'react-docgen'`: if `@storybook/react` is installed -Only available for React Storybook projects. Configure which library, if any, Storybook uses to parse React components, [react-docgen](https://github.com/reactjs/react-docgen) or [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript). Set to `false` to disable parsing React components. `react-docgen-typescript` invokes the TypeScript compiler, which makes it slow but generally accurate. `react-docgen` performs its own analysis, which is much faster but incomplete. +Configures which library, if any, Storybook uses to parse React components, [react-docgen](https://github.com/reactjs/react-docgen) or [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript). Set to `false` to disable parsing React components. `react-docgen-typescript` invokes the TypeScript compiler, which makes it slow but generally accurate. `react-docgen` performs its own analysis, which is much faster but incomplete. @@ -74,18 +93,20 @@ Only available for React Storybook projects. Configure which library, if any, St Type: `ReactDocgenTypescriptOptions` -Only available for React Storybook projects. Options to pass to react-docgen-typescript-plugin if react-docgen-typescript is enabled. See [docs for available options](https://github.com/hipstersmoothie/react-docgen-typescript-plugin). +Configures the options to pass to `react-docgen-typescript-plugin` if `react-docgen-typescript` is enabled. See [docs for available options](https://github.com/hipstersmoothie/react-docgen-typescript-plugin). + + ## `skipCompiler` Type: `boolean` diff --git a/docs/api/parameters.md b/docs/api/parameters.md index 53174e387492..70c555c61783 100644 --- a/docs/api/parameters.md +++ b/docs/api/parameters.md @@ -2,7 +2,7 @@ title: 'Parameters' --- -Parameters are static metadata used to configure your [stories](../get-started/whats-a-story.md) and [addons](../addons/introduction.md) in Storybook. They are specified at the story, meta (component), project (global) levels. +Parameters are static metadata used to configure your [stories](../get-started/whats-a-story.md) and [addons](../addons/index.md) in Storybook. They are specified at the story, meta (component), project (global) levels. ## Story parameters @@ -36,7 +36,7 @@ Parameters specified at the story level apply to that story only. They are defin -Parameter's specified in a [CSF](../writing-stories/introduction.md#component-story-format-csf) file's meta configuration apply to all stories in that file. They are defined in the `parameters` property of the `meta` (default export): +Parameter's specified in a [CSF](../writing-stories/index.md#component-story-format-csf) file's meta configuration apply to all stories in that file. They are defined in the `parameters` property of the `meta` (default export): diff --git a/docs/builders/vite.md b/docs/builders/vite.md index 69bd64d147a3..a254e7b5f7a0 100644 --- a/docs/builders/vite.md +++ b/docs/builders/vite.md @@ -114,7 +114,7 @@ If you need to override it, you can use the `viteFinal` function and adjust it. ### ArgTypes are not generated automatically -Currently, [automatic argType inference](../api/arg-types.md#automatic-argtype-inference) is only available for React, Vue 3 and Svelte (JSDocs only). With React, the Vite builder defaults to `react-docgen-typescript` if TypeScript is listed as a dependency. If you run into any issues, you can revert to `react-docgen` by updating your Storybook configuration file as follows: +Currently, [automatic argType inference](../api/arg-types.md#automatic-argtype-inference) is only available for React, Vue 3, and Svelte (JSDocs only). With React, the Vite builder defaults to `react-docgen`, a faster alternative to `react-docgen-typescript` for parsing React components. If you run into any issues, you can revert to `react-docgen-typescript` by updating your Storybook configuration file as follows: diff --git a/docs/configure/typescript.md b/docs/configure/typescript.md index eb789ed833e7..cbbfccd4833d 100644 --- a/docs/configure/typescript.md +++ b/docs/configure/typescript.md @@ -28,20 +28,50 @@ See the Vite builder [TypeScript documentation](https://github.com/storybookjs/b ### Extending the default configuration -Out of the box, Storybook is built to work with a wide range of third-party libraries, enabling you to safely access and document metadata (e.g., props, inputs) from your components without any additional configuration. Since Storybook supports multiple frameworks, it also includes a set of third-party packages to support each framework (e.g., `ts-loader` and `ngx-template-loader` for Angular, `react-docgen-typescript-plugin` for React). If you need to customize the default configuration for a specific use case scenario, refer to the [`config.typescript` API reference](../api/main-config-typescript.md). + -The above example extends the baseline configuration to remove existing props from third-party libraries. Useful if you want to document only your components. However, if you need to include them, you can do so by adjusting your configuration as follows: +Out of the box, Storybook is built to work with a wide range of third-party libraries, enabling you to safely access and document metadata (e.g., props, inputs) from your components without any additional configuration. Since Storybook supports multiple frameworks, it also includes a set of third-party packages to support each framework (e.g., `ts-loader`, `vue-docgen-api` for Vue). If you need to customize the default configuration for a specific use case scenario, you can adjust your Storybook configuration file and provide the required options. Listed below are the available options and examples of how to use them. + +| Option | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `check` | Available for Webpack-based projects.
Enables type checking within Storybook
`typescript: { check: true },` | +| `checkOptions` | Requires the `check` option to be enabled.
Configures the [`fork-ts-checker-webpack-plugin`](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) plugin
`typescript: { checkOptions:{},},` | +| `skipCompiler` | Disables parsing Typescript files through the compiler
`typescript: { skipCompiler:false,},` | + +
+ + + +Out of the box, Storybook is built to work with a wide range of third-party libraries, enabling you to safely access and document metadata (e.g., props) for your components without any additional configuration. It relies on [`react-docgen`](https://github.com/reactjs/react-docgen), a fast and highly customizable parser to process TypeScript files to infer the component's metadata and generate types automatically for improved performance and type safety. If you need to customize the default configuration for a specific use case scenario, you can adjust your Storybook configuration file and provide the required options. Listed below are the available options and examples of how to use them. + +| Option | Description | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `check` | Available for Webpack-based projects.
Enables type checking within Storybook
`typescript: { check: true },` | +| `checkOptions` | Requires the `check` option to be enabled.
Configures the [`fork-ts-checker-webpack-plugin`](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) plugin
`typescript: { checkOptions: {},},` | +| `reactDocgen` | Configures the TypeScript parser used by Storybook.
Available options: `react-docgen` (default), `react-docgen-typescript`,` false`
`typescript: { reactDocgen: 'react-docgen'},` | +| `reactDocgenTypescriptOptions` | Requires the `reactDocgen`option to be `react-docgen-typescript`.
Configures the `react-docgen-typescript-plugin` plugin per builder
`typescript: { reactDocgen: 'react-docgen-typescript', reactDocgenTypescriptOptions: {},},` | +| `skipCompiler` | Disables parsing Typescript files through the compiler
`typescript: { skipCompiler:false,},` | + +
+ + + +Additional options are available for the `typescript` configuration option. See the [`config.typescript` API reference](../api/main-config-typescript.md) for more information. + + + ## Write stories with TypeScript Storybook provides zero-config TypeScript support, allowing you to write stories using this language without additional configuration. You can use this format for improved type safety and code completion. For example, if you're testing a `Button` component, you could do the following in your story file: @@ -92,6 +122,45 @@ Now, when you define a story or update an existing one, you'll automatically get Out of the box, Storybook supports the `satisfies` operator for almost every framework already using TypeScript version 4.9 or higher. However, due to the constraints of the Angular and Web Components framework, you might run into issues when applying this operator for additional type safety. This is primarily due to how both frameworks are currently implemented, making it almost impossible for Storybook to determine if the component property is required. If you encounter this issue, please open up a support request on [GitHub Discussions](https://github.com/storybookjs/storybook/discussions/new?category=help). + + ### The TypeScript auto-completion is not working on my editor If you're using Vue single file components and TypeScript, you can add both [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) and the [TypeScript Vue Plugin](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) for editor support, additional type safety and auto-completion. Nevertheless, if you're working with Svelte, you can add the [Svelte for VSCode extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) for similar benefits. + + + + + +### Storybook doesn't create the required types for external packages + +If your project relies on a third-party library and the expected types are not being generated, preventing you from accurately documenting your components, you can adjust the `reactDocgen` configuration option in your Storybook configuration file to use `react-docgen-typescript` instead and include the required options. For example: + + + + + + + + +### The types are not being generated for my component + +If you're working with a React project, type inference is automatically enabled for your components using the `react-docgen` library for improved build times and type safety. However, you may run into a situation where some options may not work as expected (e.g., [`Enums`](https://www.typescriptlang.org/docs/handbook/enums.html), React's [`forwardRef`](https://react.dev/reference/react/forwardRef)). This is primarily due to how the `react-docgen` package is implemented, making it difficult for Storybook to infer the component's metadata and generate types automatically. To solve this, you can update the `typescript` configuration option in your Storybook configuration file to use `react-docgen-typescript` instead. For example: + + + + + + + +If you're still encountering issues, we recommend reaching out to the community using the default communication channels (e.g., [GitHub discussions](https://github.com/storybookjs/storybook/discussions/new?category=help)). + + diff --git a/docs/essentials/controls.md b/docs/essentials/controls.md index 67b65cb7f51e..60c950c34d29 100644 --- a/docs/essentials/controls.md +++ b/docs/essentials/controls.md @@ -31,7 +31,49 @@ If you have stories in the older pre-Storybook 6 style, check the [args & contro -By default, Storybook will choose a control for each arg based on its initial value. This will work well with specific arg types (e.g., `boolean` or `string`). To enable them, add the `component` annotation to the default export of your story file, and it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component using [Compodoc](https://compodoc.app/) if you opt-in to use it, including first-class support for Angular's `inputs`, `outputs`, `properties`, `methods` and `view/content child/children`. +By default, Storybook will try to infer the required argTypes and associated controls for your stories based on the component's definition and initial value of the args using [Compodoc](https://compodoc.app/), a documentation generator for Angular applications that can extract the metadata of your components, including first-class support for Angular's `inputs`, `outputs`, `properties`, `methods`, and `view/content child/children`. If you opt-in to use it, you must take additional steps to set it up properly. + +Run the following command to install the tooling. + + + + + + + +Update your `angular.json` file to include the following configuration to include it in the Storybook's inbuilt builder configuration. + + + + + + + + +Finally, update your `.storybook/preview.ts` file to include the following configuration to import the metadata generated by Compodoc and use it to generate the controls and argTypes for your stories. + + + + + + + + +When you set the `component` annotation of the default export of your story file, it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component. @@ -45,9 +87,57 @@ By default, Storybook will choose a control for each arg based on its initial va + + +By default, Storybook will try to infer the required argTypes and associated controls for your stories based on the metadata provided by the [`@storybook/ember-cli-storybook`](https://github.com/storybookjs/ember-cli-storybook) adapter. You'll need to take some additional steps to set it up properly. + +Update your `ember-cli-build.js` configuration file to include the adapter. + + + + + + + +Restart your application to generate the metadata file (i.e., `storybook-docgen/index.json`) and update your `.storybook/preview.js` file to include it, which will be used to create the controls and argTypes for your stories. + + + + + + + + + +Enabling this feature will generate a `storybook-docgen/index.json` automatically with each build. For more information on how the metadata is generated, refer to [documentation](https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember) for the Ember framework. + + + +When you set the `component` annotation of the default export of your story file, it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component. + + + + + + + + + -By default, Storybook will choose a control for each arg based on its initial value. This will work well with specific arg types (e.g., `boolean` or `string`). To enable them, add the `component` annotation to the default export of your story file and it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component using [`react-docgen`](https://github.com/reactjs/react-docgen) or [`react-docgen-typescript`](https://github.com/styleguidist/react-docgen-typescript) for TypeScript. +By default, Storybook will choose a control for each arg based on its initial value. This will work well with specific arg types (e.g., `boolean` or `string`). To enable them, add the `component` annotation to the default export of your story file, and it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component using [`react-docgen`](https://github.com/reactjs/react-docgen), a documentation generator for React components that also includes first-class support for TypeScript. @@ -79,7 +169,38 @@ By default, Storybook will choose a control for each arg based on its initial va - + + +By default, Storybook will try to infer the required argTypes and associated controls for your stories based on the component's definition and the initial value of the args. You'll need to take some additional steps to set it up properly. You can opt to generate a [`custom-elements.json`](https://github.com/webcomponents/custom-elements-json) file with [`@custom-elements-manifest/analyzer`](https://github.com/open-wc/custom-elements-manifest) if you're using the `pre-v1.0.0` version of the elements file or [`@custom-elements-manifest/analyzer`](https://github.com/open-wc/custom-elements-manifest/tree/master/packages/analyzer) for newer versions and configure it in your Storybook UI configuration file (i.e., `.storybook/preview.js|ts`) to enable it. + + + + + + + + +When you set the `component` annotation of the default export of your story file, it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component. + + + + + + + + + + By default, Storybook will choose a control for each arg based on its initial value. This will work well with specific arg types (e.g., `boolean` or `string`). To enable them, add the `component` annotation to the default export of your story file, and it will be used to infer the controls and auto-generate the matching [`argTypes`](../api/arg-types.md) for your component provided by the framework you've chosen to use. @@ -87,17 +208,16 @@ By default, Storybook will choose a control for each arg based on its initial va + + If you're using a framework that doesn't support this feature, you'll need to define the `argTypes` for your component [manually](#fully-custom-args). @@ -163,10 +283,10 @@ This replaces the input with a radio group for a more intuitive experience. Controls can automatically be inferred from arg's name with [regex](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp), but currently only for the color picker and date picker controls. If you've used the Storybook CLI to setup your project, it should have automatically created the following defaults in `.storybook/preview.js`: -| Control | Default regex | Description | -| :-------: | :--------------------------------------: | :-------------------------------------------------------: | +| Control | Default regex | Description | +| --------- | ---------------------------------------- | --------------------------------------------------------- | | **color** | /(background|color)$/i | Will display a color picker UI for the args that match it | -| **date** | `/Date$/` | Will display a date picker UI for the args that match it | +| **date** | `/Date$/` | Will display a date picker UI for the args that match it | If you haven't used the CLI to set the configuration, or if you want to define your patterns, use the `matchers` property in the `controls` parameter: @@ -221,9 +341,9 @@ Using `argTypes`, you can change the display and behavior of each control. ### Dealing with complex values -When dealing with non-primitive values, you'll notice that you'll run into some limitations. The most obvious issue is that not every value can be represented as part of the `args` param in the URL, losing the ability to share and deeplink to such a state. Beyond that, complex values such as JSX cannot be synchronized between the manager (e.g., Controls addon) and the preview (your story). +When dealing with non-primitive values, you'll notice that you'll run into some limitations. The most obvious issue is that not every value can be represented as part of the `args` param in the URL, losing the ability to share and deep link to such a state. Beyond that, complex values such as JSX cannot be synchronized between the manager (e.g., Controls addon) and the preview (your story). -One way to deal with this is to use primitive values (e.g., strings) as arg values and add a custom `render` function to convert these values to their complex counterpart before rendering. It isn't the nicest way to do it (see below), but certainly the most flexible. +One way to deal with this is to use primitive values (e.g., strings) as arg values and add a custom `render` function to convert them to their complex counterpart before rendering. It isn't the nicest way to do it (see below), but certainly the most flexible. @@ -282,7 +402,7 @@ As shown above, you can configure individual controls with the β€œcontrol" annot | **number** | `number` | Provides a numeric input to include the range of all possible values.
`argTypes: { even: { control: { type: 'number', min:1, max:30, step: 2 } }}` | | | `range` | Provides a range slider component to include all possible values.
`argTypes: { odd: { control: { type: 'range', min: 1, max: 30, step: 3 } }}` | | **object** | `object` | Provides a JSON-based editor component to handle the object's values.
Also allows edition in raw mode.
`argTypes: { user: { control: 'object' }}` | -| **array** | `object` | Provides a JSON-based editor component to handle the values of the array.
Also allows edition in raw mode.
`argTypes: { odd: { control: 'object' }}` | +| **array** | `object` | Provides a JSON-based editor component to handle the array's values.
Also allows edition in raw mode.
`argTypes: { odd: { control: 'object' }}` | | | `file` | Provides a file input component that returns an array of URLs.
Can be further customized to accept specific file types.
`argTypes: { avatar: { control: { type: 'file', accept: '.png' } }}` | | **enum** | `radio` | Provides a set of radio buttons based on the available options.
`argTypes: { contact: { control: 'radio', options: ['email', 'phone', 'mail'] }}` | | | `inline-radio` | Provides a set of inlined radio buttons based on the available options.
`argTypes: { contact: { control: 'inline-radio', options: ['email', 'phone', 'mail'] }}` | @@ -512,117 +632,13 @@ It may also contain at most one of the following operators: If no operator is provided, that is equivalent to `{ truthy: true }`. - + ## Troubleshooting ### Controls are not automatically generated for my component -Out of the box, Storybook will try to infer the required argTypes and associated controls for your stories based on the component's definition and the initial value of the args. However, in some cases, this may not be enough, and you may need to provide additional information to Storybook. To solve this, you can opt-in to use [Compodoc](https://compodoc.app/), a documentation generator for Angular applications that can extract the metadata of your components and generate the required argTypes and controls for your stories. - -Run the following command to install the tooling. - - - - - - - -Update your `angular.json` file to include the following configuration to include it in the Storybook's inbuilt builder configuration. - - - - - - - - -Finally, update your `.storybook/preview.ts` file to include the following configuration to import the metadata generated by Compodoc and use it to generate the controls and argTypes for your stories. - - - - - - - - - - - - -## Troubleshooting - -### Controls are not automatically generated for my component - -Out of the box, Storybook will try to infer the required argTypes and associated controls for your stories based on the component's definition and the initial value of the args. However, in some cases, this may not be enough, and you may need to provide additional information to Storybook. To solve this, you can generate a [`custom-elements.json`](https://github.com/webcomponents/custom-elements-json) file with [`@custom-elements-manifest/analyzer`](https://github.com/open-wc/custom-elements-manifest) if you're using the `pre-v1.0.0` version of the elements file or [`@custom-elements-manifest/analyzer`](https://github.com/open-wc/custom-elements-manifest) for newer versions and configure it in your Storybook UI configuration file (i.e., `.storybook/preview.js|ts`) to enable it. - - - - - - - - - - - - -## Troubleshooting - -### Controls are not automatically generated for my component - -Out of the box, Storybook will try to infer the required argTypes and associated controls for your stories based on the metadata provided by the [`@storybook/ember-cli-storybook`](https://github.com/storybookjs/ember-cli-storybook) addon. However, in some cases, this may not be enough, and you may need to customize your project configuration to provide additional information to Storybook to generate the required argTypes and controls for your stories. - -Update your `ember-cli-build.js` configuration file to include the addon. - - - - - - - - -Restart your application to generate the metadata file (i.e., `storybook-docgen/index.json`) and update your `.storybook/preview.js` file to include it, which will be used to create the controls and argTypes for your stories. - - - - - - - - - - -Enabling this feature will generate a `storybook-docgen/index.json` automatically with each build. For more information on how the metadata is generated, refer to [documentation](https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember) for the Ember framework. - - +If you're working with Angular, Ember, or Web Components, automatic argTypes and controls inference will not work out of the box and requires you to provide [additional configuration](#choosing-the-control-type) to allow Storybook to retrieve the necessary metadata and generate the needed argTypes and controls for your stories. However, if you need additional customization, you can always [define them manually](#fully-custom-args). @@ -650,13 +666,7 @@ Specifies which properties to exclude from the Controls addon panel. Any propert Type: `boolean` -Show the full documentation, including description and default value, for each property in the Controls addon panel. See [usage example](#show-full-documentation-for-each-property), above. - -#### `hideNoControlsWarning` - -Type: `boolean` - -Hide the warning that appears when no controls are defined for a story. See [usage example](#hide-nocontrols-warning), above. +Show the full documentation for each property in the Controls addon panel, including the description and default value. See [usage example](#show-full-documentation-for-each-property), above. #### `include` @@ -668,7 +678,7 @@ Specifies which properties to include in the Controls addon panel. Any propertie Type: `(string | { color: string; title?: string })[]` -Specify preset color swatches for the color picker control. Color value many be any valid CSS color. See [usage example](#specify-initial-preset-color-swatches), above. +Specify preset color swatches for the color picker control. The color value may be any valid CSS color. See [usage example](#specify-initial-preset-color-swatches), above. #### `sort` diff --git a/docs/snippets/angular/compodoc-install.pnpm.js.mdx b/docs/snippets/angular/compodoc-install.pnpm.js.mdx index 116346afd228..37c0310aa158 100644 --- a/docs/snippets/angular/compodoc-install.pnpm.js.mdx +++ b/docs/snippets/angular/compodoc-install.pnpm.js.mdx @@ -1,3 +1,3 @@ ```shell -pnpm add --save-dev @compodoc/compodo +pnpm add --save-dev @compodoc/compodoc ``` diff --git a/docs/snippets/angular/list-story-with-subcomponents.ts.mdx b/docs/snippets/angular/list-story-with-subcomponents.ts.mdx new file mode 100644 index 000000000000..e79ac6301ded --- /dev/null +++ b/docs/snippets/angular/list-story-with-subcomponents.ts.mdx @@ -0,0 +1,39 @@ +```ts +// List.stories.ts +import type { Meta, StoryObj } from '@storybook/angular'; + +import { moduleMetadata } from '@storybook/angular'; + +import { CommonModule } from '@angular/common'; + +import { List } from './list.component'; +import { ListItem } from './list-item.component'; + +const meta: Meta = { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent + decorators: [ + moduleMetadata({ + declarations: [List, ListItem], + imports: [CommonModule], + }), + ], +}; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = {}; + +export const OneItem: Story = { + args: {}, + render: (args) => ({ + props: args, + template: ` + + + + `, + }), +}; +``` diff --git a/docs/snippets/common/main-config-typescript-check-options.ts.mdx b/docs/snippets/common/main-config-typescript-check-options.ts.mdx index 0581f7ee5ebf..90f5e7beeaa9 100644 --- a/docs/snippets/common/main-config-typescript-check-options.ts.mdx +++ b/docs/snippets/common/main-config-typescript-check-options.ts.mdx @@ -1,7 +1,7 @@ ```ts // .storybook/main.ts -// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite) +// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-webpack5) import type { StorybookConfig } from '@storybook/your-framework'; const config: StorybookConfig = { diff --git a/docs/snippets/common/main-config-typescript-check.ts.mdx b/docs/snippets/common/main-config-typescript-check.ts.mdx index 5f0149dfcb5e..7c687ffbef58 100644 --- a/docs/snippets/common/main-config-typescript-check.ts.mdx +++ b/docs/snippets/common/main-config-typescript-check.ts.mdx @@ -1,7 +1,7 @@ ```ts // .storybook/main.ts -// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite) +// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-webpack5) import type { StorybookConfig } from '@storybook/your-framework'; const config: StorybookConfig = { diff --git a/docs/snippets/common/storybook-main-extend-ts-config.ts.mdx b/docs/snippets/common/storybook-main-extend-ts-config.ts.mdx index a7fa7fd28444..7884f8a4724a 100644 --- a/docs/snippets/common/storybook-main-extend-ts-config.ts.mdx +++ b/docs/snippets/common/storybook-main-extend-ts-config.ts.mdx @@ -8,14 +8,9 @@ const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], typescript: { - reactDocgen: 'react-docgen-typescript', - reactDocgenTypescriptOptions: { - compilerOptions: { - allowSyntheticDefaultImports: false, - esModuleInterop: false, - }, - propFilter: () => true, - }, + check: false, + checkOptions: {}, + skipCompiler: false, }, }; diff --git a/docs/snippets/common/storybook-vite-builder-react-docgen.js.mdx b/docs/snippets/common/storybook-vite-builder-react-docgen.js.mdx index 99ef2c6a0372..84d035998c63 100644 --- a/docs/snippets/common/storybook-vite-builder-react-docgen.js.mdx +++ b/docs/snippets/common/storybook-vite-builder-react-docgen.js.mdx @@ -8,7 +8,9 @@ export default { builder: '@storybook/builder-vite', }, typescript: { - reactDocgen: 'react-docgen', // πŸ‘ˆ react-docgen configured here. + // Enables the `react-docgen-typescript` parser. + // See https://storybook.js.org/docs/api/main-config-typescript for more information about this option. + reactDocgen: 'react-docgen-typescript', }, }; ``` diff --git a/docs/snippets/react/list-story-with-subcomponents.js.mdx b/docs/snippets/react/list-story-with-subcomponents.js.mdx new file mode 100644 index 000000000000..4edaa677c3f5 --- /dev/null +++ b/docs/snippets/react/list-story-with-subcomponents.js.mdx @@ -0,0 +1,22 @@ +```jsx +// List.stories.js|jsx +import React from 'react'; + +import { List } from './List'; +import { ListItem } from './ListItem'; + +export default { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +}; + +export const Empty = {}; + +export const OneItem = { + render: (args) => ( + + + + ), +}; +``` diff --git a/docs/snippets/react/list-story-with-subcomponents.ts-4-9.mdx b/docs/snippets/react/list-story-with-subcomponents.ts-4-9.mdx new file mode 100644 index 000000000000..d527fe149fac --- /dev/null +++ b/docs/snippets/react/list-story-with-subcomponents.ts-4-9.mdx @@ -0,0 +1,26 @@ +```tsx +// List.stories.ts|tsx +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { List } from './List'; +import { ListItem } from './ListItem'; + +const meta = { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = {}; + +export const OneItem: Story = { + render: (args) => ( + + + + ), +}; +``` diff --git a/docs/snippets/react/list-story-with-subcomponents.ts.mdx b/docs/snippets/react/list-story-with-subcomponents.ts.mdx new file mode 100644 index 000000000000..71a08b1fe6bb --- /dev/null +++ b/docs/snippets/react/list-story-with-subcomponents.ts.mdx @@ -0,0 +1,26 @@ +```tsx +// List.stories.ts|tsx +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { List } from './List'; +import { ListItem } from './ListItem'; + +const meta: Meta = { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +}; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = {}; + +export const OneItem: Story = { + render: (args) => ( + + + + ), +}; +``` diff --git a/docs/snippets/common/main-config-typescript-react-docgen-typescript-options.ts.mdx b/docs/snippets/react/main-config-typescript-react-docgen-typescript-options.ts.mdx similarity index 95% rename from docs/snippets/common/main-config-typescript-react-docgen-typescript-options.ts.mdx rename to docs/snippets/react/main-config-typescript-react-docgen-typescript-options.ts.mdx index fc36649054e1..77319b608505 100644 --- a/docs/snippets/common/main-config-typescript-react-docgen-typescript-options.ts.mdx +++ b/docs/snippets/react/main-config-typescript-react-docgen-typescript-options.ts.mdx @@ -1,7 +1,7 @@ ```ts // .storybook/main.ts -// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite) +// Replace your-framework with the framework you are using (e.g., react-webpack5, react-vite) import type { StorybookConfig } from '@storybook/your-framework'; const config: StorybookConfig = { diff --git a/docs/snippets/common/main-config-typescript-react-docgen.ts.mdx b/docs/snippets/react/main-config-typescript-react-docgen.ts.mdx similarity index 83% rename from docs/snippets/common/main-config-typescript-react-docgen.ts.mdx rename to docs/snippets/react/main-config-typescript-react-docgen.ts.mdx index 6b908ac4000f..c96821c5a7ce 100644 --- a/docs/snippets/common/main-config-typescript-react-docgen.ts.mdx +++ b/docs/snippets/react/main-config-typescript-react-docgen.ts.mdx @@ -1,14 +1,14 @@ ```ts // .storybook/main.ts -// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite) +// Replace your-framework with the framework you are using (e.g., react-webpack5, react-vite) import type { StorybookConfig } from '@storybook/your-framework'; const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], typescript: { - reactDocgen: 'react-docgen-typescript', + reactDocgen: 'react-docgen', }, }; diff --git a/docs/snippets/react/storybook-main-extend-ts-config.ts.mdx b/docs/snippets/react/storybook-main-extend-ts-config.ts.mdx new file mode 100644 index 000000000000..abf80d6e62f2 --- /dev/null +++ b/docs/snippets/react/storybook-main-extend-ts-config.ts.mdx @@ -0,0 +1,20 @@ +```ts +// .storybook/main.ts + +// Replace your-framework with the framework you are using (e.g., react-webpack5, react-vite) +import type { StorybookConfig } from '@storybook/your-framework'; + +const config: StorybookConfig = { + framework: '@storybook/your-framework', + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + typescript: { + check: false, + checkOptions: {}, + reactDocgen: 'react-docgen', + reactDocgenTypescriptOptions: {}, // Available only when reactDocgen is set to 'react-docgen-typescript' + skipCompiler: true, + }, +}; + +export default config; +``` diff --git a/docs/snippets/react/storybook-main-prop-filter.ts.mdx b/docs/snippets/react/storybook-main-prop-filter.ts.mdx new file mode 100644 index 000000000000..4dd6b9d649e5 --- /dev/null +++ b/docs/snippets/react/storybook-main-prop-filter.ts.mdx @@ -0,0 +1,25 @@ +```ts +// .storybook/main.ts + +// Replace your-framework with the framework you are using (e.g., react-webpack5, react-vite) +import type { StorybookConfig } from '@storybook/your-framework'; + +const config: StorybookConfig = { + framework: '@storybook/your-framework', + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + typescript: { + reactDocgen: 'react-docgen-typescript', + reactDocgenTypescriptOptions: { + compilerOptions: { + allowSyntheticDefaultImports: false, + esModuleInterop: false, + }, + // Filter out third-party props from node_modules except @mui packages. + propFilter: (prop) => + prop.parent ? !/node_modules\/(?!@mui)/.test(prop.parent.fileName) : true, + }, + }, +}; + +export default config; +``` diff --git a/docs/snippets/react/storybook-main-react-docgen-typescript.ts.mdx b/docs/snippets/react/storybook-main-react-docgen-typescript.ts.mdx new file mode 100644 index 000000000000..8dc82534b82f --- /dev/null +++ b/docs/snippets/react/storybook-main-react-docgen-typescript.ts.mdx @@ -0,0 +1,19 @@ +```ts +// .storybook/main.ts + +// Replace your-framework with the framework you are using (e.g., react-webpack5, react-vite) +import type { StorybookConfig } from '@storybook/your-framework'; + +const config: StorybookConfig = { + framework: '@storybook/your-framework', + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + typescript: { + reactDocgen: 'react-docgen-typescript', + // Provide your own options if necessary. + // See https://storybook.js.org/docs/configure/typescript for more information. + reactDocgenTypescriptOptions: {}, + }, +}; + +export default config; +``` diff --git a/docs/snippets/vue/list-story-with-sub-components.js.mdx b/docs/snippets/vue/list-story-with-sub-components.js.mdx new file mode 100644 index 000000000000..a19b58f3b3d1 --- /dev/null +++ b/docs/snippets/vue/list-story-with-sub-components.js.mdx @@ -0,0 +1,27 @@ +```js +// List.stories.js +import List from './List.vue'; +import ListItem from './ListItem.vue'; + +export default { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +}; + +export const Empty = { + render: () => ({ + components: { List }, + template: '', + }), +}; + +export const OneItem = { + render: (args) => ({ + components: { List, ListItem }, + setup() { + return { args } + } + template: '', + }), +}; +``` diff --git a/docs/snippets/vue/list-story-with-sub-components.ts-4-9.mdx b/docs/snippets/vue/list-story-with-sub-components.ts-4-9.mdx new file mode 100644 index 000000000000..6e99a25445df --- /dev/null +++ b/docs/snippets/vue/list-story-with-sub-components.ts-4-9.mdx @@ -0,0 +1,32 @@ +```ts +// List.stories.ts +import type { Meta, StoryObj } from '@storybook/vue3'; + +import List from './List.vue'; +import ListItem from './ListItem.vue'; + +const meta = { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = { + render: () => ({ + components: { List }, + template: '', + }), +}; + +export const OneItem: Story = { + render: (args) => ({ + components: { List, ListItem }, + setup() { + return { args } + } + template: '', + }), +}; +``` diff --git a/docs/snippets/vue/list-story-with-sub-components.ts.mdx b/docs/snippets/vue/list-story-with-sub-components.ts.mdx new file mode 100644 index 000000000000..87ae30aed24a --- /dev/null +++ b/docs/snippets/vue/list-story-with-sub-components.ts.mdx @@ -0,0 +1,32 @@ +```ts +// List.stories.ts +import type { Meta, StoryObj } from '@storybook/vue3'; + +import List from './List.vue'; +import ListItem from './ListItem.vue'; + +const meta: Meta = { + component: List, + subcomponents: { ListItem }, //πŸ‘ˆ Adds the ListItem component as a subcomponent +}; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = { + render: () => ({ + components: { List }, + template: '', + }), +}; + +export const OneItem: Story = { + render: (args) => ({ + components: { List, ListItem }, + setup() { + return { args } + } + template: '', + }), +}; +``` diff --git a/docs/snippets/web-components/list-story-with-subcomponents.js.mdx b/docs/snippets/web-components/list-story-with-subcomponents.js.mdx new file mode 100644 index 000000000000..2e34a5e7104d --- /dev/null +++ b/docs/snippets/web-components/list-story-with-subcomponents.js.mdx @@ -0,0 +1,20 @@ +```js +// List.stories.js +import { html } from 'lit'; + +export default { + title: 'List', + component: 'demo-list', + subcomponents: { ListItem: 'demo-list-item' }, // πŸ‘ˆ Adds the ListItem component as a subcomponent +}; + +export const Empty = {}; + +export const OneItem = { + render: () => html` + + + + `, +}; +``` diff --git a/docs/snippets/web-components/list-story-with-subcomponents.ts.mdx b/docs/snippets/web-components/list-story-with-subcomponents.ts.mdx new file mode 100644 index 000000000000..d433d2f02fd0 --- /dev/null +++ b/docs/snippets/web-components/list-story-with-subcomponents.ts.mdx @@ -0,0 +1,25 @@ +```ts +// List.stories.ts +import type { Meta, StoryObj } from '@storybook/web-components'; + +import { html } from 'lit'; + +const meta: Meta = { + title: 'List', + component: 'demo-list', + subcomponents: { ListItem: 'demo-list-item' }, // πŸ‘ˆ Adds the ListItem component as a subcomponent +}; +export default meta; + +type Story = StoryObj; + +export const Empty: Story = {}; + +export const OneItem: Story = { + render: () => html` + + + + `, +}; +``` diff --git a/docs/writing-docs/autodocs.md b/docs/writing-docs/autodocs.md index 4c1dcaea5fdb..a89dd0a51d2f 100644 --- a/docs/writing-docs/autodocs.md +++ b/docs/writing-docs/autodocs.md @@ -183,6 +183,36 @@ Creating automated documentation with Storybook's Autodocs provides you with the ## Advanced configuration +### Documenting multiple components + +Sometimes it's helpful to document multiple components together. For example, a component library’s ButtonGroup and Button components might not make sense without one another. + +Autodocs allows you to document your "main" component, defined by the `component` property, as well as one or more `subcomponents` related to it. + + + + + + + +![Subcomponents in ArgTypes doc block](../writing-stories/doc-block-arg-types-subcomponents-for-list.png) + +The main component and its subcomponents will show up in a tabbed version of the [`ArgTypes` doc block](./doc-blocks.md#argtypes). The tab titles will correspond to the keys of the `subcomponents` object. + +If you want to organize your documentation differently for component groups, we recommend [using MDX](./mdx.md). It gives you complete control over how your components are displayed and supports any configuration. + ### Customize the Docs Container The Docs Container is the component that wraps up the documentation page. It's responsible for rendering the documentation page in Storybook's UI. You can customize it by creating your own component and updating your Storybook UI configuration file (i.e., `.storybook/preview.js`) to reference it. diff --git a/docs/writing-stories/doc-block-arg-types-subcomponents-for-list.png b/docs/writing-stories/doc-block-arg-types-subcomponents-for-list.png new file mode 100644 index 000000000000..b0d5469f0e3f Binary files /dev/null and b/docs/writing-stories/doc-block-arg-types-subcomponents-for-list.png differ diff --git a/docs/writing-stories/stories-for-multiple-components.md b/docs/writing-stories/stories-for-multiple-components.md index 4e6ac15a18ef..708ccce28787 100644 --- a/docs/writing-stories/stories-for-multiple-components.md +++ b/docs/writing-stories/stories-for-multiple-components.md @@ -2,11 +2,40 @@ title: 'Stories for multiple components' --- -It's useful to write stories that [render two or more components](../writing-stories/index.md#stories-for-two-or-more-components) at once if those components are designed to work together. For example, `ButtonGroups`, `Lists`, and `Page` components. +It's useful to write stories that [render two or more components](../writing-stories/index.md#stories-for-two-or-more-components) at once if those components are designed to work together. For example, `ButtonGroups`, `Lists`, and `Page` components. Here's an example with `List` and `ListItem` components: + + + + + + + +Note that by adding a `subcomponents` property to the default export, we get an extra panel on the [ArgTypes](../writing-docs/doc-blocks.md#argtypes) and [Controls](../essentials/controls.md#) tables, listing the props of `ListItem`: + +![Subcomponents in ArgTypes doc block](./doc-block-arg-types-subcomponents-for-list.png) + +Subcomponents are only intended for documentation purposes and have some limitations: + +1. The [argTypes](../api/arg-types.md) of subcomponents are [inferred (for the renderers that support that feature)](../api/arg-types.md#automatic-argtype-inference) and cannot be manually defined or overridden. +2. The table for each documented subcomponent does _not_ include [controls](../essentials/controls.md) to change the value of the props, because controls always apply to the main component's args. + +Let's talk about some techniques you can use to mitigate the above, which are especially useful in more complicated situations. ## Reusing subcomponent stories -The simplest approach we can take is to reuse the stories of the `ListItem` in the `List`: +The simplest change we can make to the above is to reuse the stories of the `ListItem` in the `List`: