diff --git a/code/ui/blocks/src/blocks/ArgTypes.stories.tsx b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx new file mode 100644 index 000000000000..5248a56a8e3c --- /dev/null +++ b/code/ui/blocks/src/blocks/ArgTypes.stories.tsx @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { ArgTypes } from './ArgTypes'; +import * as ExampleStories from '../examples/ArgTypesParameters.stories'; + +const meta: Meta = { + title: 'Blocks/ArgTypes', + component: ArgTypes, + parameters: { + relativeCsfPaths: ['../examples/ArgTypesParameters.stories'], + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const OfComponent: Story = { + args: { + of: ExampleStories.default.component, + }, +}; + +export const OfMeta: Story = { + args: { + of: ExampleStories.default, + }, +}; + +export const OfStory: Story = { + args: { + of: ExampleStories.NoParameters, + }, +}; + +// NOTE: this will throw with no of prop +export const OfStoryUnattached: Story = { + parameters: { attached: false }, + args: { + of: ExampleStories.NoParameters, + }, +}; + +export const IncludeProp: Story = { + args: { + of: ExampleStories.NoParameters, + include: ['a'], + }, +}; + +export const IncludeParameter: Story = { + args: { + of: ExampleStories.Include, + }, +}; + +export const ExcludeProp: Story = { + args: { + of: ExampleStories.NoParameters, + exclude: ['a'], + }, +}; + +export const ExcludeParameter: Story = { + args: { + of: ExampleStories.Exclude, + }, +}; + +export const SortProp: Story = { + args: { + of: ExampleStories.NoParameters, + sort: 'alpha', + }, +}; + +export const SortParameter: Story = { + args: { + of: ExampleStories.Sort, + }, +}; diff --git a/code/ui/blocks/src/blocks/ArgTypes.tsx b/code/ui/blocks/src/blocks/ArgTypes.tsx new file mode 100644 index 000000000000..d7443489a107 --- /dev/null +++ b/code/ui/blocks/src/blocks/ArgTypes.tsx @@ -0,0 +1,76 @@ +/* eslint-disable react/destructuring-assignment */ +import type { Parameters, Renderer, StrictArgTypes } from '@storybook/csf'; +import type { ModuleExports } from '@storybook/types'; +import type { FC } from 'react'; +import type { PropDescriptor } from '@storybook/preview-api'; +import { filterArgTypes } from '@storybook/preview-api'; +import type { ArgTypesExtractor } from '@storybook/docs-tools'; +import React from 'react'; + +import type { SortType } from '../components'; +import { ArgsTable as PureArgsTable, ArgsTableError } from '../components'; +import { useOf } from './useOf'; + +type ArgTypesParameters = { + include?: PropDescriptor; + exclude?: PropDescriptor; + sort?: SortType; +}; + +type ArgTypesProps = ArgTypesParameters & { + of: Renderer['component'] | ModuleExports; +}; + +// TODO: generalize +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); +} + +function getArgTypesFromResolved(resolved: ReturnType, props: ArgTypesProps) { + if (resolved.type === 'component') { + const { + component, + projectAnnotations: { parameters }, + } = resolved; + return { + argTypes: extractComponentArgTypes(component, parameters), + parameters, + }; + } + + if (resolved.type === 'meta') { + const { + preparedMeta: { argTypes, parameters }, + } = resolved; + return { argTypes, parameters }; + } + + // 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 }, + } = resolved; + return { argTypes, parameters }; +} + +export const ArgTypes: FC = (props) => { + const { of } = props; + const resolved = useOf(of || 'meta'); + const { argTypes, parameters } = getArgTypesFromResolved(resolved, props); + const argTypesParameters = parameters.docs?.argTypes || ({} as ArgTypesParameters); + + const include = props.include ?? argTypesParameters.include; + const exclude = props.exclude ?? argTypesParameters.exclude; + const sort = props.sort ?? argTypesParameters.sort; + + const filteredArgTypes = filterArgTypes(argTypes, include, exclude); + + return ; +}; diff --git a/code/ui/blocks/src/blocks/index.ts b/code/ui/blocks/src/blocks/index.ts index 0fa8da8c4ea5..105c08191b4c 100644 --- a/code/ui/blocks/src/blocks/index.ts +++ b/code/ui/blocks/src/blocks/index.ts @@ -1,6 +1,7 @@ export { ColorPalette, ColorItem, IconGallery, IconItem, Typeset } from '../components'; export * from './Anchor'; +export * from './ArgTypes'; export * from './ArgsTable'; // eslint-disable-next-line import/export export * from './Canvas'; diff --git a/code/ui/blocks/src/components/Story.stories.tsx b/code/ui/blocks/src/components/Story.stories.tsx index 776be4f4ba87..e157a665b7c1 100644 --- a/code/ui/blocks/src/components/Story.stories.tsx +++ b/code/ui/blocks/src/components/Story.stories.tsx @@ -74,8 +74,7 @@ export const ForceInitialArgs = { // test that it ignores updated args by emitting an arg update and assert that it isn't reflected in the DOM play: async ({ args, canvasElement, loaded }: PlayFunctionContext) => { const docsContext = loaded.docsContext as DocsContextProps; - const resolved = docsContext.resolveOf(args.storyExport); - if (resolved.type !== 'story') throw new Error('Bad export, pass a story!'); + const resolved = docsContext.resolveOf(args.storyExport, ['story']); await within(canvasElement).findByText(/Button/); diff --git a/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx b/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx new file mode 100644 index 000000000000..dfb6162c4262 --- /dev/null +++ b/code/ui/blocks/src/examples/ArgTypesParameters.stories.tsx @@ -0,0 +1,50 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ArgTypesParameters } from './ArgTypesParameters'; + +/** + * Reference stories to be used by the ArgTypes stories + */ +const meta = { + title: 'Example/Stories for ArgTypes', + component: ArgTypesParameters, + 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'" } }, + }, + }, +}; + +export const Include = { + ...NoParameters, + parameters: { docs: { argTypes: { include: ['a'] } } }, +}; + +export const Exclude = { + ...NoParameters, + parameters: { docs: { argTypes: { exclude: ['a'] } } }, +}; + +export const Sort = { + ...NoParameters, + parameters: { docs: { argTypes: { sort: 'alpha' } } }, +}; diff --git a/code/ui/blocks/src/examples/ArgTypesParameters.tsx b/code/ui/blocks/src/examples/ArgTypesParameters.tsx new file mode 100644 index 000000000000..5e2522a21a1f --- /dev/null +++ b/code/ui/blocks/src/examples/ArgTypesParameters.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +type PropTypes = { a?: string; b: string }; + +export const ArgTypesParameters = ({ a = 'a', b }: PropTypes) =>
Example story
;