Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [From version 10.x to 11.0.0](#from-version-10x-to-1100)
- [`storybook dev` no longer opens a browser by default](#storybook-dev-no-longer-opens-a-browser-by-default)
- [`parameters.componentSubtitle` removed](#parameterscomponentsubtitle-removed)
- [Raised browser support floors](#raised-browser-support-floors)
- [Docs Code panel enabled by default](#docs-code-panel-enabled-by-default)
- [Node.js 22.12 or higher](#nodejs-2212-or-higher)
Expand Down Expand Up @@ -574,6 +575,30 @@ To keep opening Storybook automatically, add `--open` to your command or package
}
```

### `parameters.componentSubtitle` removed

The deprecated `parameters.componentSubtitle` fallback was removed.
Use `parameters.docs.subtitle` instead.

The `component-subtitle` automigration moves it in your preview and story files when you upgrade, or when you run `npx storybook automigrate component-subtitle`:

```diff
export default {
parameters: {
- componentSubtitle: 'Button variants',
+ docs: { subtitle: 'Button variants' },
},
};
```

When the same object already sets `docs.subtitle`, the automigration keeps it and removes `componentSubtitle`, because `docs.subtitle` already took precedence.
A `componentSubtitle` set on a single story never affected the Subtitle block, so the automigration leaves it in place for you to delete.
Files it cannot edit safely, such as parameters built from a spread, are listed in `automigrations-summary.md` for you to change by hand.

Before, any `docs.subtitle` took precedence over any `componentSubtitle`, even one set in the preview over one set in a component's meta.
After the migration, the more specific value wins, like every other parameter.
If your preview sets `docs.subtitle`, check the subtitles on your Docs pages.

### Docs Code panel enabled by default

When `@storybook/addon-docs` is installed, the Code panel is now available for stories without setting `parameters.docs.codePanel` to `true`.
Expand Down
36 changes: 0 additions & 36 deletions code/addons/docs/src/blocks/blocks/Subtitle.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import React from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';

import * as DefaultButtonStories from '../examples/Button.stories';
import * as ButtonStoriesWithMetaSubtitleAsBoth from '../examples/ButtonWithMetaSubtitleAsBoth.stories';
import * as ButtonStoriesWithMetaSubtitleAsComponentSubtitle from '../examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories';
import * as ButtonStoriesWithMetaSubtitleAsDocsSubtitle from '../examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories';
import { Subtitle } from './Subtitle';

Expand All @@ -26,23 +24,6 @@ export default meta;

type Story = StoryObj<typeof meta>;

export const OfCSFFileAsBoth: Story = {
args: {
of: ButtonStoriesWithMetaSubtitleAsBoth,
},
parameters: {
relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsBoth.stories'],
},
};
export const OfCSFFileAsComponentSubtitle: Story = {
name: 'Of CSF File As parameters.componentSubtitle',
args: {
of: ButtonStoriesWithMetaSubtitleAsComponentSubtitle,
},
parameters: {
relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories'],
},
};
export const OfCSFFileAsDocsSubtitle: Story = {
name: 'Of CSF File As parameters.docs.subtitle',
args: {
Expand All @@ -52,23 +33,6 @@ export const OfCSFFileAsDocsSubtitle: Story = {
relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories'],
},
};
export const OfMetaAsBoth: Story = {
args: {
of: ButtonStoriesWithMetaSubtitleAsBoth.default,
},
parameters: {
relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsBoth.stories'],
},
};
export const OfMetaAsComponentSubtitle: Story = {
name: 'Of Meta As parameters.componentSubtitle',
args: {
of: ButtonStoriesWithMetaSubtitleAsComponentSubtitle.default,
},
parameters: {
relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories'],
},
};
export const OfMetaAsDocsSubtitle: Story = {
name: 'Of Meta As parameters.docs.subtitle',
args: {
Expand Down
14 changes: 1 addition & 13 deletions code/addons/docs/src/blocks/blocks/Subtitle.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { FunctionComponent, ReactNode } from 'react';
import React from 'react';

import { deprecate } from 'storybook/internal/client-logger';
import { InvalidBlockOfPropError } from 'storybook/internal/preview-errors';

import { Subtitle as PureSubtitle } from '../components';
Expand All @@ -18,9 +17,6 @@ interface SubtitleProps {
of?: Of;
}

const DEPRECATION_MIGRATION_LINK =
'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#subtitle-block-and-parameterscomponentsubtitle';

const SubtitleImpl: FunctionComponent<SubtitleProps> = (props) => {
const { of, children } = props;

Expand All @@ -38,15 +34,7 @@ const SubtitleImpl: FunctionComponent<SubtitleProps> = (props) => {
}
}

const { componentSubtitle, docs } = preparedMeta?.parameters || {};

if (componentSubtitle) {
deprecate(
`Using 'parameters.componentSubtitle' property to subtitle stories is deprecated. See ${DEPRECATION_MIGRATION_LINK}`
);
}

const content = children || docs?.subtitle || componentSubtitle;
const content = children || preparedMeta?.parameters?.docs?.subtitle;

return content ? (
<PureSubtitle className="sbdocs-subtitle sb-unstyled">{content}</PureSubtitle>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { formatExistingFile, JsPackageManager } from 'storybook/internal/common';

import { fs, vol } from 'memfs';

import { checkFix, runFix } from '../helpers/fix-test-utils.ts';
import { componentSubtitle } from './component-subtitle.ts';

vi.mock('node:fs/promises', { spy: true });
vi.mock('storybook/internal/common', { spy: true });

const previewConfigPath = resolve('.storybook/preview.ts');
const storyPath = resolve('src/Button.stories.ts');
const options = {
packageManager: vi.mocked(JsPackageManager.prototype),
mainConfig: { stories: [] },
mainConfigPath: resolve('.storybook/main.ts'),
configDir: resolve('.storybook'),
storybookVersion: '11.0.0',
previewConfigPath,
storiesPaths: [storyPath],
};

const migrate = async (files: { preview?: string; story?: string }) => {
vol.fromJSON({ [previewConfigPath]: files.preview ?? '', [storyPath]: files.story ?? '' });
const failures = await runFix(componentSubtitle, { ...options, result: {} });
return {
failures,
preview: fs.readFileSync(previewConfigPath, 'utf8'),
story: fs.readFileSync(storyPath, 'utf8'),
};
};

describe('component-subtitle', () => {
beforeEach(() => {
vol.reset();
vi.mocked(readFile).mockImplementation(fs.promises.readFile as typeof readFile);
vi.mocked(writeFile).mockImplementation(fs.promises.writeFile as typeof writeFile);
vi.mocked(formatExistingFile).mockImplementation(async (_path, source) => source);
});

afterEach(() => {
vi.mocked(readFile).mockRestore();
vi.mocked(writeFile).mockRestore();
});

it('applies only when a file uses componentSubtitle', async () => {
vol.fromJSON({ [previewConfigPath]: 'export default {};', [storyPath]: 'export default {};' });
expect(await checkFix(componentSubtitle, options)).toBeNull();

fs.writeFileSync(storyPath, "export default { parameters: { componentSubtitle: 'A' } };");
expect(await checkFix(componentSubtitle, options)).toEqual({});
});

it('leaves MDX docs that mention componentSubtitle to the author', async () => {
const docsPath = resolve('src/Intro.mdx');
vol.fromJSON({
[previewConfigPath]: 'export default {};',
[docsPath]: "<Meta parameters={{ componentSubtitle: 'A' }} />",
});

expect(await checkFix(componentSubtitle, { ...options, storiesPaths: [docsPath] })).toBeNull();
});

it('moves componentSubtitle to docs.subtitle in the preview and meta, where the Subtitle block read it', async () => {
const { failures, preview, story } = await migrate({
preview: "export default { parameters: { componentSubtitle: 'Preview' } };",
story: [
"export default { parameters: { componentSubtitle: 'Meta', docs: { source: { type: 'code' } } } };",
"export const Primary = { parameters: { componentSubtitle: 'Story' } };",
].join('\n'),
});

expect(failures).toEqual([]);
expect(preview).toMatchInlineSnapshot(`
"export default { parameters: { docs: {
subtitle: 'Preview'
} } };"
`);
expect(story).toMatchInlineSnapshot(`
"export default { parameters: {
docs: {
source: { type: 'code' },
subtitle: 'Meta'
}
} };
export const Primary = { parameters: { componentSubtitle: 'Story' } };"
`);
});

it('keeps an existing docs.subtitle, which the Subtitle block preferred', async () => {
const { story } = await migrate({
story:
"export default { parameters: { componentSubtitle: 'Legacy', docs: { subtitle: 'Current' } } };",
});

expect(story).toMatchInlineSnapshot(`
"export default { parameters: {
docs: { subtitle: 'Current' }
} };"
`);
});

it('skips a file it cannot migrate safely and reports it', async () => {
const story =
"import { shared } from './shared';\nexport default { parameters: { ...shared, componentSubtitle: 'A' } };";

const result = await migrate({ story });

expect(result.failures).toEqual([
{ file: storyPath, kind: 'story', message: expect.any(String) },
]);
expect(result.story).toBe(story);
});
});
38 changes: 38 additions & 0 deletions code/lib/cli-storybook/src/automigrate/fixes/component-subtitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { CsfObject } from 'storybook/internal/csf-tools';

import picocolors from 'picocolors';

import type { Fix } from '../types.ts';

const legacyPath = ['parameters', 'componentSubtitle'];
const subtitlePath = ['parameters', 'docs', 'subtitle'];

// The Subtitle block read the preview and meta parameters only, and preferred `docs.subtitle`,
// so an existing one keeps winning.
const migrate = (object: CsfObject) => {
if (!object.get(legacyPath)) {
return;
}
if (object.get(subtitlePath)) {
object.remove(legacyPath);
} else {
object.move(legacyPath, subtitlePath);
}
};

export const componentSubtitle: Fix = {
id: 'component-subtitle',
link: 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#parameterscomponentsubtitle-removed',

prompt() {
return `Move deprecated ${picocolors.cyan('parameters.componentSubtitle')} values to ${picocolors.cyan('parameters.docs.subtitle')}`;
},

transform: () => [
{
filter: { kind: ['preview', 'story'], code: 'componentSubtitle' },
editConfig: migrate,
editCsf: (csf) => csf.objects({ stories: false }).forEach(migrate),
},
],
};
2 changes: 2 additions & 0 deletions code/lib/cli-storybook/src/automigrate/fixes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { CommandFix, Fix } from '../types.ts';
import { angularToAngularVite } from './angular-to-angular-vite.ts';
import { angularViteRemoveCompodoc } from './angular-vite-remove-compodoc.ts';
import { addonMcp } from './addon-mcp.ts';
import { componentSubtitle } from './component-subtitle.ts';
import { eslintPlugin } from './eslint-plugin.ts';
import {
enableExperimentalDocgenServer,
Expand All @@ -23,6 +24,7 @@ export const allFixes: Fix[] = [
eslintPlugin,
upgradeStorybookRelatedDependencies,
vitestSetupFile,
componentSubtitle,
rnOndeviceAddonsToDeviceAddons,
nextjsToNextjsVite,
angularToAngularVite,
Expand Down
17 changes: 17 additions & 0 deletions docs/releases/migration-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The rest of this guide will help you upgrade successfully, either automatically
- Changed defaults
- [`storybook dev` no longer opens a browser by default](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storybook-dev-no-longer-opens-a-browser-by-default)
- [Docs Code panel enabled by default](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#docs-code-panel-enabled-by-default)
- Removed APIs
- [`parameters.componentSubtitle` removed](#parameterscomponentsubtitle-removed)
- Configuration changes
- [Move top-level `setConfig` layout and UI options](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#top-level-setconfig-layout-and-ui-options-removed)
- [Vite static asset copying and conflict precedence](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#vite-publicdir-is-handled-by-storybooks-staticdirs)
Expand Down Expand Up @@ -98,6 +100,21 @@ If you prefer to debug yourself, here are a few useful things you can do to help
1. Try removing all addons that are not in the `@storybook` npm namespace (make sure you don't remove the `storybook` package). Community addons that work well with 10.x might not yet be compatible with 11.x, and this is the fastest way to isolate that possibility. If you find an addon that needs to be upgraded to work with Storybook 11, please post an issue on the addon’s repository, or better yet, a pull request to upgrade it!
2. Another debugging technique is to bisect to older prerelease versions of Storybook to figure out which release broke your Storybook. For example, assuming that the current prerelease of Storybook is `11.0.0-beta.56`, you could set the version to `11.0.0-alpha.0` in your `package.json` and reinstall to verify that it still works (`alpha.0` should be nearly identical to `10.6.x`). If it works, you could then try `11.0.0-beta.0`, then `11.0.0-beta.28` and so forth. Once you’ve isolated the bad release, read through its [CHANGELOG](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.prerelease.md) entry and perhaps there’s a change that jumps out as the culprit. If you find the problem, please submit an issue or pull request to the Storybook monorepo and we’ll do our best to take care of it quickly.

## `parameters.componentSubtitle` removed

Storybook 11 removes the deprecated `parameters.componentSubtitle` fallback. Move subtitles to `parameters.docs.subtitle` in your preview configuration and story files:

```diff
export default {
parameters: {
- componentSubtitle: 'Button variants',
+ docs: { subtitle: 'Button variants' },
},
};
```

The upgrade command offers the `component-subtitle` automigration to do this for you. See the [full migration notes](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#parameterscomponentsubtitle-removed) for what it cannot migrate and how subtitle precedence changes.

## Yarn PnP support removed

Storybook 11 removes support for Yarn [Plug'n'Play](https://yarnpkg.com/features/pnp) (PnP), which was deprecated in Storybook 10:
Expand Down
Loading