Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Implement Controls block #20683

Merged
merged 7 commits into from
Jan 20, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions code/ui/blocks/src/blocks/ArgTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ type ArgTypesParameters = {
};

type ArgTypesProps = ArgTypesParameters & {
of: Renderer['component'] | ModuleExports;
of?: Renderer['component'] | ModuleExports;
};

// TODO: generalize
function extractComponentArgTypes(
component: Renderer['component'],
parameters: Parameters
Expand Down
69 changes: 69 additions & 0 deletions code/ui/blocks/src/blocks/Controls.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Meta, StoryObj } from '@storybook/react';

import { Controls } from './Controls';
import * as ExampleStories from '../examples/ControlsParameters.stories';

const meta: Meta<typeof Controls> = {
component: Controls,
parameters: {
relativeCsfPaths: ['../examples/ControlsParameters.stories'],
},
};
export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

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 = {
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
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,
},
};
98 changes: 98 additions & 0 deletions code/ui/blocks/src/blocks/Controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* eslint-disable react/destructuring-assignment */
import type { Args, Globals, Renderer } from '@storybook/csf';
import type { DocsContextProps, ModuleExports, PreparedStory } from '@storybook/types';
import type { FC } from 'react';
import React, { useCallback, useEffect, useState, useContext } from 'react';
import type { PropDescriptor } from '@storybook/preview-api';
import { filterArgTypes } from '@storybook/preview-api';
import {
STORY_ARGS_UPDATED,
UPDATE_STORY_ARGS,
RESET_STORY_ARGS,
GLOBALS_UPDATED,
} from '@storybook/core-events';

import type { SortType } from '../components';
import { ArgsTable as PureArgsTable } from '../components';
import { DocsContext } from './DocsContext';

type ControlsParameters = {
include?: PropDescriptor;
exclude?: PropDescriptor;
sort?: SortType;
};

type ControlsProps = ControlsParameters & {
of?: Renderer['component'] | ModuleExports;
};

const useArgs = (
story: PreparedStory,
context: DocsContextProps
): [Args, (args: Args) => void, (argNames?: string[]) => void] => {
const storyContext = context.getStoryContext(story);
const { id: storyId } = story;

const [args, setArgs] = useState(storyContext.args);
useEffect(() => {
const onArgsUpdated = (changed: { storyId: string; args: Args }) => {
if (changed.storyId === storyId) {
setArgs(changed.args);
}
};
context.channel.on(STORY_ARGS_UPDATED, onArgsUpdated);
return () => context.channel.off(STORY_ARGS_UPDATED, onArgsUpdated);
}, [storyId, context.channel]);
const updateArgs = useCallback(
(updatedArgs) => context.channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }),
[storyId, context.channel]
);
tmeasday marked this conversation as resolved.
Show resolved Hide resolved
const resetArgs = useCallback(
(argNames?: string[]) => context.channel.emit(RESET_STORY_ARGS, { storyId, argNames }),
[storyId, context.channel]
);
return [args, updateArgs, resetArgs];
};

const useGlobals = (story: PreparedStory, context: DocsContextProps): [Globals] => {
const storyContext = context.getStoryContext(story);

const [globals, setGlobals] = useState(storyContext.globals);
useEffect(() => {
const onGlobalsUpdated = (changed: { globals: Globals }) => {
setGlobals(changed.globals);
};
context.channel.on(GLOBALS_UPDATED, onGlobalsUpdated);
return () => context.channel.off(GLOBALS_UPDATED, onGlobalsUpdated);
}, [context.channel]);

return [globals];
};

export const Controls: FC<ControlsProps> = (props) => {
const { of } = props;
const context = useContext(DocsContext);
const { story } = context.resolveOf(of || 'story', ['story']);
const { parameters, argTypes } = 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 [globals] = useGlobals(story, context);

const filteredArgTypes = filterArgTypes(argTypes, include, exclude);

return (
<PureArgsTable
rows={filteredArgTypes}
args={args}
globals={globals}
updateArgs={updateArgs}
resetArgs={resetArgs}
sort={sort}
/>
);
};
5 changes: 2 additions & 3 deletions code/ui/blocks/src/blocks/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Title } from './Title';
import { Subtitle } from './Subtitle';
import { Description } from './Description';
import { Primary } from './Primary';
import { PRIMARY_STORY } from './types';
import { ArgsTable } from './ArgsTable';
import { Controls } from './Controls';
import { Stories } from './Stories';

export const DocsPage: FC = () => (
Expand All @@ -14,7 +13,7 @@ export const DocsPage: FC = () => (
<Subtitle />
<Description />
<Primary />
<ArgsTable story={PRIMARY_STORY} />
<Controls />
<Stories />
</>
);
50 changes: 50 additions & 0 deletions code/ui/blocks/src/examples/ControlsParameters.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ControlsParameters } from './ControlsParameters';

/**
* Reference stories to be used by the Controls stories
*/
const meta = {
title: 'Example/Stories for the Controls Block',
component: ControlsParameters,
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<typeof ControlsParameters>;

export default meta;
type Story = StoryObj<typeof meta>;

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: { controls: { include: ['a'] } } },
};

export const Exclude = {
...NoParameters,
parameters: { docs: { controls: { exclude: ['a'] } } },
};

export const Sort = {
...NoParameters,
parameters: { docs: { controls: { sort: 'alpha' } } },
};
5 changes: 5 additions & 0 deletions code/ui/blocks/src/examples/ControlsParameters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

type PropTypes = { a?: string; b: string };

export const ControlsParameters = ({ a = 'a', b }: PropTypes) => <div>Example story</div>;