From 93c04165264e985e0bbfb714e632280da2089b43 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 11:48:19 +0100 Subject: [PATCH 01/10] add subcomponents support to ArgTypes block --- .../ui/blocks/src/blocks/ArgTypes.stories.tsx | 40 ++++++++++++++++++- code/ui/blocks/src/blocks/ArgTypes.tsx | 34 ++++++++++++---- .../src/examples/ArgTypesParameters.tsx | 12 ++++++ ...pesWithSubcomponentsParameters.stories.tsx | 26 ++++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 code/ui/blocks/src/examples/ArgTypesWithSubcomponentsParameters.stories.tsx diff --git a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx index a177868ba92a..a77f9e3ad26f 100644 --- a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx +++ b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx @@ -3,12 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'; import { ArgTypes } from './ArgTypes'; import * as ExampleStories from '../examples/ArgTypesParameters.stories'; +import * as SubcomponentsExampleStories from '../examples/ArgTypesWithSubcomponentsParameters.stories'; const meta: Meta = { title: 'Blocks/ArgTypes', component: ArgTypes, parameters: { - relativeCsfPaths: ['../examples/ArgTypesParameters.stories'], + relativeCsfPaths: [ + '../examples/ArgTypesParameters.stories', + '../examples/ArgTypesWithSubcomponentsParameters.stories', + ], docsStyles: true, }, }; @@ -46,7 +50,6 @@ export const OfUndefined: Story = { decorators: [(s) => (window?.navigator.userAgent.match(/StorybookTestRunner/) ?
: s())], }; -// NOTE: this will throw with no of prop export const OfStoryUnattached: Story = { parameters: { attached: false }, args: { @@ -92,3 +95,36 @@ export const SortParameter: Story = { of: ExampleStories.Sort, }, }; + +export const SubcomponentsOfMeta: Story = { + args: { + of: SubcomponentsExampleStories.default, + }, +}; + +export const SubcomponentsOfStory: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + }, +}; + +export const SubcomponentsIncludeProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + include: ['a', 'f'], + }, +}; + +export const SubcomponentsExcludeProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + exclude: ['a', 'c', 'f', 'g'], + }, +}; + +export const SubcomponentsSortProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + sort: 'alpha', + }, +}; diff --git a/code/ui/blocks/src/blocks/ArgTypes.tsx b/code/ui/blocks/src/blocks/ArgTypes.tsx index 3515af129bbc..7fe3cf8a66c9 100644 --- a/code/ui/blocks/src/blocks/ArgTypes.tsx +++ b/code/ui/blocks/src/blocks/ArgTypes.tsx @@ -7,9 +7,11 @@ import { filterArgTypes } from '@storybook/preview-api'; import type { ArgTypesExtractor } from '@storybook/docs-tools'; import React from 'react'; +import { mapValues } from 'lodash'; import type { SortType } from '../components'; -import { ArgsTable as PureArgsTable, ArgsTableError } from '../components'; +import { ArgsTable as PureArgsTable, ArgsTableError, TabbedArgsTable } from '../components'; import { useOf } from './useOf'; +import { getComponentName } from './utils'; type ArgTypesParameters = { include?: PropDescriptor; @@ -31,7 +33,7 @@ function extractComponentArgTypes( return extractArgTypes(component); } -function getArgTypesFromResolved(resolved: ReturnType, props: ArgTypesProps) { +function getArgTypesFromResolved(resolved: ReturnType) { if (resolved.type === 'component') { const { component, @@ -40,22 +42,23 @@ function getArgTypesFromResolved(resolved: ReturnType, props: ArgT return { argTypes: extractComponentArgTypes(component, parameters), parameters, + component, }; } if (resolved.type === 'meta') { const { - preparedMeta: { argTypes, parameters }, + preparedMeta: { argTypes, parameters, component, subcomponents }, } = resolved; - return { argTypes, parameters }; + return { argTypes, parameters, component, subcomponents }; } // In the case of the story, the enhanceArgs argTypeEnhancer has already added the extracted // arg types from the component to the prepared story. const { - story: { argTypes, parameters }, + story: { argTypes, parameters, component, subcomponents }, } = resolved; - return { argTypes, parameters }; + return { argTypes, parameters, component, subcomponents }; } export const ArgTypes: FC = (props) => { @@ -64,7 +67,7 @@ export const ArgTypes: FC = (props) => { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); } const resolved = useOf(of || 'meta'); - const { argTypes, parameters } = getArgTypesFromResolved(resolved, props); + const { argTypes, parameters, component, subcomponents } = getArgTypesFromResolved(resolved); const argTypesParameters = parameters.docs?.argTypes || ({} as ArgTypesParameters); const include = props.include ?? argTypesParameters.include; @@ -73,5 +76,20 @@ export const ArgTypes: FC = (props) => { const filteredArgTypes = filterArgTypes(argTypes, include, exclude); - return ; + const hasSubcomponents = Boolean(subcomponents) && Object.keys(subcomponents).length > 0; + + if (!hasSubcomponents) { + return ; + } + + const mainComponentName = getComponentName(component); + const subcomponentTabs = mapValues(subcomponents, (comp) => ({ + rows: filterArgTypes(extractComponentArgTypes(comp, parameters), include, exclude), + sort, + })); + const tabs = { + [mainComponentName]: { rows: filteredArgTypes, sort }, + ...subcomponentTabs, + }; + return ; }; diff --git a/code/ui/blocks/src/examples/ArgTypesParameters.tsx b/code/ui/blocks/src/examples/ArgTypesParameters.tsx index 5e2522a21a1f..038b3ac85f18 100644 --- a/code/ui/blocks/src/examples/ArgTypesParameters.tsx +++ b/code/ui/blocks/src/examples/ArgTypesParameters.tsx @@ -3,3 +3,15 @@ import React from 'react'; type PropTypes = { a?: string; b: string }; export const ArgTypesParameters = ({ a = 'a', b }: PropTypes) =>
Example story
; + +type SubcomponentAPropTypes = { e: boolean; c: boolean; d?: boolean }; + +export const SubcomponentA = ({ d = false }: SubcomponentAPropTypes) => ( +
Example subcomponent A
+); + +type SubcomponentBPropTypes = { g: number; h: number; f?: number }; + +export const SubcomponentB = ({ f = 42 }: SubcomponentBPropTypes) => ( +
Example subcomponent B
+); diff --git a/code/ui/blocks/src/examples/ArgTypesWithSubcomponentsParameters.stories.tsx b/code/ui/blocks/src/examples/ArgTypesWithSubcomponentsParameters.stories.tsx new file mode 100644 index 000000000000..79b6c14f882b --- /dev/null +++ b/code/ui/blocks/src/examples/ArgTypesWithSubcomponentsParameters.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ArgTypesParameters, SubcomponentA, SubcomponentB } from './ArgTypesParameters'; + +/** + * Reference stories to be used by the ArgTypes stories + */ +const meta = { + title: 'examples/Stories for the ArgTypes Block with Subcomponents', + component: ArgTypesParameters, + subcomponents: { SubcomponentA, SubcomponentB }, + args: { b: 'b' }, + argTypes: { + // @ts-expect-error Meta type is trying to force us to use real props as args + extraMetaArgType: { + type: { name: 'string' }, + name: 'Extra Meta', + description: 'An extra argtype added at the meta level', + table: { defaultValue: { summary: "'a default value'" } }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const NoParameters: Story = {}; From 15e88a4b43acfaa5829ef10d6eedac30104b0ac3 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 12:51:55 +0100 Subject: [PATCH 02/10] add Categories story to ArgTypes stories --- .../ui/blocks/src/blocks/ArgTypes.stories.tsx | 6 ++++ .../examples/ArgTypesParameters.stories.tsx | 34 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx index a77f9e3ad26f..58f1bf37910f 100644 --- a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx +++ b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx @@ -96,6 +96,12 @@ export const SortParameter: Story = { }, }; +export const Categories: Story = { + args: { + of: ExampleStories.Categories, + }, +}; + export const SubcomponentsOfMeta: Story = { args: { of: SubcomponentsExampleStories.default, diff --git a/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx b/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx index 94df6f2b374d..e79c0c1bf0e3 100644 --- a/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx +++ b/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx @@ -34,17 +34,45 @@ export const NoParameters: Story = { }, }; -export const Include = { +export const Include: Story = { ...NoParameters, parameters: { docs: { argTypes: { include: ['a'] } } }, }; -export const Exclude = { +export const Exclude: Story = { ...NoParameters, parameters: { docs: { argTypes: { exclude: ['a'] } } }, }; -export const Sort = { +export const Sort: Story = { ...NoParameters, parameters: { docs: { argTypes: { sort: 'alpha' } } }, }; + +export const Categories: Story = { + ...NoParameters, + argTypes: { + c: { + description: 'a description', + table: { + category: 'the first category', + }, + }, + d: { + table: { + category: 'the first category', + subcategory: 'a subcategory', + }, + }, + e: { + table: { + subcategory: 'a subcategory without a category', + }, + }, + f: { + table: { + category: 'the second category', + }, + }, + } as any, +}; From 63c3623e2a38c4c462e4ced6763e1ca475dfff00 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 12:56:10 +0100 Subject: [PATCH 03/10] add Categories story to Controls block --- .../ui/blocks/src/blocks/Controls.stories.tsx | 6 +++ .../examples/ControlsParameters.stories.tsx | 43 +++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/code/ui/blocks/src/blocks/Controls.stories.tsx b/code/ui/blocks/src/blocks/Controls.stories.tsx index 7bde517b7bfa..27e840f9b199 100644 --- a/code/ui/blocks/src/blocks/Controls.stories.tsx +++ b/code/ui/blocks/src/blocks/Controls.stories.tsx @@ -79,3 +79,9 @@ export const SortParameter: Story = { of: ExampleStories.Sort, }, }; + +export const Categories: Story = { + args: { + of: ExampleStories.Categories, + }, +}; diff --git a/code/ui/blocks/src/examples/ControlsParameters.stories.tsx b/code/ui/blocks/src/examples/ControlsParameters.stories.tsx index 603f188a30de..402e49c601f3 100644 --- a/code/ui/blocks/src/examples/ControlsParameters.stories.tsx +++ b/code/ui/blocks/src/examples/ControlsParameters.stories.tsx @@ -34,17 +34,54 @@ export const NoParameters: Story = { }, }; -export const Include = { +export const Include: Story = { ...NoParameters, parameters: { docs: { controls: { include: ['a'] } } }, }; -export const Exclude = { +export const Exclude: Story = { ...NoParameters, parameters: { docs: { controls: { exclude: ['a'] } } }, }; -export const Sort = { +export const Sort: Story = { ...NoParameters, parameters: { docs: { controls: { sort: 'alpha' } } }, }; + +export const Categories: Story = { + ...NoParameters, + argTypes: { + c: { + description: 'a description', + control: { + type: 'text', + }, + table: { + category: 'the first category', + }, + }, + d: { + control: { + type: 'number', + }, + table: { + category: 'the first category', + subcategory: 'a subcategory', + }, + }, + e: { + control: { + type: 'color', + }, + table: { + subcategory: 'a subcategory without a category', + }, + }, + f: { + table: { + category: 'the second category', + }, + }, + } as any, +}; From 34c40457fd1f206ebbdb4ca49440a27348dfaf56 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 13:26:36 +0100 Subject: [PATCH 04/10] add subcomponents support to Controls block --- .../ui/blocks/src/blocks/Controls.stories.tsx | 33 +++++++++++++- code/ui/blocks/src/blocks/Controls.tsx | 44 +++++++++++++++---- .../components/ArgsTable/TabbedArgsTable.tsx | 10 ++--- .../src/examples/ControlsParameters.tsx | 12 +++++ ...olsWithSubcomponentsParameters.stories.tsx | 36 +++++++++++++++ 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 code/ui/blocks/src/examples/ControlsWithSubcomponentsParameters.stories.tsx diff --git a/code/ui/blocks/src/blocks/Controls.stories.tsx b/code/ui/blocks/src/blocks/Controls.stories.tsx index 27e840f9b199..c5d67cd9fd80 100644 --- a/code/ui/blocks/src/blocks/Controls.stories.tsx +++ b/code/ui/blocks/src/blocks/Controls.stories.tsx @@ -3,11 +3,15 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Controls } from './Controls'; import * as ExampleStories from '../examples/ControlsParameters.stories'; +import * as SubcomponentsExampleStories from '../examples/ControlsWithSubcomponentsParameters.stories'; const meta: Meta = { component: Controls, parameters: { - relativeCsfPaths: ['../examples/ControlsParameters.stories'], + relativeCsfPaths: [ + '../examples/ControlsParameters.stories', + '../examples/ControlsWithSubcomponentsParameters.stories', + ], docsStyles: true, }, }; @@ -85,3 +89,30 @@ export const Categories: Story = { of: ExampleStories.Categories, }, }; + +export const SubcomponentsOfStory: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + }, +}; + +export const SubcomponentsIncludeProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + include: ['a', 'f'], + }, +}; + +export const SubcomponentsExcludeProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + exclude: ['a', 'c', 'f', 'g'], + }, +}; + +export const SubcomponentsSortProp: Story = { + args: { + of: SubcomponentsExampleStories.NoParameters, + sort: 'alpha', + }, +}; diff --git a/code/ui/blocks/src/blocks/Controls.tsx b/code/ui/blocks/src/blocks/Controls.tsx index 9712aeda130e..f705f345bef7 100644 --- a/code/ui/blocks/src/blocks/Controls.tsx +++ b/code/ui/blocks/src/blocks/Controls.tsx @@ -1,16 +1,19 @@ /* eslint-disable react/destructuring-assignment */ -import type { Renderer } from '@storybook/csf'; +import type { Renderer, Parameters, StrictArgTypes } from '@storybook/csf'; import type { ModuleExports } from '@storybook/types'; import type { FC } from 'react'; import React, { useContext } from 'react'; import { filterArgTypes } from '@storybook/preview-api'; import type { PropDescriptor } from '@storybook/preview-api'; +import type { ArgTypesExtractor } from '@storybook/docs-tools'; +import { mapValues } from 'lodash'; import type { SortType } from '../components'; -import { ArgsTable as PureArgsTable } from '../components'; +import { ArgsTable as PureArgsTable, ArgsTableError, TabbedArgsTable } from '../components'; import { DocsContext } from './DocsContext'; import { useGlobals } from './useGlobals'; import { useArgs } from './useArgs'; +import { getComponentName } from './utils'; type ControlsParameters = { include?: PropDescriptor; @@ -22,6 +25,17 @@ type ControlsProps = ControlsParameters & { of?: Renderer['component'] | ModuleExports; }; +function extractComponentArgTypes( + component: Renderer['component'], + parameters: Parameters +): StrictArgTypes { + const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = parameters.docs || {}; + if (!extractArgTypes) { + throw new Error(ArgsTableError.ARGS_UNSUPPORTED); + } + return extractArgTypes(component); +} + export const Controls: FC = (props) => { const { of } = props; if ('of' in props && of === undefined) { @@ -30,26 +44,40 @@ export const Controls: FC = (props) => { const context = useContext(DocsContext); const { story } = context.resolveOf(of || 'story', ['story']); - const { parameters, argTypes } = story; + const { parameters, argTypes, component, subcomponents } = story; const controlsParameters = parameters.docs?.controls || ({} as ControlsParameters); const include = props.include ?? controlsParameters.include; const exclude = props.exclude ?? controlsParameters.exclude; const sort = props.sort ?? controlsParameters.sort; - const [args, updateArgs, resetArgs] = useArgs(story, context); + const [, updateArgs, resetArgs] = useArgs(story, context); const [globals] = useGlobals(story, context); const filteredArgTypes = filterArgTypes(argTypes, include, exclude); + const hasSubcomponents = Boolean(subcomponents) && Object.keys(subcomponents).length > 0; + + if (!hasSubcomponents) { + return ; + } + + const mainComponentName = getComponentName(component); + const subcomponentTabs = mapValues(subcomponents, (comp) => ({ + rows: filterArgTypes(extractComponentArgTypes(comp, parameters), include, exclude), + sort, + })); + const tabs = { + [mainComponentName]: { rows: filteredArgTypes, sort }, + ...subcomponentTabs, + }; return ( - ); }; diff --git a/code/ui/blocks/src/components/ArgsTable/TabbedArgsTable.tsx b/code/ui/blocks/src/components/ArgsTable/TabbedArgsTable.tsx index 99a1b46f902c..5a0e421201d2 100644 --- a/code/ui/blocks/src/components/ArgsTable/TabbedArgsTable.tsx +++ b/code/ui/blocks/src/components/ArgsTable/TabbedArgsTable.tsx @@ -2,15 +2,15 @@ import type { FC } from 'react'; import React from 'react'; import { TabsState } from '@storybook/components'; -import type { ArgsTableProps, SortType } from './ArgsTable'; +import type { ArgsTableProps } from './ArgsTable'; // eslint-disable-next-line import/no-cycle import { ArgsTable } from './ArgsTable'; -export interface TabbedArgsTableProps { - children?: React.ReactNode; +type DistributiveOmit = T extends any ? Omit : never; + +export type TabbedArgsTableProps = DistributiveOmit & { tabs: Record; - sort?: SortType; -} +}; export const TabbedArgsTable: FC = ({ tabs, ...props }) => { const entries = Object.entries(tabs); diff --git a/code/ui/blocks/src/examples/ControlsParameters.tsx b/code/ui/blocks/src/examples/ControlsParameters.tsx index 8d32189898d6..ceaa0801eb76 100644 --- a/code/ui/blocks/src/examples/ControlsParameters.tsx +++ b/code/ui/blocks/src/examples/ControlsParameters.tsx @@ -3,3 +3,15 @@ import React from 'react'; type PropTypes = { a?: string; b: string }; export const ControlsParameters = ({ a = 'a', b }: PropTypes) =>
Example story
; + +type SubcomponentAPropTypes = { e: boolean; c: boolean; d?: boolean }; + +export const SubcomponentA = ({ d = false }: SubcomponentAPropTypes) => ( +
Example subcomponent A
+); + +type SubcomponentBPropTypes = { g: number; h: number; f?: number }; + +export const SubcomponentB = ({ f = 42 }: SubcomponentBPropTypes) => ( +
Example subcomponent B
+); diff --git a/code/ui/blocks/src/examples/ControlsWithSubcomponentsParameters.stories.tsx b/code/ui/blocks/src/examples/ControlsWithSubcomponentsParameters.stories.tsx new file mode 100644 index 000000000000..65d350564c1a --- /dev/null +++ b/code/ui/blocks/src/examples/ControlsWithSubcomponentsParameters.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ControlsParameters, SubcomponentA, SubcomponentB } from './ControlsParameters'; + +/** + * Reference stories to be used by the Controls stories + */ +const meta = { + title: 'examples/Stories for the Controls Block with Subcomponents', + component: ControlsParameters, + subcomponents: { SubcomponentA, SubcomponentB }, + args: { b: 'b' }, + argTypes: { + // @ts-expect-error Meta type is trying to force us to use real props as args + extraMetaArgType: { + type: { name: 'string' }, + name: 'Extra Meta', + description: 'An extra argtype added at the meta level', + table: { defaultValue: { summary: "'a default value'" } }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const NoParameters: Story = { + argTypes: { + // @ts-expect-error Story type is trying to force us to use real props as args + extraStoryArgType: { + type: { name: 'string' }, + name: 'Extra Story', + description: 'An extra argtype added at the story level', + table: { defaultValue: { summary: "'a default value'" } }, + }, + }, +}; From 686df3f3f5692dd75935eb4ff824b3d166ea0d6b Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 13:32:46 +0100 Subject: [PATCH 05/10] remove subcomponents deprecation warning --- .../template/stories/docspage/description.stories.ts | 1 - .../src/modules/store/csf/processCSFFile.ts | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/code/addons/docs/template/stories/docspage/description.stories.ts b/code/addons/docs/template/stories/docspage/description.stories.ts index f42edc5901c3..e6a2eac1587f 100644 --- a/code/addons/docs/template/stories/docspage/description.stories.ts +++ b/code/addons/docs/template/stories/docspage/description.stories.ts @@ -2,7 +2,6 @@ import { global as globalThis } from '@storybook/global'; export default { component: globalThis.Components.Button, - // FIXME: remove array subcomponents in 7.0? subcomponents: { Pre: globalThis.Components.Pre, }, diff --git a/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts b/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts index 55fc8895eecd..4ee70aaebb60 100644 --- a/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts +++ b/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts @@ -39,15 +39,6 @@ const checkDisallowedParameters = (parameters?: Parameters) => { checkStorySort(parameters); }; -const checkSubcomponents = (meta: ModuleExports) => { - if (meta.subcomponents) { - deprecate(dedent`The \`subcomponents\` annotation is deprecated. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#argstable-block' - `); - } -}; - // Given the raw exports of a CSF file, check and normalize it. export function processCSFFile( moduleExports: ModuleExports, @@ -63,7 +54,6 @@ export function processCSFFile( importPath ); checkDisallowedParameters(meta.parameters); - checkSubcomponents(meta); const csfFile: CSFFile = { meta, stories: {}, moduleExports }; From b380b850253e47cb976f3659a3006ec48b92d632 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 14:24:30 +0100 Subject: [PATCH 06/10] remove ArgsTable doc block --- MIGRATION.md | 15 +- code/ui/blocks/src/blocks/ArgsTable.tsx | 277 ------------------------ code/ui/blocks/src/blocks/index.ts | 1 - 3 files changed, 11 insertions(+), 282 deletions(-) delete mode 100644 code/ui/blocks/src/blocks/ArgsTable.tsx diff --git a/MIGRATION.md b/MIGRATION.md index 670f89c77ac8..509a014e44cc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -60,8 +60,9 @@ - [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) + - [`ArgsTable` Doc block removed](#argstable-doc-block-removed) + - [`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) - [StoryStore and methods deprecated](#storystore-and-methods-deprecated) @@ -975,11 +976,17 @@ api.collapseAll(); // becomes api.emit(STORIES_COLLAPSE_ALL) api.expandAll(); // becomes api.emit(STORIES_EXPAND_ALL) ``` -#### Source Doc block properties +#### `ArgsTable` Doc block removed + +The `ArgsTable` doc block have now been removed in favor of `ArgTypes` and `Controls`. [More info](#argstable-block). + +With this removal we've reintroduced `subcomponents` support to `ArgTypes`, `Controls` and in autodocs by popular demand. + +#### `Source` Doc block properties `id` and `ids` are now removed in favor of the `of` property. [More info](#doc-blocks). -#### Canvas Doc block properties +#### `Canvas` Doc block properties The following properties were removed from the Canvas Doc block: diff --git a/code/ui/blocks/src/blocks/ArgsTable.tsx b/code/ui/blocks/src/blocks/ArgsTable.tsx deleted file mode 100644 index 239e616e1b93..000000000000 --- a/code/ui/blocks/src/blocks/ArgsTable.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import type { FC } from 'react'; -import React, { useContext, useEffect, useState, useCallback } from 'react'; -import mapValues from 'lodash/mapValues.js'; -import type { ArgTypesExtractor } from '@storybook/docs-tools'; -import type { PropDescriptor } from '@storybook/preview-api'; -import { filterArgTypes } from '@storybook/preview-api'; -import type { StrictArgTypes, Args, Globals, Parameters } from '@storybook/types'; -import { - STORY_ARGS_UPDATED, - UPDATE_STORY_ARGS, - RESET_STORY_ARGS, - GLOBALS_UPDATED, -} from '@storybook/core-events'; -import { deprecate } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; -import type { ArgsTableProps as PureArgsTableProps, SortType } from '../components'; -import { ArgsTable as PureArgsTable, ArgsTableError, TabbedArgsTable } from '../components'; - -import type { DocsContextProps } from './DocsContext'; -import { DocsContext } from './DocsContext'; -import type { Component } from './types'; -import { PRIMARY_STORY } from './types'; -import { getComponentName } from './utils'; -import { useStory } from './useStory'; - -interface BaseProps { - include?: PropDescriptor; - exclude?: PropDescriptor; - sort?: SortType; -} - -type OfProps = BaseProps & { - of: '^' | Component; -}; - -type ComponentsProps = BaseProps & { - parameters: Parameters; - components: { - [label: string]: Component; - }; -}; - -type StoryProps = BaseProps & { - story: '.' | '^' | string; - showComponent?: boolean; -}; - -type ArgsTableProps = BaseProps | OfProps | ComponentsProps | StoryProps; - -const useArgs = ( - storyId: string, - context: DocsContextProps -): [Args, (args: Args) => void, (argNames?: string[]) => void] => { - const storyContext = context.getStoryContext(context.storyById()); - - const [args, setArgs] = useState(storyContext.args); - useEffect(() => { - const cb = (changed: { storyId: string; args: Args }) => { - if (changed.storyId === storyId) { - setArgs(changed.args); - } - }; - context.channel.on(STORY_ARGS_UPDATED, cb); - return () => context.channel.off(STORY_ARGS_UPDATED, cb); - }, [storyId]); - const updateArgs = useCallback( - (updatedArgs: any) => context.channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }), - [storyId] - ); - const resetArgs = useCallback( - (argNames?: string[]) => context.channel.emit(RESET_STORY_ARGS, { storyId, argNames }), - [storyId] - ); - return [args, updateArgs, resetArgs]; -}; - -const useGlobals = (context: DocsContextProps): [Globals] => { - const storyContext = context.getStoryContext(context.storyById()); - const [globals, setGlobals] = useState(storyContext.globals); - - useEffect(() => { - const cb = (changed: { globals: Globals }) => { - setGlobals(changed.globals); - }; - context.channel.on(GLOBALS_UPDATED, cb); - return () => context.channel.off(GLOBALS_UPDATED, cb); - }, []); - - return [globals]; -}; - -export const extractComponentArgTypes = ( - component: Component, - parameters: Parameters, - include?: PropDescriptor, - exclude?: PropDescriptor -): StrictArgTypes => { - const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = parameters.docs || {}; - if (!extractArgTypes) { - throw new Error(ArgsTableError.ARGS_UNSUPPORTED); - } - let argTypes = extractArgTypes(component); - argTypes = filterArgTypes(argTypes, include, exclude); - - return argTypes; -}; - -const isShortcut = (value?: string) => { - return value && [PRIMARY_STORY].includes(value); -}; - -export const getComponent = (props: ArgsTableProps = {}, component: Component): Component => { - const { of } = props as OfProps; - const { story } = props as StoryProps; - if (isShortcut(of) || isShortcut(story)) { - return component || null; - } - if (!of) { - throw new Error(ArgsTableError.NO_COMPONENT); - } - return of; -}; - -const addComponentTabs = ( - tabs: Record, - components: Record, - parameters: Parameters, - include?: PropDescriptor, - exclude?: PropDescriptor, - sort?: SortType -) => ({ - ...tabs, - ...mapValues(components, (comp) => ({ - rows: extractComponentArgTypes(comp, parameters, include, exclude), - sort, - })), -}); - -export const StoryTable: FC< - StoryProps & { component: Component; subcomponents: Record } -> = (props) => { - const context = useContext(DocsContext); - const { - story: storyName, - component, - subcomponents, - showComponent, - include, - exclude, - sort, - } = props; - try { - let storyId; - switch (storyName) { - case PRIMARY_STORY: { - const primaryStory = context.storyById(); - storyId = primaryStory.id; - break; - } - default: { - storyId = context.storyIdByName(storyName); - } - } - - const story = useStory(storyId, context); - // eslint-disable-next-line prefer-const - let [args, updateArgs, resetArgs] = useArgs(storyId, context); - - const [globals] = useGlobals(context); - if (!story) return ; - - const argTypes = filterArgTypes(story.argTypes, include, exclude); - - const mainLabel = getComponentName(component) || 'Story'; - - let tabs = { [mainLabel]: { rows: argTypes, args, globals, updateArgs, resetArgs } } as Record< - string, - PureArgsTableProps - >; - - // Use the dynamically generated component tabs if there are no controls - const storyHasArgsWithControls = argTypes && Object.values(argTypes).find((v) => !!v?.control); - - if (!storyHasArgsWithControls) { - updateArgs = null; - resetArgs = null; - tabs = {}; - } - - if (component && (!storyHasArgsWithControls || showComponent)) { - tabs = addComponentTabs(tabs, { [mainLabel]: component }, story.parameters, include, exclude); - } - - if (subcomponents) { - if (Array.isArray(subcomponents)) { - throw new Error( - `Unexpected subcomponents array. Expected an object whose keys are tab labels and whose values are components.` - ); - } - tabs = addComponentTabs(tabs, subcomponents, story.parameters, include, exclude); - } - return ; - } catch (err) { - return ; - } -}; - -export const ComponentsTable: FC = (props) => { - const { components, include, exclude, sort, parameters } = props; - - const tabs = addComponentTabs({}, components, parameters, include, exclude); - return ; -}; - -export const ArgsTable: FC = (props) => { - deprecate(dedent`The ArgsTable doc block is deprecated. Instead use the ArgTypes doc block for static tables or the Controls doc block for tables with controls. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#argstable-block - `); - const context = useContext(DocsContext); - - let parameters: Parameters; - let component: any; - let subcomponents: Record; - try { - ({ parameters, component, subcomponents } = context.storyById()); - } catch (err) { - const { of } = props as OfProps; - if ('of' in props && of === undefined) { - throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); - } - ({ - projectAnnotations: { parameters }, - } = context.resolveOf(of, ['component'])); - } - - const { include, exclude, components, sort: sortProp } = props as ComponentsProps; - const { story: storyName } = props as StoryProps; - - const sort = sortProp || parameters.controls?.sort; - - const main = getComponent(props, component); - if (storyName) { - return ; - } - - if (!components && !subcomponents) { - let mainProps; - try { - mainProps = { rows: extractComponentArgTypes(main, parameters, include, exclude) }; - } catch (err) { - mainProps = { error: err.message }; - } - - return ; - } - - if (components) { - return ( - - ); - } - - const mainLabel = getComponentName(main); - return ( - - ); -}; - -ArgsTable.defaultProps = { - of: PRIMARY_STORY, -}; diff --git a/code/ui/blocks/src/blocks/index.ts b/code/ui/blocks/src/blocks/index.ts index 464ddc8c0b76..c6d3565b6869 100644 --- a/code/ui/blocks/src/blocks/index.ts +++ b/code/ui/blocks/src/blocks/index.ts @@ -2,7 +2,6 @@ export { ColorPalette, ColorItem, IconGallery, IconItem, Typeset } from '../comp export * from './Anchor'; export * from './ArgTypes'; -export * from './ArgsTable'; export * from './Canvas'; export * from './Controls'; export * from './Description'; From 56e6f7c86db4cd26ab17e6ad77a5815b98038c9a Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 14:43:58 +0100 Subject: [PATCH 07/10] remove unused imports --- code/lib/preview-api/src/modules/store/csf/processCSFFile.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts b/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts index 4ee70aaebb60..c29322a25277 100644 --- a/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts +++ b/code/lib/preview-api/src/modules/store/csf/processCSFFile.ts @@ -8,9 +8,8 @@ import type { NormalizedComponentAnnotations, } from '@storybook/types'; import { isExportStory } from '@storybook/csf'; -import { deprecate, logger } from '@storybook/client-logger'; +import { logger } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; import { normalizeStory } from './normalizeStory'; import { normalizeComponentAnnotations } from './normalizeComponentAnnotations'; From 194291ab3aed12e5968155cd2222e339cf169156 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 16 Jan 2024 16:52:06 +0100 Subject: [PATCH 08/10] Migration.md improvements Co-authored-by: Michael Shilman --- MIGRATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index cff40f49b925..fd402be2dddb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -998,9 +998,9 @@ api.expandAll(); // becomes api.emit(STORIES_EXPAND_ALL) #### `ArgsTable` Doc block removed -The `ArgsTable` doc block have now been removed in favor of `ArgTypes` and `Controls`. [More info](#argstable-block). +The `ArgsTable` doc block has been removed in favor of `ArgTypes` and `Controls`. [More info](#argstable-block). -With this removal we've reintroduced `subcomponents` support to `ArgTypes`, `Controls` and in autodocs by popular demand. +With this removal we've reintroduced `subcomponents` support to `ArgTypes`, `Controls`, and autodocs. We've also undeprecated `subcomponents`, by popular demand. #### `Source` Doc block properties From 0ac271041470ac3475087ae00a01e8b6dcb0df9f Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 18 Jan 2024 13:24:19 +0100 Subject: [PATCH 09/10] improve stories --- .../ui/blocks/src/blocks/ArgTypes.stories.tsx | 27 +++++++++++++++++++ .../ui/blocks/src/blocks/Controls.stories.tsx | 26 ++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx index 58f1bf37910f..509497af2be1 100644 --- a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx +++ b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx @@ -4,6 +4,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import { ArgTypes } from './ArgTypes'; import * as ExampleStories from '../examples/ArgTypesParameters.stories'; import * as SubcomponentsExampleStories from '../examples/ArgTypesWithSubcomponentsParameters.stories'; +import { within } from '@storybook/test'; +import type { PlayFunctionContext } from '@storybook/csf'; const meta: Meta = { title: 'Blocks/ArgTypes', @@ -102,13 +104,31 @@ export const Categories: Story = { }, }; +const findSubcomponentTabs = async ( + canvas: ReturnType, + step: PlayFunctionContext['step'] +) => { + let subcomponentATab: HTMLElement; + let subcomponentBTab: HTMLElement; + await step('should have tabs for the subcomponents', async () => { + subcomponentATab = await canvas.findByText('SubcomponentA'); + subcomponentBTab = await canvas.findByText('SubcomponentB'); + }); + return { subcomponentATab, subcomponentBTab }; +}; + export const SubcomponentsOfMeta: Story = { args: { of: SubcomponentsExampleStories.default, }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await findSubcomponentTabs(canvas, step); + }, }; export const SubcomponentsOfStory: Story = { + ...SubcomponentsOfMeta, args: { of: SubcomponentsExampleStories.NoParameters, }, @@ -119,9 +139,15 @@ export const SubcomponentsIncludeProp: Story = { of: SubcomponentsExampleStories.NoParameters, include: ['a', 'f'], }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + const { subcomponentBTab } = await findSubcomponentTabs(canvas, step); + await subcomponentBTab.click(); + }, }; export const SubcomponentsExcludeProp: Story = { + ...SubcomponentsIncludeProp, args: { of: SubcomponentsExampleStories.NoParameters, exclude: ['a', 'c', 'f', 'g'], @@ -129,6 +155,7 @@ export const SubcomponentsExcludeProp: Story = { }; export const SubcomponentsSortProp: Story = { + ...SubcomponentsIncludeProp, args: { of: SubcomponentsExampleStories.NoParameters, sort: 'alpha', diff --git a/code/ui/blocks/src/blocks/Controls.stories.tsx b/code/ui/blocks/src/blocks/Controls.stories.tsx index c5d67cd9fd80..9d32d9fe12f1 100644 --- a/code/ui/blocks/src/blocks/Controls.stories.tsx +++ b/code/ui/blocks/src/blocks/Controls.stories.tsx @@ -4,6 +4,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Controls } from './Controls'; import * as ExampleStories from '../examples/ControlsParameters.stories'; import * as SubcomponentsExampleStories from '../examples/ControlsWithSubcomponentsParameters.stories'; +import { within } from '@storybook/test'; +import type { PlayFunctionContext } from '@storybook/csf'; const meta: Meta = { component: Controls, @@ -90,10 +92,27 @@ export const Categories: Story = { }, }; +const findSubcomponentTabs = async ( + canvas: ReturnType, + step: PlayFunctionContext['step'] +) => { + let subcomponentATab: HTMLElement; + let subcomponentBTab: HTMLElement; + await step('should have tabs for the subcomponents', async () => { + subcomponentATab = await canvas.findByText('SubcomponentA'); + subcomponentBTab = await canvas.findByText('SubcomponentB'); + }); + return { subcomponentATab, subcomponentBTab }; +}; + export const SubcomponentsOfStory: Story = { args: { of: SubcomponentsExampleStories.NoParameters, }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await findSubcomponentTabs(canvas, step); + }, }; export const SubcomponentsIncludeProp: Story = { @@ -101,9 +120,15 @@ export const SubcomponentsIncludeProp: Story = { of: SubcomponentsExampleStories.NoParameters, include: ['a', 'f'], }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + const { subcomponentBTab } = await findSubcomponentTabs(canvas, step); + await subcomponentBTab.click(); + }, }; export const SubcomponentsExcludeProp: Story = { + ...SubcomponentsIncludeProp, args: { of: SubcomponentsExampleStories.NoParameters, exclude: ['a', 'c', 'f', 'g'], @@ -111,6 +136,7 @@ export const SubcomponentsExcludeProp: Story = { }; export const SubcomponentsSortProp: Story = { + ...SubcomponentsIncludeProp, args: { of: SubcomponentsExampleStories.NoParameters, sort: 'alpha', From 533ea850a7c225ca2393c56d9ffaf2434b786a7e Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 18 Jan 2024 13:28:35 +0100 Subject: [PATCH 10/10] enable docgen in build test mode --- code/ui/.storybook/main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/ui/.storybook/main.ts b/code/ui/.storybook/main.ts index a15c3a4fd988..3defc0046331 100644 --- a/code/ui/.storybook/main.ts +++ b/code/ui/.storybook/main.ts @@ -57,7 +57,10 @@ const config: StorybookConfig = { ], build: { test: { + // we have stories for the blocks here, we can't exclude them disableBlocks: false, + // some stories in blocks (ArgTypes, Controls) depends on argTypes inference + disableDocgen: false, }, }, framework: {