diff --git a/MIGRATION.md b/MIGRATION.md index 08a2907dac6e..0a13cc59da81 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -59,6 +59,8 @@ - [Description Doc block properties](#description-doc-block-properties) - [Story Doc block properties](#story-doc-block-properties) - [Manager API expandAll and collapseAll methods](#manager-api-expandall-and-collapseall-methods) + - [Source Doc block properties](#source-doc-block-properties) + - [Canvas Doc block properties](#canvas-doc-block-properties) - [`Primary` Doc block properties](#primary-doc-block-properties) - [`createChannel` from `@storybook/postmessage` and `@storybook/channel-websocket`](#createchannel-from-storybookpostmessage-and--storybookchannel-websocket) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) @@ -950,6 +952,22 @@ api.collapseAll() // becomes api.emit(STORIES_COLLAPSE_ALL) api.expandAll() // becomes api.emit(STORIES_EXPAND_ALL) ``` +#### Source Doc block properties + +`id` and `ids` are now removed in favor of the `of` property. [More info](#doc-blocks). + +#### Canvas Doc block properties + +The following properties were removed from the Canvas Doc block: + +- children +- isColumn +- columns +- withSource +- mdxSource + +[More info](#doc-blocks). + #### `Primary` Doc block properties The `name` prop is now removed in favor of the `of` property. [More info](#doc-blocks). diff --git a/code/ui/blocks/src/blocks/Canvas.tsx b/code/ui/blocks/src/blocks/Canvas.tsx index 39604e05547f..556817296b42 100644 --- a/code/ui/blocks/src/blocks/Canvas.tsx +++ b/code/ui/blocks/src/blocks/Canvas.tsx @@ -1,47 +1,17 @@ /* eslint-disable react/destructuring-assignment */ -import React, { Children, useContext } from 'react'; -import type { FC, ReactElement, ReactNode } from 'react'; -import type { ModuleExport, ModuleExports, PreparedStory, Renderer } from '@storybook/types'; -import { deprecate } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; +import React, { useContext } from 'react'; +import type { FC } from 'react'; +import type { ModuleExport, ModuleExports } from '@storybook/types'; import type { Layout, PreviewProps as PurePreviewProps } from '../components'; -import { Preview as PurePreview, PreviewSkeleton } from '../components'; -import type { DocsContextProps } from './DocsContext'; +import { Preview as PurePreview } from '../components'; import { DocsContext } from './DocsContext'; -import type { SourceContextProps } from './SourceContainer'; import { SourceContext } from './SourceContainer'; import type { SourceProps } from './Source'; -import { useSourceProps, SourceState as DeprecatedSourceState, SourceState } from './Source'; -import { useStories } from './useStory'; +import { useSourceProps } from './Source'; import type { StoryProps } from './Story'; -import { getStoryId, Story } from './Story'; +import { Story } from './Story'; import { useOf } from './useOf'; -export { DeprecatedSourceState as SourceState }; - -type DeprecatedCanvasProps = { - /** - * @deprecated multiple stories are not supported - */ - isColumn?: boolean; - /** - * @deprecated multiple stories are not supported - */ - columns?: number; - /** - * @deprecated use `sourceState` instead - */ - withSource?: DeprecatedSourceState; - /** - * @deprecated use `source.code` instead - */ - mdxSource?: string; - /** - * @deprecated reference stories with the `of` prop instead - */ - children?: ReactNode; -}; - type CanvasProps = Pick & { /** * Pass the export defining a story to render that story @@ -92,129 +62,16 @@ type CanvasProps = Pick; }; -const useDeprecatedPreviewProps = ( - { - withSource, - mdxSource, - children, - layout: layoutProp, - ...props - }: DeprecatedCanvasProps & { of?: ModuleExport; layout?: Layout }, - docsContext: DocsContextProps, - sourceContext: SourceContextProps -) => { - /* - get all story IDs by traversing through the children, - filter out any non-story children (e.g. text nodes) - and then get the id from each story depending on available props - */ - const storyIds = (Children.toArray(children) as ReactElement[]) - .filter((c) => c.props && (c.props.id || c.props.name || c.props.of)) - .map((c) => getStoryId(c.props, docsContext)); - - const stories = useStories(storyIds, docsContext); - const isLoading = stories.some((s) => !s); - const sourceProps = useSourceProps( - { - ...(mdxSource ? { code: decodeURI(mdxSource) } : { ids: storyIds }), - ...(props.of && { of: props.of }), - }, - docsContext, - sourceContext - ); - if (withSource === SourceState.NONE) { - return { isLoading, previewProps: props }; - } - - // if the user has specified a layout prop, use that... - let layout = layoutProp; - // ...otherwise, try to infer it from the story parameters - stories.forEach((story) => { - if (layout || !story) { - return; - } - layout = story?.parameters.layout ?? story.parameters.docs?.canvas?.layout; - }); - - return { - isLoading, - previewProps: { - ...props, // pass through columns etc. - layout: layout ?? 'padded', - withSource: sourceProps, - isExpanded: (withSource || sourceProps.state) === SourceState.OPEN, - }, - }; -}; - -export const Canvas: FC = (props) => { +export const Canvas: FC = (props) => { const docsContext = useContext(DocsContext); const sourceContext = useContext(SourceContext); - const { children, of, source } = props; + const { of, source } = props; if ('of' in props && of === undefined) { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); } - const { isLoading, previewProps } = useDeprecatedPreviewProps(props, docsContext, sourceContext); - - let story: PreparedStory; - let sourceProps; - /** - * useOf and useSourceProps will throw if they can't find the story, in the scenario where - * the doc is unattached (no primary story) and 'of' is undefined. - * That scenario is valid in the deprecated API, where children is used as story refs rather than 'of'. - * So if children is passed we allow the error to be swallowed and we'll use them instead. - * We use two separate try blocks and throw the error afterwards to not break the rules of hooks. - */ - let hookError; - try { - ({ story } = useOf(of || 'story', ['story'])); - } catch (error) { - if (!children) { - hookError = error; - } - } - try { - sourceProps = useSourceProps({ ...source, ...(of && { of }) }, docsContext, sourceContext); - } catch (error) { - if (!children) { - hookError = error; - } - } - if (hookError) { - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw hookError; - } - - if (props.withSource) { - deprecate(dedent`Setting source state with \`withSource\` is deprecated, please use \`sourceState\` with 'hidden', 'shown' or 'none' instead. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (props.mdxSource) { - deprecate(dedent`Setting source code with \`mdxSource\` is deprecated, please use source={{code: '...'}} instead. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (props.isColumn !== undefined || props.columns !== undefined) { - deprecate(dedent`\`isColumn\` and \`columns\` props are deprecated as the Canvas block now only supports showing a single story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (children) { - deprecate(dedent`Passing children to Canvas is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - return isLoading ? ( - - ) : ( - {children} - ); - } + const { story } = useOf(of || 'story', ['story']); + const sourceProps = useSourceProps({ ...source, ...(of && { of }) }, docsContext, sourceContext); const layout = props.layout ?? story.parameters.layout ?? story.parameters.docs?.canvas?.layout ?? 'padded'; diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index 033dfd718571..59906aa77c43 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -9,8 +9,6 @@ import type { } from '@storybook/types'; import { SourceType } from '@storybook/docs-tools'; -import { deprecate } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; import type { SourceCodeProps } from '../components/Source'; import { Source as PureSource, SourceError } from '../components/Source'; import type { DocsContextProps } from './DocsContext'; @@ -18,14 +16,6 @@ import { DocsContext } from './DocsContext'; import type { SourceContextProps, SourceItem } from './SourceContainer'; import { UNKNOWN_ARGS_HASH, argsHash, SourceContext } from './SourceContainer'; -import { useStories } from './useStory'; - -export enum SourceState { - OPEN = 'open', - CLOSED = 'closed', - NONE = 'none', -} - type SourceParameters = SourceCodeProps & { /** * Where to read the source code from, see `SourceType` @@ -54,25 +44,12 @@ export type SourceProps = SourceParameters & { */ of?: ModuleExport; - /** @deprecated use of={storyExport} instead */ - id?: string; - - /** @deprecated use of={storyExport} instead */ - ids?: string[]; - /** * Internal prop to control if a story re-renders on args updates */ __forceInitialArgs?: boolean; }; -const getSourceState = (stories: PreparedStory[]) => { - const states = stories.map((story) => story.parameters.docs?.source?.state).filter(Boolean); - if (states.length === 0) return SourceState.CLOSED; - // FIXME: handling multiple stories is a pain - return states[0]; -}; - const getStorySource = ( storyId: StoryId, args: Args, @@ -126,19 +103,14 @@ const getSnippet = ({ }; // state is used by the Canvas block, which also calls useSourceProps -type SourceStateProps = { state: SourceState }; type PureSourceProps = ComponentProps; export const useSourceProps = ( props: SourceProps, docsContext: DocsContextProps, sourceContext: SourceContextProps -): PureSourceProps & SourceStateProps => { - const storyIds = props.ids || (props.id ? [props.id] : []); - const storiesFromIds = useStories(storyIds, docsContext); - - // The check didn't actually change the type. - let stories: PreparedStory[] = storiesFromIds as PreparedStory[]; +): PureSourceProps => { + let story: PreparedStory; const { of } = props; if ('of' in props && of === undefined) { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); @@ -146,65 +118,54 @@ export const useSourceProps = ( if (of) { const resolved = docsContext.resolveOf(of, ['story']); - stories = [resolved.story]; - } else if (stories.length === 0) { + story = resolved.story; + } else { try { // Always fall back to the primary story for source parameters, even if code is set. - stories = [docsContext.storyById()]; + story = docsContext.storyById(); } catch (err) { // You are allowed to use and unattached. } } - if (!storiesFromIds.every(Boolean)) { - return { error: SourceError.SOURCE_UNAVAILABLE, state: SourceState.NONE }; - } - const sourceParameters = (stories[0]?.parameters?.docs?.source || {}) as SourceParameters; - let { code } = props; // We will fall back to `sourceParameters.code`, but per story below + const sourceParameters = (story?.parameters?.docs?.source || {}) as SourceParameters; + const { code } = props; // We will fall back to `sourceParameters.code`, but per story below let format = props.format ?? sourceParameters.format; const language = props.language ?? sourceParameters.language ?? 'jsx'; const dark = props.dark ?? sourceParameters.dark ?? false; - if (!code) { - code = stories - .map((story, index) => { - // In theory you can use a storyId from a different CSF file that hasn't loaded yet. - if (!story) return ''; - - const storyContext = docsContext.getStoryContext(story); - - // eslint-disable-next-line no-underscore-dangle - const argsForSource = props.__forceInitialArgs - ? storyContext.initialArgs - : storyContext.unmappedArgs; - - const source = getStorySource(story.id, argsForSource, sourceContext); - if (index === 0) { - // Take the format from the first story - format = source.format ?? story.parameters.docs?.source?.format ?? false; - } - return getSnippet({ - snippet: source.code, - storyContext: { ...storyContext, args: argsForSource }, - typeFromProps: props.type, - transformFromProps: props.transform, - }); - }) - .join('\n\n'); + if (!code && !story) { + return { error: SourceError.SOURCE_UNAVAILABLE }; } - - const state = getSourceState(stories as PreparedStory[]); - - return code - ? { - code, - format, - language, - dark, - // state is used by the Canvas block when it calls this function - state, - } - : { error: SourceError.SOURCE_UNAVAILABLE, state }; + if (code) { + return { + code, + format, + language, + dark, + }; + } + const storyContext = docsContext.getStoryContext(story); + + // eslint-disable-next-line no-underscore-dangle + const argsForSource = props.__forceInitialArgs + ? storyContext.initialArgs + : storyContext.unmappedArgs; + + const source = getStorySource(story.id, argsForSource, sourceContext); + format = source.format ?? story.parameters.docs?.source?.format ?? false; + + return { + code: getSnippet({ + snippet: source.code, + storyContext: { ...storyContext, args: argsForSource }, + typeFromProps: props.type, + transformFromProps: props.transform, + }), + format, + language, + dark, + }; }; /** @@ -213,20 +174,8 @@ export const useSourceProps = ( * the source for the current story if nothing is provided. */ export const Source: FC = (props) => { - if (props.id) { - deprecate(dedent`The \`id\` prop on Source is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#source-block - `); - } - if (props.ids) { - deprecate(dedent`The \`ids\` prop on Source is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#source-block - `); - } const sourceContext = useContext(SourceContext); const docsContext = useContext(DocsContext); - const { state, ...sourceProps } = useSourceProps(props, docsContext, sourceContext); + const sourceProps = useSourceProps(props, docsContext, sourceContext); return ; }; diff --git a/code/ui/blocks/src/blocks/index.ts b/code/ui/blocks/src/blocks/index.ts index 7f3d11f56f64..464ddc8c0b76 100644 --- a/code/ui/blocks/src/blocks/index.ts +++ b/code/ui/blocks/src/blocks/index.ts @@ -3,7 +3,6 @@ export { ColorPalette, ColorItem, IconGallery, IconItem, Typeset } from '../comp export * from './Anchor'; export * from './ArgTypes'; export * from './ArgsTable'; -// eslint-disable-next-line import/export export * from './Canvas'; export * from './Controls'; export * from './Description'; @@ -18,7 +17,6 @@ export * from './Heading'; export * from './Markdown'; export * from './Meta'; export * from './Primary'; -// eslint-disable-next-line import/export export * from './Source'; export * from './SourceContainer'; export * from './Stories'; diff --git a/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx b/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx deleted file mode 100644 index da9f9685e7d9..000000000000 --- a/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx +++ /dev/null @@ -1,211 +0,0 @@ -/// ; -/// ; -import React from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { userEvent, within } from '@storybook/testing-library'; -import { expect } from '@storybook/jest'; -import { Canvas, SourceState } from '../Canvas'; -import { Story as StoryComponent } from '../Story'; -import * as ButtonStories from '../../examples/Button.stories'; -import * as CanvasParameterStories from '../../examples/CanvasParameters.stories'; - -const meta: Meta = { - component: Canvas, - parameters: { - theme: 'light', - relativeCsfPaths: ['../examples/Button.stories', '../examples/CanvasParameters.stories'], - docsStyles: true, - }, - render: (args) => { - return ( - - - - ); - }, -}; -export default meta; - -type Story = StoryObj; - -const expectAmountOfStoriesInSource = - (amount: number): Story['play'] => - async ({ canvasElement }) => { - const canvas = within(canvasElement); - - // Arrange - find the "Show code" button - const showCodeButton = await canvas.findByText('Show code'); - await expect(showCodeButton).toBeInTheDocument(); - - // Act - click button to show code - await userEvent.click(showCodeButton); - - // Assert - check that the correct amount of stories' source is shown - const buttonNodes = await canvas.findAllByText(`label`); - await expect(buttonNodes).toHaveLength(amount); - }; - -export const BasicStoryChild: Story = {}; - -export const BasicStoryChildUnattached: Story = { - parameters: { attached: false }, -}; - -export const NoStoryChildrenUnattached: Story = { - parameters: { attached: false }, - render: (args) => { - return ( - -

This is a plain paragraph, no stories

-
- ); - }, -}; -export const NoStoryChildrenUnattachedWithMDXSource: Story = { - ...NoStoryChildrenUnattached, - args: { - mdxSource: `const customStaticSource = true;`, - }, -}; - -export const WithSourceOpen: Story = { - args: { - withSource: SourceState.OPEN, - }, -}; -export const WithSourceClosed: Story = { - args: { - withSource: SourceState.CLOSED, - }, -}; - -export const WithMdxSource: Story = { - name: 'With MDX Source', - args: { - withSource: SourceState.OPEN, - mdxSource: `