diff --git a/MIGRATION.md b/MIGRATION.md index eecaf94950a2..2141d4dde078 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,7 @@

Migration

+- [From version 8.0 to 8.1.0](#from-version-80-to-810) + - [Subtitle block and `parameters.componentSubtitle`](#subtitle-block-and-parameterscomponentsubtitle) - [From version 7.x to 8.0.0](#from-version-7x-to-800) - [Portable stories](#portable-stories) - [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory) @@ -90,17 +92,17 @@ - [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid) - [Removed `config` preset](#removed-config-preset-1) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) - - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) - - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) - - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) + - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) + - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) + - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) + - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) + - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) + - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) + - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) - [From version 7.0.0 to 7.2.0](#from-version-700-to-720) - - [Addon API is more type-strict](#addon-api-is-more-type-strict) - - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) + - [Addon API is more type-strict](#addon-api-is-more-type-strict) + - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) @@ -126,7 +128,7 @@ - [Deploying build artifacts](#deploying-build-artifacts) - [Dropped support for file URLs](#dropped-support-for-file-urls) - [Serving with nginx](#serving-with-nginx) - - [Ignore story files from node_modules](#ignore-story-files-from-node_modules) + - [Ignore story files from node\_modules](#ignore-story-files-from-node_modules) - [7.0 Core changes](#70-core-changes) - [7.0 feature flags removed](#70-feature-flags-removed) - [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates) @@ -140,7 +142,7 @@ - [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default) - [7.0 Vite changes](#70-vite-changes) - [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically) - - [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) + - [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook) - [7.0 Webpack changes](#70-webpack-changes) - [Webpack4 support discontinued](#webpack4-support-discontinued) - [Babel mode v7 exclusively](#babel-mode-v7-exclusively) @@ -190,7 +192,7 @@ - [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration) - [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration) - [Autoplay in docs](#autoplay-in-docs) - - [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global) + - [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global) - [7.0 Deprecations and default changes](#70-deprecations-and-default-changes) - [storyStoreV7 enabled by default](#storystorev7-enabled-by-default) - [`Story` type deprecated](#story-type-deprecated) @@ -403,6 +405,14 @@ - [Packages renaming](#packages-renaming) - [Deprecated embedded addons](#deprecated-embedded-addons) +## From version 8.0 to 8.1.0 + +### Subtitle block and `parameters.componentSubtitle` + +The `Subtitle` block now accepts an `of` prop, which can be a reference to a CSF file or a default export (meta). + +`parameters.componentSubtitle` has been deprecated to be consistent with other parameters related to autodocs, instead use `parameters.docs.subtitle`. + ## From version 7.x to 8.0.0 ### Portable stories diff --git a/code/ui/blocks/src/blocks/Subtitle.stories.tsx b/code/ui/blocks/src/blocks/Subtitle.stories.tsx new file mode 100644 index 000000000000..4fe4a2ef6a19 --- /dev/null +++ b/code/ui/blocks/src/blocks/Subtitle.stories.tsx @@ -0,0 +1,104 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { Subtitle } from './Subtitle'; +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'; + +const meta: Meta = { + component: Subtitle, + parameters: { + controls: { + include: [], + hideNoControlsWarning: true, + }, + // workaround for https://github.com/storybookjs/storybook/issues/20505 + docs: { source: { type: 'code' } }, + attached: false, + docsStyles: true, + }, +}; +export default meta; + +type Story = StoryObj; + +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: { + of: ButtonStoriesWithMetaSubtitleAsDocsSubtitle, + }, + parameters: { + 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: { + of: ButtonStoriesWithMetaSubtitleAsDocsSubtitle.default, + }, + parameters: { + relativeCsfPaths: ['../examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories'], + }, +}; +export const DefaultAttached: Story = { + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: true }, +}; +export const OfUndefinedAttached: Story = { + args: { + // @ts-expect-error this is supposed to be undefined + // eslint-disable-next-line import/namespace + of: DefaultButtonStories.NotDefined, + }, + parameters: { + chromatic: { disableSnapshot: true }, + relativeCsfPaths: ['../examples/Button.stories'], + attached: true, + }, + decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ?
: s())], +}; +export const OfStringMetaAttached: Story = { + name: 'Of "meta" Attached', + args: { + of: 'meta', + }, + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: true }, +}; +export const Children: Story = { + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: true }, + render: () => This subtitle is a string passed as a children, +}; diff --git a/code/ui/blocks/src/blocks/Subtitle.tsx b/code/ui/blocks/src/blocks/Subtitle.tsx index 143543cb27fb..9b7556e9c7c6 100644 --- a/code/ui/blocks/src/blocks/Subtitle.tsx +++ b/code/ui/blocks/src/blocks/Subtitle.tsx @@ -1,15 +1,40 @@ import type { FunctionComponent, ReactNode } from 'react'; -import React, { useContext } from 'react'; +import React from 'react'; +import { deprecate } from '@storybook/client-logger'; + import { Subtitle as PureSubtitle } from '../components'; -import { DocsContext } from './DocsContext'; +import type { Of } from './useOf'; +import { useOf } from './useOf'; interface SubtitleProps { children?: ReactNode; + /** + * Specify where to get the subtitle from. + * If not specified, the subtitle will be extracted from the meta of the attached CSF file. + */ + of?: Of; } -export const Subtitle: FunctionComponent = ({ children }) => { - const docsContext = useContext(DocsContext); - const content = children || docsContext.storyById().parameters?.componentSubtitle; +const DEPRECATION_MIGRATION_LINK = + 'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#subtitle-block-and-parameterscomponentsubtitle'; + +export const Subtitle: FunctionComponent = (props) => { + const { of, children } = props; + + if ('of' in props && of === undefined) { + throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); + } + + const { preparedMeta } = useOf(of || 'meta', ['meta']); + 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; return content ? ( {content} diff --git a/code/ui/blocks/src/examples/Button.stories.tsx b/code/ui/blocks/src/examples/Button.stories.tsx index e5fc5b2e3457..a49f88f5d8f8 100644 --- a/code/ui/blocks/src/examples/Button.stories.tsx +++ b/code/ui/blocks/src/examples/Button.stories.tsx @@ -17,6 +17,9 @@ const meta = { notes: 'These are notes for the Button stories', info: 'This is info for the Button stories', jsx: { useBooleanShorthandSyntax: false }, + docs: { + subtitle: 'This is the subtitle for the Button stories', + }, }, } satisfies Meta; diff --git a/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsBoth.stories.tsx b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsBoth.stories.tsx new file mode 100644 index 000000000000..5b4235c07c57 --- /dev/null +++ b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsBoth.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta = { + title: 'examples/Button with Meta Subtitle in Both', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + parameters: { + // Stop *this* story from being stacked in Chromatic + theme: 'default', + // this is to test the deprecated features of the Subtitle block + componentSubtitle: 'This subtitle is set in parameters.componentSubtitle', + docs: { + subtitle: 'This subtitle is set in parameters.docs.subtitle', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const WithMetaSubtitleAsBoth: Story = { + args: { + primary: true, + label: 'Button', + }, +}; diff --git a/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories.tsx b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories.tsx new file mode 100644 index 000000000000..57a106340421 --- /dev/null +++ b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsComponentSubtitle.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta = { + title: 'examples/Button with Meta Subtitle in componentSubtitle', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + parameters: { + // Stop *this* story from being stacked in Chromatic + theme: 'default', + // this is to test the deprecated features of the Subtitle block + componentSubtitle: 'This subtitle is set in parameters.componentSubtitle', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const WithMetaSubtitleInComponentSubtitle: Story = { + args: { + primary: true, + label: 'Button', + }, +}; diff --git a/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories.tsx b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories.tsx new file mode 100644 index 000000000000..3df3110baf6c --- /dev/null +++ b/code/ui/blocks/src/examples/ButtonWithMetaSubtitleAsDocsSubtitle.stories.tsx @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta = { + title: 'examples/Button with Meta Subtitle in docs.subtitle', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + }, + parameters: { + // Stop *this* story from being stacked in Chromatic + theme: 'default', + docs: { + subtitle: 'This subtitle is set in parameters.docs.subtitle', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const WithMetaSubtitleInDocsSubtitle: Story = { + args: { + primary: true, + label: 'Button', + }, +}; diff --git a/code/ui/blocks/src/examples/EmptyExample.tsx b/code/ui/blocks/src/examples/EmptyExample.tsx index d9ad80b7a120..a1b48922f303 100644 --- a/code/ui/blocks/src/examples/EmptyExample.tsx +++ b/code/ui/blocks/src/examples/EmptyExample.tsx @@ -2,7 +2,7 @@ import React from 'react'; export const EmptyExample = ({}) => (
- This component is not intended to render anything, it simply serves a something to hang + This component is not intended to render anything, it simply serves as something to hang parameters off
); diff --git a/docs/api/doc-block-subtitle.md b/docs/api/doc-block-subtitle.md index 1104184c9992..3478a0c2fe8a 100644 --- a/docs/api/doc-block-subtitle.md +++ b/docs/api/doc-block-subtitle.md @@ -30,6 +30,12 @@ import { Subtitle } from '@storybook/blocks'; Type: `JSX.Element | string` -Default: `parameters.componentSubtitle` +Default: `parameters.docs.subtitle` Provides the content. + +### `of` + +Type: CSF file exports + +Specifies which meta's subtitle is displayed.