mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-06 07:21:16 +08:00
Addon-docs: DocsPage slots for fine-grained user control (#7680)
Addon-docs: DocsPage slots for fine-grained user control
This commit is contained in:
commit
4f9a846103
@ -30,12 +30,12 @@ const str = (o: any) => {
|
||||
throw new Error(`Description: expected string, got: ${JSON.stringify(o)}`);
|
||||
};
|
||||
|
||||
const getNotes = (notes?: Notes) =>
|
||||
export const getNotes = (notes?: Notes) =>
|
||||
notes && (typeof notes === 'string' ? notes : str(notes.markdown) || str(notes.text));
|
||||
|
||||
const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text));
|
||||
export const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(info.text));
|
||||
|
||||
const getDocgen = (component?: Component) =>
|
||||
export const getDocgen = (component?: Component) =>
|
||||
component && component.__docgenInfo && str(component.__docgenInfo.description);
|
||||
|
||||
export const getDescriptionProps = (
|
||||
|
@ -2,22 +2,33 @@ import React from 'react';
|
||||
|
||||
import { parseKind } from '@storybook/router';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { DocsPage as PureDocsPage, DocsPageProps } from '@storybook/components';
|
||||
import {
|
||||
DocsPage as PureDocsPage,
|
||||
DocsPageProps as PureDocsPageProps,
|
||||
PropsTable,
|
||||
PropsTableProps,
|
||||
} from '@storybook/components';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { DocsContainer } from './DocsContainer';
|
||||
import { Description } from './Description';
|
||||
import { Description, getDocgen } from './Description';
|
||||
import { Story } from './Story';
|
||||
import { Preview } from './Preview';
|
||||
import { Props } from './Props';
|
||||
import { Props, getPropsTableProps } from './Props';
|
||||
|
||||
enum DocsStoriesType {
|
||||
ALL = 'all',
|
||||
PRIMARY = 'primary',
|
||||
REST = 'rest',
|
||||
}
|
||||
export type StringSlot = (context: DocsContextProps) => string | void;
|
||||
export type PropsSlot = (context: DocsContextProps) => PropsTableProps | void;
|
||||
export type StorySlot = (
|
||||
storyData: StoryData,
|
||||
isPrimary: boolean,
|
||||
context: DocsContextProps
|
||||
) => DocsStoryProps;
|
||||
|
||||
interface DocsStoriesProps {
|
||||
type?: DocsStoriesType;
|
||||
export interface DocsPageProps {
|
||||
titleSlot: StringSlot;
|
||||
subtitleSlot: StringSlot;
|
||||
descriptionSlot: StringSlot;
|
||||
propsSlot: PropsSlot;
|
||||
storySlot: StorySlot;
|
||||
}
|
||||
|
||||
interface DocsStoryProps {
|
||||
@ -34,19 +45,28 @@ interface StoryData {
|
||||
parameters?: any;
|
||||
}
|
||||
|
||||
const getDocsStories = (type: DocsStoriesType, componentStories: StoryData[]): DocsStoryProps[] => {
|
||||
let stories = componentStories;
|
||||
if (type !== DocsStoriesType.ALL) {
|
||||
const [first, ...rest] = stories;
|
||||
stories = type === DocsStoriesType.PRIMARY ? [first] : rest;
|
||||
}
|
||||
return stories.map(({ id, name, parameters: { notes, info } }) => ({
|
||||
id,
|
||||
name,
|
||||
description: notes || info || null,
|
||||
}));
|
||||
const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
|
||||
const {
|
||||
hierarchyRootSeparator: rootSeparator,
|
||||
hierarchySeparator: groupSeparator,
|
||||
} = (parameters && parameters.options) || {
|
||||
hierarchyRootSeparator: '|',
|
||||
hierarchySeparator: /\/|\./,
|
||||
};
|
||||
const { groups } = parseKind(selectedKind, { rootSeparator, groupSeparator });
|
||||
return (groups && groups[groups.length - 1]) || selectedKind;
|
||||
};
|
||||
|
||||
const defaultSubtitleSlot: StringSlot = ({ parameters }) =>
|
||||
parameters && parameters.componentDescription;
|
||||
|
||||
const defaultPropsSlot: PropsSlot = context => getPropsTableProps({ of: '.' }, context);
|
||||
|
||||
const defaultDescriptionSlot: StringSlot = ({ parameters }) =>
|
||||
parameters && getDocgen(parameters.component);
|
||||
|
||||
const defaultStorySlot: StorySlot = (storyData, isPrimary, context) => storyData;
|
||||
|
||||
const StoriesHeading = styled.h2();
|
||||
const StoryHeading = styled.h3();
|
||||
|
||||
@ -65,58 +85,35 @@ const DocsStory: React.FunctionComponent<DocsStoryProps> = ({
|
||||
</>
|
||||
);
|
||||
|
||||
const DocsStories: React.FunctionComponent<DocsStoriesProps> = ({ type = DocsStoriesType.ALL }) => (
|
||||
const DocsPage: React.FunctionComponent<DocsPageProps> = ({
|
||||
titleSlot,
|
||||
subtitleSlot,
|
||||
descriptionSlot,
|
||||
propsSlot,
|
||||
storySlot,
|
||||
}) => (
|
||||
<DocsContext.Consumer>
|
||||
{({ selectedKind, storyStore }) => {
|
||||
{context => {
|
||||
const title = titleSlot(context) || '';
|
||||
const subtitle = subtitleSlot(context) || '';
|
||||
const description = descriptionSlot(context) || '';
|
||||
const propsTableProps = propsSlot(context);
|
||||
|
||||
const { selectedKind, storyStore } = context;
|
||||
const componentStories = (storyStore.raw() as StoryData[]).filter(
|
||||
s => s.kind === selectedKind
|
||||
);
|
||||
const stories = getDocsStories(type, componentStories);
|
||||
if (stories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const expanded = type !== DocsStoriesType.PRIMARY;
|
||||
return (
|
||||
<>
|
||||
{expanded && <StoriesHeading>Stories</StoriesHeading>}
|
||||
{stories.map(s => (
|
||||
<DocsStory key={s.id} expanded={expanded} {...s} />
|
||||
))}
|
||||
</>
|
||||
const [primary, ...rest] = componentStories.map((storyData, idx) =>
|
||||
storySlot(storyData, idx === 0, context)
|
||||
);
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
|
||||
const getDocsPageProps = (context: DocsContextProps): DocsPageProps => {
|
||||
const { selectedKind, selectedStory, parameters } = context;
|
||||
const {
|
||||
hierarchyRootSeparator: rootSeparator,
|
||||
hierarchySeparator: groupSeparator,
|
||||
} = (parameters && parameters.options) || {
|
||||
hierarchyRootSeparator: '|',
|
||||
hierarchySeparator: /\/|\./,
|
||||
};
|
||||
|
||||
const { groups } = parseKind(selectedKind, { rootSeparator, groupSeparator });
|
||||
const title = (groups && groups[groups.length - 1]) || selectedKind;
|
||||
|
||||
return {
|
||||
title,
|
||||
subtitle: parameters && parameters.componentDescription,
|
||||
};
|
||||
};
|
||||
|
||||
const DocsPage: React.FunctionComponent = () => (
|
||||
<DocsContext.Consumer>
|
||||
{context => {
|
||||
const docsPageProps = getDocsPageProps(context);
|
||||
return (
|
||||
<PureDocsPage {...docsPageProps}>
|
||||
<Description of="." />
|
||||
<DocsStories type={DocsStoriesType.PRIMARY} />
|
||||
<Props of="." />
|
||||
<DocsStories type={DocsStoriesType.REST} />
|
||||
<PureDocsPage title={title} subtitle={subtitle}>
|
||||
<Description markdown={description} />
|
||||
<DocsStory {...primary} expanded={false} />
|
||||
{propsTableProps && <PropsTable {...propsTableProps} />}
|
||||
<StoriesHeading>Stories</StoriesHeading>
|
||||
{rest.map(story => story && <DocsStory {...story} expanded />)}
|
||||
</PureDocsPage>
|
||||
);
|
||||
}}
|
||||
@ -125,11 +122,28 @@ const DocsPage: React.FunctionComponent = () => (
|
||||
|
||||
interface DocsPageWrapperProps {
|
||||
context: DocsContextProps;
|
||||
titleSlot?: StringSlot;
|
||||
subtitleSlot?: StringSlot;
|
||||
descriptionSlot?: StringSlot;
|
||||
propsSlot?: PropsSlot;
|
||||
storySlot?: StorySlot;
|
||||
}
|
||||
|
||||
const DocsPageWrapper: React.FunctionComponent<DocsPageWrapperProps> = ({ context }) => (
|
||||
const DocsPageWrapper: React.FunctionComponent<DocsPageWrapperProps> = ({
|
||||
context,
|
||||
titleSlot = defaultTitleSlot,
|
||||
subtitleSlot = defaultSubtitleSlot,
|
||||
descriptionSlot = defaultDescriptionSlot,
|
||||
propsSlot = defaultPropsSlot,
|
||||
storySlot = defaultStorySlot,
|
||||
}) => (
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
<DocsContainer context={{ ...context, mdxKind: context.selectedKind }} content={DocsPage} />
|
||||
<DocsContainer
|
||||
context={{ ...context, mdxKind: context.selectedKind }}
|
||||
content={() => (
|
||||
<DocsPage {...{ titleSlot, subtitleSlot, descriptionSlot, storySlot, propsSlot }} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
export { DocsPageWrapper as DocsPage };
|
||||
|
@ -4,6 +4,7 @@ import { Global, ThemeProvider, themes, createReset, convert } from '@storybook/
|
||||
import { withCssResources } from '@storybook/addon-cssresources';
|
||||
import { withA11y } from '@storybook/addon-a11y';
|
||||
import { withNotes } from '@storybook/addon-notes';
|
||||
import { DocsPage } from '@storybook/addon-docs/blocks';
|
||||
|
||||
import 'storybook-chromatic';
|
||||
|
||||
@ -57,6 +58,10 @@ addParameters({
|
||||
{ name: 'light', value: '#eeeeee' },
|
||||
{ name: 'dark', value: '#222222' },
|
||||
],
|
||||
// eslint-disable-next-line react/prop-types
|
||||
docs: ({ context }) => (
|
||||
<DocsPage context={context} subtitleSlot={({ selectedKind }) => `Subtitle: ${selectedKind}`} />
|
||||
),
|
||||
});
|
||||
|
||||
configure(
|
||||
|
Loading…
x
Reference in New Issue
Block a user