Merge pull request #16159 from storybookjs/15762-mdx-csf3-support

MDX: Support CSF3 play/render functions
This commit is contained in:
Michael Shilman 2021-09-26 05:53:07 +08:00 committed by GitHub
commit 8395c06d04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 424 additions and 15 deletions

View File

@ -22,11 +22,7 @@ export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;
type PureStoryProps = ComponentProps<typeof PureStory>;
type Annotations = Pick<
StoryAnnotations,
'decorators' | 'parameters' | 'args' | 'argTypes' | 'loaders'
>;
type CommonProps = Annotations & {
type CommonProps = StoryAnnotations & {
height?: string;
inline?: boolean;
};

View File

@ -0,0 +1,28 @@
import { Meta, Story } from '@storybook/addon-docs';
import Button from '../../components/TsButton';
<Meta
title="csf3"
component={Button}
play={() => console.log('component play')}
render={(args) => (
<div style={{ border: '5px solid green' }}>
<Button {...args} />
</div>
)}
/>
# CSF3 Features
<Story name="Inherited Render" />
<Story
name="Story Render"
render={(args) => (
<div style={{ border: '5px solid red' }}>
<Button {...args} />
</div>
)}
/>
<Story name="Story Play" play={() => console.log('story play')} />

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 auto-title-docs-only.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<pre>
<code parentName=\\"pre\\" {...{}}>{\` import { Meta } from '@storybook/addon-docs';
<Meta />
# Auto-title Docs Only
Spme **markdown** here!
\`}</code>
</pre>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
const componentMeta = { includeStories: [] };
const mdxStoryNameToKey = {};
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 auto-title.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta component={Button} mdxType=\\"Meta\\" />
<Story name=\\"Basic\\" mdxType=\\"Story\\">
<Button mdxType=\\"Button\\">Basic</Button>
</Story>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = () => <Button>Basic</Button>;
basic.storyName = 'Basic';
basic.parameters = { storySource: { source: '<Button>Basic</Button>' } };
const componentMeta = { component: Button, includeStories: ['basic'] };
const mdxStoryNameToKey = { Basic: 'basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 component-render.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta
title=\\"Button\\"
component={Button}
render={(args) => <Button {...args} mdxType=\\"Button\\" />}
mdxType=\\"Meta\\"
/>
<Story name=\\"Basic\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = {};
basic.storyName = 'Basic';
basic.parameters = { storySource: { source: '{}' } };
const componentMeta = {
title: 'Button',
component: Button,
render: (args) => <Button {...args} />,
includeStories: ['basic'],
};
const mdxStoryNameToKey = { Basic: 'basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 default-render.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" component={Button} mdxType=\\"Meta\\" />
<Story name=\\"Basic\\" mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = {};
basic.storyName = 'Basic';
basic.parameters = { storySource: { source: '{}' } };
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
const mdxStoryNameToKey = { Basic: 'basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 story-play.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" component={Button} mdxType=\\"Meta\\" />
<Story name=\\"Basic\\" play={() => console.log('play')} mdxType=\\"Story\\" />
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = {};
basic.storyName = 'Basic';
basic.parameters = { storySource: { source: '{}' } };
basic.play = () => console.log('play');
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
const mdxStoryNameToKey = { Basic: 'basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs-mdx-compiler-plugin csf3 story-render.mdx 1`] = `
"/* @jsxRuntime classic */
/* @jsx mdx */
import { assertIsFn, AddContext } from '@storybook/addon-docs';
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
const layoutProps = {};
const MDXLayout = 'wrapper';
function MDXContent({ components, ...props }) {
return (
<MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
<Meta title=\\"Button\\" component={Button} mdxType=\\"Meta\\" />
<Story
name=\\"Basic\\"
render={(args) => <Button {...args} mdxType=\\"Button\\" />}
mdxType=\\"Story\\"
/>
</MDXLayout>
);
}
MDXContent.isMDXComponent = true;
export const basic = {};
basic.storyName = 'Basic';
basic.parameters = { storySource: { source: '{}' } };
basic.render = (args) => <Button {...args} />;
const componentMeta = { title: 'Button', component: Button, includeStories: ['basic'] };
const mdxStoryNameToKey = { Basic: 'basic' };
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
...(componentMeta.parameters.docs || {}),
page: () => (
<AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}>
<MDXContent />
</AddContext>
),
};
export default componentMeta;
"
`;

View File

@ -415,15 +415,98 @@ describe('docs-mdx-compiler-plugin', () => {
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta title="Button" />
# Bad story
<Story>
<Button>One</Button>
</Story>
`)
).rejects.toThrow('Expected a Story name, id, or story attribute');
});
describe('csf3', () => {
it('auto-title-docs-only.mdx', () => {
expect(
generate(dedent`
import { Meta } from '@storybook/addon-docs';
<Meta />
# Auto-title Docs Only
Spme **markdown** here!
`)
).toMatchSpecificSnapshot(snap('auto-title-docs-only'));
});
it('auto-title.mdx', () => {
expect(
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta component={Button} />
<Story name="Basic">
<Button>Basic</Button>
</Story>
`)
).toMatchSpecificSnapshot(snap('auto-title'));
});
it('default-render.mdx', () => {
expect(
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta title="Button" component={Button} />
<Story name="Basic" />
`)
).toMatchSpecificSnapshot(snap('default-render'));
});
it('component-render.mdx', () => {
expect(
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta title="Button" component={Button} render={(args) => <Button {...args} />} />
<Story name="Basic" />
`)
).toMatchSpecificSnapshot(snap('component-render'));
});
it('story-render.mdx', () => {
expect(
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta title="Button" component={Button} />
<Story name="Basic" render={(args) => <Button {...args} />} />
`)
).toMatchSpecificSnapshot(snap('story-render'));
});
it('story-play.mdx', () => {
expect(
generate(dedent`
import { Button } from '@storybook/react/demo';
import { Story, Meta } from '@storybook/addon-docs';
<Meta title="Button" component={Button} />
<Story name="Basic" play={() => console.log('play')} />
`)
).toMatchSpecificSnapshot(snap('story-play'));
});
});
});

View File

@ -152,13 +152,18 @@ function genStoryExport(ast: t.JSXElement, context: Context) {
let sourceCode = null;
let storyVal = null;
if (!bodyNodes.length) {
// plain text node
const { code } = generate(ast.children[0], {});
storyCode = `'${code}'`;
sourceCode = storyCode;
storyVal = `() => (
${storyCode}
)`;
if (ast.children.length > 0) {
// plain text node
const { code } = generate(ast.children[0], {});
storyCode = `'${code}'`;
sourceCode = storyCode;
storyVal = `() => (
${storyCode}
)`;
} else {
sourceCode = '{}';
storyVal = '{}';
}
} else {
const bodyParts = bodyNodes.map((bodyNode) => getBodyPart(bodyNode, context));
// if we have more than two children
@ -222,6 +227,18 @@ function genStoryExport(ast: t.JSXElement, context: Context) {
statements.push(`${storyKey}.loaders = ${loaderCode};`);
}
const play = expressionOrNull(getAttr(ast.openingElement, 'play'));
if (play) {
const { code: playCode } = generate(play, {});
statements.push(`${storyKey}.play = ${playCode};`);
}
const render = expressionOrNull(getAttr(ast.openingElement, 'render'));
if (render) {
const { code: renderCode } = generate(render, {});
statements.push(`${storyKey}.render = ${renderCode};`);
}
context.storyNameToKey[storyName] = storyKey;
return {
@ -282,6 +299,7 @@ function genMeta(ast: t.JSXElement, options: CompilerOptions) {
const subcomponents = genAttribute('subcomponents', ast.openingElement);
const args = genAttribute('args', ast.openingElement);
const argTypes = genAttribute('argTypes', ast.openingElement);
const render = genAttribute('render', ast.openingElement);
return {
title,
@ -293,6 +311,7 @@ function genMeta(ast: t.JSXElement, options: CompilerOptions) {
subcomponents,
args,
argTypes,
render,
};
}